Package org.pentaho.platform.engine.services.solution

Source Code of org.pentaho.platform.engine.services.solution.ActionDelegate$ResourceCallback

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License, version 2 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*
* Copyright 2006 - 2013 Pentaho Corporation.  All rights reserved.
*/

package org.pentaho.platform.engine.services.solution;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.actionsequence.dom.ActionInputConstant;
import org.pentaho.actionsequence.dom.ActionSequenceDocument;
import org.pentaho.actionsequence.dom.IActionInput;
import org.pentaho.actionsequence.dom.IActionOutput;
import org.pentaho.actionsequence.dom.IActionResource;
import org.pentaho.actionsequence.dom.IActionSequenceOutput;
import org.pentaho.platform.api.action.ActionPreProcessingException;
import org.pentaho.platform.api.action.IAction;
import org.pentaho.platform.api.action.IDefinitionAwareAction;
import org.pentaho.platform.api.action.ILoggingAction;
import org.pentaho.platform.api.action.IPreProcessingAction;
import org.pentaho.platform.api.action.ISessionAwareAction;
import org.pentaho.platform.api.action.IStreamingAction;
import org.pentaho.platform.api.engine.ActionExecutionException;
import org.pentaho.platform.api.engine.ActionValidationException;
import org.pentaho.platform.api.engine.ILogger;
import org.pentaho.platform.api.repository.IContentItem;
import org.pentaho.platform.engine.core.output.SimpleContentItem;
import org.pentaho.platform.engine.services.messages.Messages;
import org.pentaho.platform.util.beans.ActionHarness;
import org.pentaho.platform.util.beans.AlternateIndexFormatter;
import org.pentaho.platform.util.beans.PropertyNameFormatter;
import org.pentaho.platform.util.beans.SuffixAppenderFormatter;
import org.pentaho.platform.util.beans.ValueGenerator;
import org.pentaho.platform.util.beans.ValueSetErrorCallback;
import org.pentaho.platform.util.web.MimeHelper;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* The purpose of the {@link ActionDelegate} is to represent an action object (which implements {@link IAction}) as
* an {@link IComponent}.
*
* @see IAction
*/
@SuppressWarnings( "serial" )
public class ActionDelegate extends ComponentBase {

  private ActionHarness actionHarness;

  protected static final PropertyNameFormatter ALTERNATE_INDEX_FORMATTER = new AlternateIndexFormatter();

  protected static final PropertyNameFormatter COMPATIBILITY_FORMATTER = new ActionSequenceCompatibilityFormatter();

  protected static final PropertyNameFormatter STREAM_APPENDER_FORMATTER = new SuffixAppenderFormatter( "Stream" ); //$NON-NLS-1$

  private Object actionBean;

  private IActionInput[] actionDefintionInputs;

  private IActionOutput[] actionDefintionOutputs;

  public ActionDelegate( Object actionBean ) {
    this.actionBean = actionBean;
    actionHarness = new ActionHarness( (IAction) actionBean );
  }

  public Object getActionBean() {
    return actionBean;
  }

  /**
   * Clean-up should happen in the {@link IAction#execute()}
   **/
  @Override
  public void done() {
  }

  /**
   * This method will tell you if an output in the action definition references an output stream that has a
   * global/public destination, such as "response", or "content". An action definition output is considered thusly,
   * if it has a counterpart of the same name in the action sequence outputs AND that output is of type "content"
   * AND it has declared one or more destinations.
   *
   * @param contentOutput
   *          the action definition output to check
   * @return true if this output corresponds to a public destination-bound output
   */
  protected boolean hasPublicDestination( IActionOutput contentOutput ) {
    String resolvedName = contentOutput.getPublicName();
    IActionSequenceOutput publicOutput = getActionDefinition().getDocument().getOutput( resolvedName );
    if ( publicOutput == null ) {
      return false;
    }
    return ( publicOutput.getType().equals( ActionSequenceDocument.CONTENT_TYPE )
      && publicOutput.getDestinations().length > 0 );
  }

