Pineapple defines a framework for implementation of new plugins. The purpose of the framework is make it possible to extend Pineapple with new functionality.
The basics:
Advanced features:
Implementation related issues:
A plugin can written with two classes:
First, the plugin class:
package com.alpha.pineapple.plugin.helloworld; import com.alpha.pineapple.plugin.Plugin; @Plugin() public class PluginImpl {}
..and the operation class:
package com.alpha.pineapple.plugin.helloworld; import com.alpha.pineapple.plugin.Operation; import com.alpha.pineapple.plugin.PluginOperation; import com.alpha.pineapple.session.Session; @PluginOperation("hello-world") public class HelloWorldOperation implements Operation { public void execute(Object content, Session session, ExecutionResult result) throws PluginExecutionFailedException { System.out.println("Hello world"); }
The purpose of the plugin class is to define meta data about a plugin. The plugin class implicit defines the plugin id which is the most important meta data attribute for a plugin. It will later be described how the plugin class can be used to explicit define additional meta data attributes to control input unmarshalling for a plugin.
The plugin id is defined as the Java package where the plugin class is located in. The plugin class for the hello world plugin shown above, is located in the package com.alpha.pineapple.plugin.helloworld. As a result the plugin id for hello world plugin is com.alpha.pineapple.plugin.helloworld.
The plugin id must be unique for each plugin and only a single plugin class is allowed to be implemented in each package.
The plugin id used in the environment configuration to defined which plugin a resource is bound to at runtime. The plugin id is defined as part of the definition of a resource. The plugin id is defined in the resource attribute named plugin. For information on definition of resources, refer to the Environment configuration reference.
A plugin class is implemented by creating a class and annotating it with the @Plugin annotation.
Example: The plugin class for the Pineapple WebLogic JMX plugin (with plugin id = com.alpha.pineapple.plugin.weblogic.jmx) is implemented as:
package com.alpha.pineapple.plugin.weblogic.jmx; import com.alpha.pineapple.plugin.Plugin; @Plugin() public class PluginImpl {}
As the plugin class only serves to define meta data there is no requirements or need for implementation of any interfaces or methods.
When Pineapple is started it scans the entire class path for classes which are annotated with the @Plugin annotation. If an annotated class is found then Pineapple registers the plugin and starts to scan the package (and sub packages) where the plugin class is found for operation classes. This results in a small overhead at startup but it eliminates the need for configuration files which configures plugins. For information on what happens during Pineapple startup, refer to the Initialization of the core component.
The purpose of a operation class is to implement a single Pineapple operation in a plugin. A plugin implements an operation class for each supported operation. Pineapple can be used to execute operations of any name and each plugin only implements operation classes for each of operations that it wishes to support.
Steps required to implement an operation class:
The operation class must be located i the same package (or sub packages) as the plugin class. Pineapple considers the package where the plugin class is found as the root package of the plugin. Pineapple will limit its search to the root package and sub packages for operations classes.
The Operation interface is defined as:
package com.alpha.pineapple.plugin; import com.alpha.pineapple.session.Session; public interface Operation { public void execute( Object content, Session session, ExecutionResult result ) throws PluginExecutionFailedException; }
The interface contains a single execute(Object content, Session session, ExecutionResult result) method which will invoked by Pineapple at runtime if the environment configuration and the module given as input to the operation specifies it. The arguments values will contain:
The concepts of input unmarshalling and session handling for plugins are described in details below.
An operation class must be annotated with the @PluginOperation annotation, which defines a single meta data attribute: the operation id. The operation id defines which the operation class implements. The operation id must be unique within the root package (or sub packages) defined by the plugin class. If multiple operations is found with an identical id then Pineapple's plugin scan fails. The @PluginOperation doesn't define any default values, so a value is mandatory. Null values isn't allowed, operations with a null or empty id are discarded when Pineapple scans for plugins.
TODO: handle multiple identical operation ids
TODO: handle empty operation id
TODO: handle null operation id
Example: The skeletion of the operation class for the Pineapple WebLogic JMX plugin,which is defined with the operation id deploy-configuration :
package com.alpha.pineapple.plugin.weblogic.jmx.operation; import com.alpha.pineapple.plugin.Operation; import com.alpha.pineapple.plugin.PluginOperation; @PluginOperation("deploy-configuration") public class DeployConfiguration implements Operation { public void execute( Object content, Session session ) throws PluginExecutionFailedException { // logic goes here.. } }
If a plugin is invoked with a operation that it doesn't support then Pineapple delegates the execution to a null operation. The result is that in practice a plugin doesn't need to implement any operations at all. A plugin without any operations wouldn't be of any use.
TODO: implement.
There are two places to implement exception handling in plugins:
..there are two ways to handle exceptions in operations; .. 1) catch and update the execution result .. 2) throw an PluginExecutionFailedException and let Pineapple catch and update the execution result
..the preferred way to handle exceptions in operations is to catch them within the operation code and update the execution result accordingly.
..the operation interface defines trowing of PluginExecutionFailedException in the execute method, so an operation could throw an exception of this type an error occours. ..the throw execution will be caught by Pineapple and used to determine update the state of the execution result object for the operation. A thrown exection will result in a execution state == failed. ..the detais of the thrown execption, e.g. error message and stack trace, will be added as detail to the execution result object for operation:
catch ( Exception e ) { Object[] args = { StackTraceHelper.getStrackTrace( e ) }; String message = messageProvider.getMessage("to.error", args ); throw new PluginExecutionFailedException( message, e ); }
The Session interface defines two methods which throws typed exceptions:
package com.alpha.pineapple.session; public interface Session { public void connect(Resource resource, Credential credential) throws SessionConnectException; public void disconnect() throws SessionDisconnectException; }
The connect(..) methods should throw a SessionConnectException when establishing a connecting to a resource fails.
The disconnect(..) methods should throw a SessionDisconnectException when terminating a connection to a resource fails.
If a plugin extends the Session interface then added methods should throw SessionException to signal errors during operation of a connected session.
..PluginException is the root exception for plugins..
..PluginExecutionFailedException and PluginInitializationFailedException are subclasses of PluginException.
..PluginInitializationFailedException is thrown internal by Pineapple when plugin activation and invocation fails.
Input unmarshalling is the process of binding schema-based XML from a module model file to objects and delivering the objects to an invoked operation class. For information on module models, refer to the Module configuration reference.
The first step is to define the data model for the plugin. The model is defined in XML schema. The schema can either be defined as part of the implementation of the plugin or an existing shcema can be used.
The recommended namespace for plugin is:
http://pineapple.dev.java.net/ns/plugin/${plugin-name} where ${plugin-name} is substituted with the plugin name.
Example: The namespace for the infrastructure plugin is http://pineapple.dev.java.net/ns/plugin/infrastructure_1_0
Example: The Weblogic JMX plugin uses a schema which was defined by BEA (r.i.p.). The namespace of this schema is http://www.bea.com/ns/weblogic/920/domain
A XML binding technology such a JAXB or XMLBeans should be used to generate the model classes. The model classes should be in included in the plugin build.
If the plugin schema is defined as part of the plugin then it is.... TODO: write...
If the model classes already exists because the an existing schema is used, then reference the classes in the Maven build to ensure that they are included in the build.
Pineapple uses the OXM (object-to-XML) portion of the Spring framewok for unmarshalling of schema-based module models in XML file to objects. Spring-OXM is used because it provide a unifying interface for accessing different OXM frameworks. The core part of the Spring-OXM framework is the Marshaller and Unmarshaller interface. Pineapple uses the Unmarshaller interface for binding XML to objects. For information on Spring OXM, refer to the Spring OXM site.
Input unmarshalling is configured on the @Plugin annotation on a plugin class. The @Plugin annotation supports two optional meta data attributes for configuring input unmarshalling:
The two attributes is used in conjuction to setup unmarshalling.
Defines the name of an XML file which contain the definition of a Spring application context. The purpose of the application context is to configure a Spring-OXM unmarshaller. When Pineapple is started it will attempt to load the file from the classpath, so any path information in the attribute must be relative to the classpath.
Example: The plugin class for the blue dream plugin defines a value for the configFile attribute:
package com.alpha.pineapple.plugin.bluedream; import com.alpha.pineapple.plugin.Plugin; @Plugin(configFile="dreaming-context.xml", unmarshaller="blueUnmarshaller") public class PluginImpl {}
..during startup Pineapple will attempt to load the configuration file named dreaming-context.xml from the root of the classpath.
If no value is provided for the configFile attribute then the default value is used: ${pluginId}-config.xml. The ${pluginId} is the plugin id from plugin class, i.e. the package where the plugin class is located in. It is recommended to use the default value.
If the recommended default value is used, then the ${pluginId}-config.xml file should be placed in the src/resources folder of the Maven project which implements the plugin.
Example: The plugin class for the Pineapple WebLogic JMX plugin doesn't explicitly define a value for the configFile attribute:
package com.alpha.pineapple.plugin.weblogic.jmx; import com.alpha.pineapple.plugin.Plugin; @Plugin() public class PluginImpl {}
..instead Pineapple will use the default value com.alpha.pineapple.plugin.weblogic.jmx-config.xml.
If Pineapple can't find the configuration file with the application context then marshalling is disabled and the operation is invoked with a null value.
Refering back to the initial hello world example, it didn't define any Spring configuration file for unmarshalling, so it that case the input unmarshalling was disabled.
TODO: implement.
The unmarshaller attribute on the @Plugin annotation defines the id of the unmarshaller in the Spring application context which should used to unmarshall the module model XML to objects. The Spring application context was previously defined by the configFile attribute. Pineapple will look up a marshaller with the id defined by the unmarshaller attribute and then use the returned unmarshaller object to unmarshall the XML at hand.
TODO: handle if no bean is found
TODO: handle if found bean isnt a marshaller
Example: The plugin class for the blue dream plugin defines a value for the unmarshaller attribute:
package com.alpha.pineapple.plugin.bluedream; import com.alpha.pineapple.plugin.Plugin; @Plugin(configFile="dreaming-context.xml", unmarshaller="blueUnmarshaller") public class PluginImpl {}
..during startup Pineapple will load the configuration file named dreaming-context.xml and use the unmarshaller named blueUnmarshaller in the application context.
If no value is provided for the unmarshaller attribute then the default value is used: unmarshaller. It is recommended to use the default value.
Example: The plugin class for the Pineapple WebLogic JMX plugin doesn't explicitly define a value for the unmarshaller attribute:
package com.alpha.pineapple.plugin.weblogic.jmx; import com.alpha.pineapple.plugin.Plugin; @Plugin() public class PluginImpl {}
..instead Pineapple will use the default value unmarshaller to look for a unmarshaller in the Spring application context.
The purpose of the configuration file is to define a unmarshaller bean. The id of the unmarshaller must correspond to the value of the unmarshaller attribute in the @Plugin annotation.
Use the Spring schema and the Spring-OXM schema to write the configuration file. The used schemas are:
The minimal configuration file without a unmashaller definition looks like:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <!-- insert unmarshaller definition here --> </beans>
TODO: upgrade the spring-context-2.5.xsd (Spring 3.0.0.M3) to spring-context-3.0.xsd when it is fixed in Spring 3.0.0.
To define a unmarshaller, add the stanza:
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="com.alpha.pineapple.plugin.infrastructure.model"/>
where the value if the id attribute must match the value of the unmarshaller in the @Plugin annotation. The value of contextPath attribute should match the package name which contains the model classes used by the plugin and which should be unmarshalled.
Example: The plugin infrastructure test plugin uses JABX for unmarshalling. The model classses for the plugin are located in the package com.alpha.pineapple.plugin.infrastructure.model. The configuration looks like:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <oxm:jaxb2-marshaller id="unmarshaller" contextPath="com.alpha.pineapple.plugin.infrastructure.model"/> </beans>
To define a unmarshaller, add the stanza:
<oxm:xmlbeans-marshaller id="unmarshaller"/>
where the value if the id attribute must match the value of the unmarshaller in the @Plugin annotation.
Example: The application context file for the Pineapple WebLogic JMX plugin is named com.alpha.pineapple.plugin.weblogic.jmx-config.xml. The plugin uses XMLBeans for unmarshalling, so the configuration file contains the definition of a xmlbeans-unmarshaller with the id unmarshaller:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <oxm:xmlbeans-marshaller id="unmarshaller"/> </beans>
Example #2: The plugin class for the blue dream plugin is defined as:
package com.alpha.pineapple.plugin.bluedream; import com.alpha.pineapple.plugin.Plugin; @Plugin(configFile="dreaming-context.xml", unmarshaller="blueUnmarshaller") public class PluginImpl {}
..which configures Pineapple to look for a configuration file named dreaming-context.xml. The configuration file contains:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <oxm:xmlbeans-marshaller id="blueUnmarshaller"/> </beans>
..after the file is loaded, Pineapple will use the unmarshaller named blueUnmarshaller based on the value of the unmarshaller attribute from the @Plugin annotation.
Session handling is the process using a session object to connect to a remote device accessed by a plugin. So, if a plugin needs to connect to a remote device then a plugin must implement a session class.
Steps required to implement an session class:
Plugins use the com.alpha.pineapple.session.Session interface to implement session handling code:
package com.alpha.pineapple.session; import com.alpha.pineapple.model.configuration.Credential; import com.alpha.pineapple.model.configuration.Resource; public interface Session { public void connect(Resource resource, Credential credential) throws SessionConnectException; public void disconnect() throws SessionDisconnectException; }
The interface should be extended if the operation classes in the plugin need additional methods to communicate which the session.
The purpose of the @PluginSession annotation it mark the class as the session class which should be used by the plugin for session handling.
Prior to invoking an operation, Pineapple will create a session instance and connect to the targeted resource using the connect(..) method on the session. The point is that a operation class will only be invoked will a connected session.
If connecting/disconnecting fails, Pineapple contain session retry logic which will retry to connect/disconnect a session several times prior to failing.
The connect(..) method is invoked by Pineapple prior to injecting the session object into an operation.
The disconnect(..) method is invoked by Pineapple after execution of the operation into which the session was injected.
An operation class should never invoke the connect(..) and the disconnect() methods of the session object.
It is fine to invoke all other methods to use the session logic in implemented in the session class.
Plugin operations can get access to the resource or crdential objects which is used to initialize the sessions in two ways:
If the plugin doesn't define session handling then the session parameter in the execute(...) method will invoked with a null session object which implements the Session interface.
Currently Pineapple will use the class NullSessionImpl but this is subject to change.
When an operation is invoked with public void execute( Object content, Session session, ExecutionResult result) then result contains an execution result object which is created by Pineapple with the state:
It is the responsibility of the operation class to set the execution state prior to completing its execution:
If the operation is an simple operation which doesn't invokes any commands then the operation should explicit set the state to either successful, failed, or error illustrated by this code fragment:
try { if (somethingImportantSucceeeded) { // set successful result executionResult.setState(ExecutionState.SUCCESS); // create message Object[] args = { this.hostname, this.ip }; String message = messageSource.getMessage("operation_i18n_id.succeed", args, null ); executionResult.addMessage("Message", message ); return; } // set failed result executionResult.setState(ExecutionState.FAILURE); // create message String message = messageSource.getMessage("operation_i18n_id.failed", null, null ); executionResult.addMessage("Message", message ); } catch ( SomeException e ) { // set result with error executionResult.setState(ExecutionState.ERROR); // create messages executionResult.addMessage("Message", messageSource.getMessage("operation_i18n_id.error", args, null ) ); executionResult.addMessage("StackTrace", StackTraceHelper.getStrackTrace( e )); }
If the operation is implemented using command which add child execution result objects to the execution result supplied by Pineapple, then the operation can set the state using the computed as illustrated by the code fragment:
// compute execution state from children executionResult.setState(ExecutionState.COMPUTED); // declare message String message; // handle test outcome if (executionResult.getState() == ExecutionState.SUCCESS ) { message = messageSource.getMessage("operation_i18n_id.completed", null, null ); } else { ExecutionResult[] failedTests = executionResult.getChildrenWithState( ExecutionState.FAILURE ); ExecutionResult[] errorTests = executionResult.getChildrenWithState( ExecutionState.ERROR ); Object[] args = { failedTests.length, errorTests.length }; message = messageSource.getMessage("operation_i18n_id.failed", args, null ); } // store the message executionResult.addMessage("Message", message );
When the invocation of public void execute( Object content, Session session, ExecutionResult result) returns then Pineapple validates that the execution result isn't running. If the state is still running then Pinapple sets the state as failed with the message Operation failed........ TODO: implement..
This feature requires the Enabling annotation based dependency injection in Spring to be enabled first.
An operation can gain access to a ExecutionInfo object which contains runtime information about the operation and the module. Two steps are required to implement access to the execution info object in an operation:
First, define a dependency injected field in the operation class:
/** * Execution info provider. */ @Resource ExecutionInfoProvider coreExecutionInfoProvider;
or alternatively:
/** * Execution info provider. */ @Resource(name="coreExecutionInfoProvider" ) ExecutionInfoProvider executionInfoProvider;
Second, resolve the execution info object at runtime using the execution result object for the operation as key:
// lookup execution info ExecutionInfo info = coreExecutionInfoProvider.get(result);
The execution info provider object is resolved from the name coreExecutionInfoProvider.
The name coreExecutionInfoProvider is reserved by Pineapple in plugin application contexts. If an plugin defines a bean with this name then the definition will overridden at runtime by Pineapple and the bean will be resolved to the execution info provider object instead.
This feature requires the Enabling annotation based dependency injection in Spring to be enabled first.
An operation can gain access to a RuntimeDirectoryProvider object which contains runtime information about which runtime directories Pineapple is using. One step is required to implement access to the provider object in an operation:
First, define a dependency injected field in the operation class:
/** * Runtime directory provider. */ @Resource RuntimeDirectoryProvider coreRuntimeDirectoryProvider;
or alternatively:
/** * Runtime directory provider. */ @Resource(name="coreRuntimeDirectoryProvider" ) RuntimeDirectoryProvider provider;
Use the provider at runtime:
// get modules directory File modulesDirectory = coreRuntimeDirectoryProvider.getModulesDirectory();
The runtime directory provider object is resolved from the name coreRuntimeDirectoryProvider.
The name coreRuntimeDirectoryProvider is reserved by Pineapple in plugin application contexts. If an plugin defines a bean with this name then the definition will overridden at runtime by Pineapple and the bean will be resolved to the runtime directory provider object instead.
A model element path (MEP) is a path definition which occurs in a module model which defines directories or files to be processed as part of the execution of an operation.
To support location independent model element paths, plugins can choose to support the modulepath: path prefix, which enabled users of plugin to define path such as:
<some-path>modulepath:some/resource/path/myfile.txt</some-path>
The runtime directory provider interface RuntimeDirectoryProvider provides two methods which can be used by plugins to resolve model element paths:
File resolveModelPath(String path, ModuleInfo info); boolean startsWithModulePathPrefix(String path);
If resolveModelPath is invoked with a path with starts with modulepath: then the path is resolved to ${pineapple-home-directory}\modules\${current-module-directory}\${remaining-path}.
Example: If the module alpha-module is located physically at:
c:/pineapple/modules/alpha-module
and some model within the module contains the path:
modulepath:some/resource/path/myfile.txt
then during execution of the module, the path will be resolved to:
c:/pineapple/modules/alpha-module/some/resource/path/myfile.txt
To support location independent module element paths, plugins can choose to support the modules: path prefix, which enabled users of plugin to define path such as:
<some-path>modules:some-module</some-path>
This allows one module to reference another module in a location independent way.
The runtime directory provider interface RuntimeDirectoryProvider provides two methods which can be used by plugins to resolve model element paths:
File resolveModelPath(String path, ModuleInfo info); boolean startsWithModulesPrefix(String path);
If resolveModelPath is invoked with a path with starts with modules: then the path is resolved to ${pineapple-home-directory}\modules\${some-module}.
Example: If the Pineapple modules directory ${pineapple-home-directory}\modules is resolved to:
/var/pineapple/modules
And the module beta-module, contain a model with the path:
moduleroot:alpha-module/some/path
then during execution of beta-module the path will be be resolved to:
/var/pineapple/modules/alpha-module/some/path
This feature requires the Enabling annotation based dependency injection in Spring to be enabled first.
An operation can gain access to a AdministrationProvider object which provides access to runtime administration of the core component. Two steps are required to implement access to the provider in an operation:
First, define a dependency injected field in the operation class:
/** * Administration provider. */ @Resource AdministrationProvider coreAdministrationProvider;
or alternatively:
/** * Administration provider. */ @Resource(name="coreAdministrationProvider" ) AdministrationProvider administrationProvider;
Use the provider at runtime:
// get resource repository ResourceRepository resourceRepository = coreAdministrationProvider.getResourceRepository();
The administration provider object is resolved from the name coreAdministrationProvider.
The name coreAdministrationProvider is reserved by Pineapple in plugin application contexts. If an plugin defines a bean with this name then the definition will overridden at runtime by Pineapple and the bean will be resolved to the administration provider object instead.
This feature requires the Enabling annotation based dependency injection in Spring to be enabled first.
An operation can gain access to a VariablesubstitutionProvider object which support substittion of variables from multitple source into text or files. Two steps are required to implement access to the provider in an operation:
First, define a dependency injected field in the operation class:
/** * Variable substitution provider. */ @Resource VariableSubstitutionProvider coreVariableSubstitutionProvider;
or alternatively:
/** * Administration provider. */ @Resource(name="coreVariableSubstitutionProvider" ) VariableSubstitutionProvider variableSubstitutionProvider;
Use the provider at runtime to resolve variables:
String processed = coreVariableSubstitutionProvider.substitute(source, session, result);
Or create a new (temporary file) with variables processed:
File processedFile = coreVariableSubstitutionProvider.createSubstitutedFile(sourceFile, session, result);
If the provider is invoked with a session as argument then the provider will resolve the variables from the properties defined on the resource that the session is created.
When an operation targetes multiple resources (through a list or a regular expression) then Pineapple will iterate over the resources and execute the plugin for each target resource. If the the pluign substitutes text or files using the session-resource-properties as input then each artifact will have the variables substituted in from that particular set of session-resource-properties. The result is that each artifact will have a different substituted in depending on the value of the variable in each resource.
The variable substitution provider object is resolved from the name coreVariableSubstitutionProvider.
The name coreVariableSubstitutionProvider is reserved by Pineapple in plugin application contexts. If an plugin defines a bean with this name then the definition will overridden at runtime by Pineapple and the bean will be resolved to the variable substitution provider object instead.
An operation can annotated with the * operation ID which defines an wild card operation:
package com.alpha.pineapple.plugin.something.operation; import com.alpha.pineapple.plugin.Operation; import com.alpha.pineapple.plugin.PluginOperation; @PluginOperation("*") public class DefaultOperation implements Operation { public void execute( Object content, Session session ) throws PluginExecutionFailedException { result.setState(ExecutionState.SUCCESS); // complete as success } }
When a plugin is invoked and it implements an operation with the wild card operation ID, then Pineapple will resolve any operation to the wild operation. The result is that the plugin supports any operation that Pineapple might be invoked with.
There is one exception. If a plugin implements one operation for operation "A" and an operation with the wild card operation ID "*":
package com.alpha.pineapple.plugin.something.operation; import com.alpha.pineapple.plugin.Operation; import com.alpha.pineapple.plugin.PluginOperation; @PluginOperation("*") public class WildcardOperationImpl implements Operation { public void execute( Object content, Session session ) throws PluginExecutionFailedException { system.out.println("Operation *"; result.setState(ExecutionState.SUCCESS); // complete as success } } @PluginOperation("A") public class OperationAImpl implements Operation { public void execute( Object content, Session session ) throws PluginExecutionFailedException { system.out.println("Operation A"; result.setState(ExecutionState.SUCCESS); // complete as success } }
When Pineapple is invoked with operation "A" then it will resolve the operation to "A", e.g. OperationAImpl in the example code above and print "Operation A". The named operation takes presence over the the wild card operation.
If Pineapple is invoked with any other operation, e.g. "ljewhf" or "test" or.., then it will resolve the operation to "*", e.g. WildcardOperationImpl in the example code above and print "Operation *".
Support for pojo in the plugin to use Springs annotation based dependency injection:
/** * Message source I18N support. */ @Resource MessageSource messageSource;
..can be implemented by:
The configuration of annotation based dependency injection is defined in the Spring context project, so add this dependency to the plugin project:
<dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.context</artifactId> </dependency>
The configuration of the @Resource annotation is defined in the JSR-250 api project, so add this dependency to the plugin project:
<dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> </dependency>
Add the context schema header to the plugin configuration file:
Example:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > </beans>
Add tag to enabled annotation dependency injection when Spring initializes the context:
<!-- enable annotation based configuration / dependency injection --> <context:annotation-config />
Support for internationalization (I18N) can be implemented using interfaces and classes from the pineapple-api project by:
The I18N classes are defined in the pineapple-api project, and any plugin project will define a dependency to the API project because this project also contains the plugin interfaces and annotations. Verify that this Maven dependency to the api project is defined in the plugin project POM:
<dependency> <groupId>com.alpha.pineapple</groupId> <artifactId>pineapple-api</artifactId> </dependency>
Create a text file in the Maven project which contains the messages for the plugin.
Place the message file in the directory src/main/resources in the plugin project.
Name the message file ${pluginId}-messages.properties where ${pluginId} is the plugin id from plugin class, i.e. the package where the plugin class is located in.
Example: The message file for infrastructure test plugin is com.alpha.pineapple.plugin.net-messages.properties.
If the plugin contain a Spring configuration file for definition of the unmarshaller bean used for for input unmarshalling then add an additional bean definition to the configuration file which defines the message provider:
Example:
<!-- define message provider for internationalization --> <bean id="messageProvider" class="com.alpha.pineapple.i18n.PropertyFileMessageProviderImpl"> <property name="basename" value="com.alpha.pineapple.plugin.net-messages"/> </bean>
The id of the bean should be messageProvider.
If the plugin doesn't support input marshalling then the plugin class doesn't define the attribute the configFile and unmarshaller on @Plugin annotation.
In this situation add the configFile attribute to the @Plugin annotation and use the application context file to define a message provider bean for the plugin which can be injected into all objects which are part of the plugin.
Pineapple only support a single implementation of the MessageProvider interface which is named com.alpha.pineapple.i18n.PropertyFileMessageProviderImpl.
The PropertyFileMessageProviderImpl class supports usage of dots in the name of message file. This enables usage of the naming scheme with the plugin id for message file: ${pluginId}-messages.properties.
To get access to the message provider bean from a class in the plugin, declare a field in a class and let Spring inject the message provider when the class is initialized.
Example:
/** * Message provider I18N support. */ @Resource MessageProvider messageProvider;
To use the message provider, first define a key-value pair in the message file:
Example: com.alpha.pineapple.plugin.net-messages.properties contains:
good_message=This a good message from [{0}] and [{1}].
And resolve the message in the class:
// log debug message if ( logger.isDebugEnabled() ) { Object[] args = { "Titus", "Ed Chianese" }; String message = messageProvider.getMessage("good_message", args ); logger.debug(message); }
During initialization of a plugin, the operation classes are located using the component scanning from in the Spring framework. Pineapple will register each operation with its no-arg constructor.
Pineapple only uses the no-arg construction on operation classes to create instances. Implement operation classes which support dependency injection by annotating the fields on operation classes with the @Resource annotation.
Dependent objects will then be injected at runtime doing either:
Pineapple is using the no-arg constructor to create operation objects, so it is perfered to implement dependency injection using annotated fields.
Implement integration tests by using:
Example:
@RunWith( SpringJUnit4ClassRunner.class ) @ContextConfiguration( locations = { "/com.alpha.pineapple.plugin.weblogic.jmx-test-config.xml" } ) public class TestDeployedConfigurationTest { /** * Object under test */ @Resource TestDeployedConfiguration operation; @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } /** * Test that TestDeployedConfiguration can be looked up from the context. */ @Test public void testCanGetTestDeployedConfigurationFromContext() { assertNotNull( operation ); } }
For info about setting up integration test projects refer to, How-to: Write integration tests in Pineapple.
When using JAXB2 for unmarshalling, JAXB2 doens't unmarshall attribute values correctly. The objects are initialized with null or default values.
This issue can be avoided by setting the schema attribute attributeFormDefault="unqualified" as shown:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://pineapple.dev.java.net/ns/plugin/infrastructure_1_0" targetNamespace="http://pineapple.dev.java.net/ns/plugin/infrastructure_1_0" elementFormDefault="qualified" attributeFormDefault="unqualified">