This example illustrates how to write a operation class which uses the execution framework, i.e. reports the outcome of the execution by using the execution result object.
The class com.alpha.pineapple.plugin.net.operation.TestDeployedConfiguration is used an example. The class is part of the infrastructure test plugin where it implements the test-deployed-configuration operation.
@PluginOperation( OperationNames.TEST_DEPLOYED_CONFIGURATION ) public class TestDeployedConfiguration implements Operation { Logger logger = Logger.getLogger( this.getClass().getName() ); /** * Commands */ @Resource Command testResolveNameToIpAddressCommand; @Resource Command testHostListenPortCommand; @Resource Command testLoadBalancingCommand; @Resource Command testStickynessCommand; @Resource Command testHttpRedirectCommand; @Resource Command testHttpHeaderCommand; @Resource Command testUncPathCommand; // Command runner @Resource CommandRunner commandRunner; // Model mapper object. @Resource Mapper mapper; // Message provider for I18N support. @Resource MessageProvider messageProvider; public void execute( Object content, Session session, ExecutionResult executionResult ) throws PluginExecutionFailedException { // validate parameters Validate.notNull( content, "content is undefined." ); Validate.notNull( session, "session is undefined." ); Validate.notNull( executionResult, "executionResult is undefined." ); // log debug message if ( logger.isDebugEnabled() ) { Object[] args = { content.getClass().getName(), content }; String message = messageProvider.getMessage("tdc.start", args ); logger.debug( message ); } // throw exception if required type isn't available if ( !( content instanceof Infrastructure ) ) { Object[] args = { Infrastructure.class }; String message = messageProvider.getMessage("tdc.content_typecast_failed", args ); throw new PluginExecutionFailedException( message ); } // configure command runner with execution result commandRunner.setExecutionResult( executionResult ); try { // type cast to infrastructure model object Infrastructure model = (Infrastructure) content; // put proxies into a map HashMap<String, Proxy> proxyMap = new HashMap<String, Proxy>(); for ( Proxy proxy : model.getProxy() ) { proxyMap.put( proxy.getId(), proxy ); } // run tests runResolveToIpTests( model ); runHostListenPortTests( model ); runStickynessTests( model, proxyMap ); runLoadBalancingTests( model, proxyMap ); runHttpRedirectTests( model, proxyMap ); runHttpHeaderTests( model, proxyMap ); runFtpServerActiveTests( model ); runFtpServerContainsDirectoryTests( model ); runFtpServerCreateDirectoryTests( model ); runAccessUncPathTests( model ); // compute execution state from children executionResult.completeAsComputed(messageProvider, "tdc.completed", null, "tdc.failed", null ); } catch ( CommandException e ) { Object[] args = { StackTraceHelper.getStrackTrace( e ) }; String message = messageProvider.getMessage("tdc.error", args ); throw new PluginExecutionFailedException( message, e ); } } // all of the test methods goes here... }
The execution result object is used to report the outcome of the operation. The result execution is used to capture one of three outcomes:
It is the responsibility of the code which executes the operation to create a execution result object which can be used by the operation to report the outcome of it execution.
The execution result for the operation is created by the Pineapple core component in the class com.alpha.pineapple.command.InvokePluginsCommand.
The execution result object is the 3rd argument in the Execute(Object content, Session session, ExecutionResult executionResult) method in the com.alpha.pineapple.plugin.Operation interface which is implemented by all operations.
The operation uses a command runner object to execute test commands and capture the outcome of the execution of each command in a separate execution result object.
The command runner is defined as a annotated field on the operation and injects using the Spring dependency injection mechanism:
/** * Command runner */ @Resource CommandRunner commandRunner;
The command runner is configured with the execution result for the operation prior to running any commands:
// configure command runner with execution result commandRunner.setExecutionResult( executionResult );
Afterwards when the runner invoked to run a command, it will create a child execution object from the operation execution result object and use the child to capture the execution of the commands:
void runHostListenPortTests( Infrastructure model ) throws CommandException { for ( HostListenPortTest test : model.getHostListenPortTest() ) { // create description String description = "..some description...." // create context Context context = commandRunner.createContext(); // map model content to context mapper.mapHostListenPortTest( test, context ); // run test commandRunner.run(testHostListenPortCommand, description, context); } }
Whether the operation succeeds or fails depends on the outcome of the tests which are executed by the operation. The tests are implemented as Chain commands and the outcome of their execution is captured in child execution results which are created by the command runner when each test is run.
After all tests are run the operation computes its final state by invoking the completeAsComputed(...) method and the operation is done executing:
public void execute( Object content, Session session, ExecutionResult executionResult ) throws PluginExecutionFailedException { // some sanity tests and logging here // configure command runner with execution result commandRunner.setExecutionResult( executionResult ); try { // type cast to infrastructure model object Infrastructure model = (Infrastructure) content; // put proxies into a map HashMap<String, Proxy> proxyMap = .....; // run tests runResolveToIpTests( model ); runHostListenPortTests( model ); runStickynessTests( model, proxyMap ); runLoadBalancingTests( model, proxyMap ); runHttpRedirectTests( model, proxyMap ); runHttpHeaderTests( model, proxyMap ); runFtpServerActiveTests( model ); runFtpServerContainsDirectoryTests( model ); runFtpServerCreateDirectoryTests( model ); runAccessUncPathTests( model ); // compute execution state from children executionResult.completeAsComputed(messageProvider, "tdc.completed", null, "tdc.failed", null ); } catch ( CommandException e ) { Object[] args = { StackTraceHelper.getStrackTrace( e ) }; String message = messageProvider.getMessage("tdc.error", args ); throw new PluginExecutionFailedException( message, e ); } }
If the operation terminates with an error then the state of the execution result object isn't updated.
The operation should catch any exception it knows could occur in its code and then rethrow it as an PluginExecutionFailedException:
public void execute( Object content, Session session, ExecutionResult executionResult ) throws PluginExecutionFailedException { try { // logic, logic // compute execution state from children executionResult.completeAsComputed(messageProvider, "tdc.completed", null, "tdc.failed", null ); } catch ( CommandException e ) { Object[] args = { StackTraceHelper.getStrackTrace( e ) }; String message = messageProvider.getMessage("tdc.error", args ); throw new PluginExecutionFailedException( message, e ); } }
The PluginExecutionFailedException and any other exception will be caught by Pineapple in the class InvokePluginsCommand which will update the state of the operation execution result object to error. The error handling in Pineapple could code look something like like:
try { // invoke operation operation.execute( moduleModel, session, modelResult ); } catch (Exception e) { // set result with error modelResult.completeAsError(messageProvider, "ipc.error", null, e) }