  /**
   * Wires up inputs outputs and resources to an Action and executes it.
   */
  @Override
  protected boolean executeAction() throws Throwable {
    //
    // Set inputs
    //
    InputErrorCallback errorCallback = new InputErrorCallback();
    for ( IActionInput input : getActionDefinition().getInputs() ) {

      Object inputValue = input.getValue();

      if ( input instanceof ActionInputConstant ) {
        // if the input is coming from the component definition section,
        // do parameter replacement on the string and the result of that
        // is the input value
        inputValue = input.getStringValue( true );
      }

      errorCallback.setValue( inputValue );
      actionHarness.setValue( input.getName(), inputValue, errorCallback, COMPATIBILITY_FORMATTER,
          ALTERNATE_INDEX_FORMATTER );
    }

    //
    // Set resources
    //
    ResourceCallback resourceCallback = new ResourceCallback();
    for ( IActionResource res : getActionDefinition().getResources() ) {
      actionHarness.setValue( res.getName(), res.getInputStream(), resourceCallback, COMPATIBILITY_FORMATTER,
          ALTERNATE_INDEX_FORMATTER );
    }

    //
    // Provide output stream for the streaming action. We are going to look for all outputs where
    // type = "content", and derive output streams to hand to the IStreamingAction.
    //
    Map<String, IContentItem> outputContentItems = new HashMap<String, IContentItem>();
    StreamOutputErrorCallback streamingOutputCallback = new StreamOutputErrorCallback();
    OuputStreamGenerator outputStreamGenerator = new OuputStreamGenerator( outputContentItems );

    IActionOutput[] contentOutputs = getActionDefinition().getOutputs( ActionSequenceDocument.CONTENT_TYPE );
    if ( contentOutputs.length > 0 ) {
      for ( IActionOutput contentOutput : contentOutputs ) {
        outputStreamGenerator.setContentOutput( contentOutput );
        actionHarness.setValue( contentOutput.getName(), outputStreamGenerator, streamingOutputCallback,
            STREAM_APPENDER_FORMATTER, COMPATIBILITY_FORMATTER, ALTERNATE_INDEX_FORMATTER );
      }
    }
    // else, This is not necessarily an error condition. Let the action bean decide.

    //
    // Execute the Action if the bean is executable
    //
    if ( actionBean instanceof IAction ) {
      ( (IAction) actionBean ).execute();
    }

    //
    // Get and store outputs
    //
    for ( IActionOutput output : actionDefintionOutputs ) {
      String outputName = output.getName();
      outputName = COMPATIBILITY_FORMATTER.format( outputName );

      // if streaming output, add it to the context and don't try to get it from the Action bean
      if ( outputContentItems.containsKey( outputName ) ) {
        IContentItem contentItem = outputContentItems.get( outputName );

        if ( !( contentItem instanceof SimpleContentItem ) ) {
          // this is a special output for streaming actions and does not require a bean accessor
          output.setValue( contentItem );
        }
      } else if ( actionHarness.isReadable( outputName ) ) {
        Object outputVal = actionHarness.getValue( outputName );
        output.setValue( outputVal );
      } else {
        if ( loggingLevel <= ILogger.WARN ) {
          warn( Messages.getInstance().getString( "ActionDelegate.WARN_OUTPUT_NOT_READABLE", //$NON-NLS-1$
              outputName, output.getType(), actionBean.getClass().getSimpleName() ) );
        }
      }
    }
    return true;
  }

  class StreamOutputErrorCallback implements ValueSetErrorCallback {
    public void failedToSetValue( Object bean, String name, Object value, String destPropertyType, Throwable cause )
      throws ActionExecutionException {
      throw new ActionExecutionException( Messages.getInstance().getErrorString(
          "ActionDelegate.ERROR_0008_FAILED_TO_SET_STREAM", name, OutputStream.class.getName(), //$NON-NLS-1$
          actionBean.getClass().getSimpleName(), destPropertyType ), cause );
    }

    public void propertyNotWritable( Object bean, String name ) {
      if ( loggingLevel <= ILogger.WARN ) {
        warn( Messages.getInstance().getString( "ActionDelegate.WARN_INPUT_NOT_WRITABLE", actionBean //$NON-NLS-1$
            .getClass().getSimpleName(), name, OutputStream.class.getName() ) );
      }
    }
  };

  class OuputStreamGenerator implements ValueGenerator {

    private Map<String, IContentItem> outputContentItems;

    private IActionOutput curActionOutput;

    private boolean streamingCheckPerformed = false;

    public OuputStreamGenerator( Map<String, IContentItem> outputContentItems ) {
      this.outputContentItems = outputContentItems;
    }

    public void setContentOutput( IActionOutput actionOutput ) throws Exception {
      curActionOutput = actionOutput;
    }

    public Object getValue( String name ) throws Exception {
      // fail early if we cannot handle stream outputs
      if ( !streamingCheckPerformed && !( actionBean instanceof IStreamingAction ) ) {
        throw new ActionExecutionException( Messages.getInstance().getErrorString(
            "ActionDelegate.ERROR_0002_ACTION_CANNOT_ACCEPT_STREAM", //$NON-NLS-1$
            name, actionBean.getClass().getSimpleName() ) );
      }
      streamingCheckPerformed = true;

      String mimeType = ( (IStreamingAction) actionBean ).getMimeType( name );
      if ( StringUtils.isEmpty( mimeType ) ) {
        throw new ActionValidationException( Messages.getInstance().getErrorString(
            "ActionDelegate.ERROR_0001_MIMETYPE_NOT_DECLARED" ) ); //$NON-NLS-1$
      }

      IContentItem contentItem = null;

      //
      // If the output is mapped publicly and has a destination associated with it, then we will be asking
      // the current IOuputHandler to create an IContentItem (OuputStream) for us. Otherwise, we will asking the
      // IContentOutputHandler impl registered to handle content destined for "contentrepo" to create
      // an IContentItem (OutputStream) for us.
      //
      if ( hasPublicDestination( curActionOutput ) ) {
        // most output handlers will manage multiple destinations for us and hand us back a MultiContentItem
        contentItem = getRuntimeContext().getOutputContentItem( curActionOutput.getPublicName(), mimeType );
      } else {
        String extension = MimeHelper.getExtension( mimeType );
        if ( extension == null ) {
          extension = ".bin"; //$NON-NLS-1$
        }
        contentItem = getRuntimeContext().getOutputItem( curActionOutput.getName(), mimeType, extension );
      }

      if ( contentItem == null ) {
        // this is the best I can do here to point users to a tangible problem without unwrapping code in
        // RuntimeEngine
        // - AP
        throw new ActionValidationException( Messages.getInstance().getErrorString(
            "ActionDelegate.ERROR_0003_OUTPUT_STREAM_NOT_AVAILABLE_1", //$NON-NLS-1$
            curActionOutput.getPublicName() ) );
      }

      // this will be a MultiOutputStream in the case where there is more than one destination for the content
      // output
      OutputStream contentOutputStream = contentItem.getOutputStream( getActionName() );
      if ( contentOutputStream == null ) {
        throw new ActionExecutionException( Messages.getInstance().getErrorString(
            "ActionDelegate.ERROR_0004_OUTPUT_STREAM_NOT_AVAILABLE_2", //$NON-NLS-1$
            actionBean.getClass().getSimpleName() ) );
      }

      // save this for later when we set the action outputs
      outputContentItems.put( curActionOutput.getName(), contentItem );

      return contentOutputStream;
    }
  }

  class ResourceCallback implements ValueSetErrorCallback {
    public void failedToSetValue( Object bean, String name, Object value, String destPropertyType, Throwable cause )
      throws ActionExecutionException {
      String className = ( value != null ) ? value.getClass().getName() : "ClassNameNotAvailable"; //$NON-NLS-1$
      throw new ActionExecutionException( Messages.getInstance().getErrorString(
          "ActionDelegate.ERROR_0006_FAILED_TO_SET_RESOURCE", //$NON-NLS-1$
          name, className, actionBean.getClass().getSimpleName(), destPropertyType ), cause );
    }

    public void propertyNotWritable( Object bean, String name ) {
      if ( loggingLevel <= ILogger.WARN ) {
        warn( Messages.getInstance().getString( "ActionDelegate.WARN_RESOURCE_NOT_WRITABLE", actionBean //$NON-NLS-1$
            .getClass().getSimpleName(), name, InputStream.class.getName() ) );
      }
    }
  }

  class InputErrorCallback implements ValueSetErrorCallback {
    private Object curValue;

    public void setValue( Object value ) throws Exception {
      curValue = value;
    }

    public void failedToSetValue( Object bean, String name, Object value, String destPropertyType, Throwable cause )
      throws ActionExecutionException {
      String className = ( value != null ) ? value.getClass().getName() : "ClassNameNotAvailable"; //$NON-NLS-1$
      throw new ActionExecutionException( Messages.getInstance().getErrorString(
          "ActionDelegate.ERROR_0005_FAILED_TO_SET_INPUT", //$NON-NLS-1$
          name, className, actionBean.getClass().getSimpleName(), destPropertyType ), cause );
    }

    public void propertyNotWritable( Object bean, String name ) throws Exception {
      if ( loggingLevel <= ILogger.WARN ) {
        // log a warning if there is no way to get this input to the Action
        String valueType = ( curValue == null ) ? null : curValue.getClass().getName();
        warn( Messages.getInstance().getString(
            "ActionDelegate.WARN_INPUT_NOT_WRITABLE", actionBean.getClass().getSimpleName(), //$NON-NLS-1$
            name, valueType ) );
      }
    }
  }

  /**
   * Any initialization can be done in the {@link IPreProcessingAction#doPreExecution()}
   */
  @Override
  public boolean init() {
    return true;
  }

  /**
   * Validation of Action input values should happen in the {@link IAction#execute()} This method is used as a pre
   * execution hook where we setup as much runtime information as possible prior to the actual execute call.
   **/
  @Override
  protected boolean validateAction() {
    if ( actionBean == null ) {
      throw new IllegalArgumentException( Messages.getInstance().getErrorString(
          "ActionDelegate.ERROR_0007_NO_ACTION_BEAN_SPECIFIED" ) ); //$NON-NLS-1$
    }

    //
    // Provide a commons logging logger for logging actions
    // The log name will be the name of the Action class
    //
    if ( actionBean instanceof ILoggingAction ) {
      ( (ILoggingAction) actionBean ).setLogger( LogFactory.getLog( actionBean.getClass() ) );
    }

    //
    // Provide a session to the Action if an ISessionAwareAction
    //
    if ( actionBean instanceof ISessionAwareAction ) {
      ( (ISessionAwareAction) actionBean ).setSession( getSession() );
    }

    actionDefintionInputs = getActionDefinition().getInputs();
    actionDefintionOutputs = getActionDefinition().getOutputs();

    //
    // If an Action is action-definition aware, then here is the place (prior to
    // execution) to tell it about the action definition.
    //
    List<String> inputNames = new ArrayList<String>();
    for ( IActionInput input : actionDefintionInputs ) {
      inputNames.add( input.getName() );
    }
    List<String> outputNames = new ArrayList<String>();
    for ( IActionOutput output : actionDefintionOutputs ) {
      outputNames.add( output.getName() );
    }
    if ( actionBean instanceof IDefinitionAwareAction ) {
      IDefinitionAwareAction definitionAwareAction = (IDefinitionAwareAction) actionBean;
      definitionAwareAction.setInputNames( inputNames );
      definitionAwareAction.setOutputNames( outputNames );
    }

    //
    // Invoke any pre-execution processing if the Action requires it.
    //
    if ( actionBean instanceof IPreProcessingAction ) {
      try {
        ( (IPreProcessingAction) actionBean ).doPreExecution();
      } catch ( ActionPreProcessingException e ) {
        throw new RuntimeException( e );
      }
    }
    // we do not use the return value to indicate failure.
    return true;
  }

  @Override
  protected boolean validateSystemSettings() {
    return true;
  }

  @Override
  public Log getLogger() {
    return LogFactory.getLog( ActionDelegate.class );
  }
}
TOP

Related Classes of org.pentaho.platform.engine.services.solution.ActionDelegate$ResourceCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.