Implementation of supported use cases in the core component:
How to implement a plugin provider:
pineapple-core uses log4j for logging. Each pineapple client must provide a Log4j configuration which is accessible at runtime as a result of the build process.
The core component does not provide a Log4j configuration as part of its production build. A log4j configuration file is defined in the project in the directory src/test/resources/log4.properties for testing.
The test configuration configures Log4j to write to the log file to ${user.home}/.pineapple/logs/pineapple.log.
The core component contains a configuration file which defines a Spring application context. The Spring application context defines dependency injection of objects as part of initialization of the core component. The file is located in src/main/resources/com.alpha.pineapple.core-config.xml.
The spring application context defines a test profile named integration-test whose beans are only added to the context when a test class activates the profile through the usage of the profile Id integration-test, e.g. @ActiveProfiles("integration-test"):
<beans profile="integration-test" > <bean id="ObjectMotherCredentialProvider" class="com.alpha.testutils.ObjectMotherCredentialProvider" /> </beans>
The package com.alpha.pineapple contains public interface and classes of the core component:
The factory class can be created in two ways:
If the factory is created using the new operator its dependencies isn't injected:
The factory will at runtime validate whether the dependencies are resolved, if not the factory will load the core Spring application context and resolve them by if self.
The factory is idempotent in regard to creation to of core and provider instances since they are defined with default scope in the Spring context.
The package com.alpha.pineapple.admin contains public interface and classes for runtime administration of the core component:
The package com.alpha.pineapple.credential contains classes used to provide credentials to Pineapple resources. The credentials are used to access and authenticate the application with external resources:
The package com.alpha.pineapple.credential.encryption contains classes used to encrypt passwords stored in the crendetials.xml file and keep the passwords encrypted when read into memory:
Credential passwords should be encrypted. The goal of the design is to achieve:
The design realizes these use cases to support the encryption requirements:
When Initialize credential provider is performed: The credentials are loaded from the credentials.xml:
When Create credential is performed: The credential is created with the password is encrypted with the prefix encrypted: and stored in-memory encrypted. The configuration is saved to synchronize the in-memory representation and the file content. Encryption is handled from FileBasedCredentialProviderImpl which delegates the creation of CredentialInfo to a CredentialInfoFactory where the password is encrypted prior to storing it in the credential info.
When Update credential is performed: The credential is deleted and then a new credential is created. The password is encrypted with the prefix encrypted: and stored in-memory encrypted. The configuration is saved to synchronize the in-memory representation and the file content. Encryption is handled from FileBasedCredentialProviderImpl which delegates the creation of CredentialInfo to a CredentialInfoFactory where the password is encrypted prior to storing it in the credential info.
When Delete credential is performed: The credential is deleted. The configuration is saved to synchronize the in-memory representation and the file content.
When Get credential is performed: Decryption is done by delegating the Credential creation to the CredentialConfigurationMarshaller in this case the PasswordEncryptingCredentialConfigurationMarshallerImpl. The method is only invoked when the core component creates a session to a resource.
The FileBasedCredentialProviderImpl delegates the Load credentials and Save credentials to its configured mashaller which is PasswordEncryptingCredentialConfigurationMarshallerImpl. The marshaller handles the encryption cases described above and then delegates the loading, saving and mapping to the CredentialConfigurationMarshallerImpl. The result is that all encryption functionality can be isolated to two classes; PasswordEncryptingCredentialInfoFactoryImpl and PasswordEncryptingCredentialConfigurationMarshallerImpl.
Factory for creation of credential info objects with encrypted passwords. The factory supports creation of objects which implements the CredentialInfo interface. The factory creates objects of the type CredentialInfoImpl. When a credential info object is created the password is encrypted and prefixed with encrypted: prefix.
The factory is used by the PasswordEncryptingCredentialConfigurationMarshallerImpl class and the FileBasedCredentialProviderImpl class to support encryption of passwords in memory and on disk.
The marshaller is the central class in the design to support encryption of credential passwords.
The marshaller delegates the main bulk of load, saving and mapping logic to the CredentialConfigurationMarshallerImpl class and only intercepts calls when encryption is required.
The symmetric encryption functionality used by PasswordEncryptingCredentialInfoFactoryImpl and PasswordEncryptingCredentialConfigurationMarshallerImpl to encrypt credential passwords is implemented through the usage of the Jasypt string encryptor StandardPBEStringEncryptor.
The encryptor is configured to receive its configuration from the configuration object FileBasedPasswordPBEConfigImpl. The configuration object implements the Jasypt PBEConfig interface which defines a configuration object for a StandardPBEStringEncryptor encryptor.
The configuration object implements this algorithm to resolve the password:
Encryption of credential passwords are implemented through usage of the Jasypt library.
The symmetric password encryptor StandardPBEStringEncryptor is used. The encryptor is configured in the spring configuration file with a fixed salt. The salt is required to be fixed to support usage of the encryptor across JVM restarts.
The password for the encryptor is resolved by the CredentialProviderPasswordEnvironmentPBEConfigImpl class which loads the password from an external password file (read the class description below for more details).
The package com.alpha.pineapple.module contains classes for representation and storage of modules:
The repository administers a set of available modules resolved from the modules directory. The modules directory is resolved from the runtime directory provider.
The class is defined as a bean in the application context for the core component. The instance created by the the context is not initialized. The repository is initialized by invoking the initialize() method. This method can be invoked from clients to refresh to content of the repository.
The package com.alpha.pineapple.resource contains classes for storage of information about resources defined in the environment configuration:
The repository administers the set of configured resources in the environment configuration.
The repository support resolution of resources using a wild card environment. The name of the wild card environment is *. The used algorithm of resolution of a resource is, e.g. get(E,R):
The marshaller supports mapping from the unmarshalled configuration classes to the internal representation using the info objects. It also support mapping from the info classes to the configuration class used for marshalling.
Finally it supports the save operation which enables the repository to save the resource configuration on updates to the repository.
The package com.alpha.pineapple.plugin.repository contains classes for storage of information about registered plugins:
The overall interface for the plugin repository is split into interfaces, PluginRepository and PluginRuntimeRepository, to be able to include a public interface (e.g. PluginRepository) in the Administration API (exposed through the API project) and define a internal interface for the core core component (e.g. PluginRuntimeRepository) which contains implementation specific details for resolution and execution of plugins at runtime.
Inclusion of the implementation specific details (e.g. stuff like OXM marshallers) in the public interface would resulted in the inclusion of Spring Framework interfaces (e.g. again the OXM marshallers) in the API project. This would have bloated the dependency graph for the API project and this was deemed unwise and thus the interface was split into two.
The class is defined as a bean in the application context for the core component. The instance created by the the context is not initialized. The repository is initialized by invoking the method: PluginRepository.initialize( String[] pluginIds );. The method is invoked in the InitializePluginActivatorCommand.
The repository will only scan for plugins candidates in the packages which is defined in the collection of plugin id's supplied as arguments to the initialize(..). The actual scanning is delegated to the PluginCandidateScannerImpl.
After a list of plugin candidates have been identified and stored in a application context. Then each candidate is processed in turn by the PluginInitializerImpl which attempts to initialize the candidate as a plugin.
The repository support resolution of operations using a wild card operation. The name of the wild card operation is *. The used algorithm of resolution of an operation is, e.g. get(P,O):
Implements the BeanNameGenerator interface. The class is used by the PluginCandidateScannerImpl during scanning of the class path for plugin candidates in the scanForPlugins(..) method to generate bean names for plugin candidates.
The class generates names of the form: plugin: package-name.
The class is used by the PluginRepositoryImpl to scan the class path for plugin candidates.
The scanner will only scan for plugins candidates in the packages which is defined in the collection of plugin id's supplied as arguments to the scanForPlugins(..). The scanning criteria's is defined as:
The scanner returns the collection of identified plugin candidates stored as beans in a application context. The bean id's are generated by the PluginNameGeneratorImpl class.
The class is used by the PluginRepositoryImpl to initialize plugin candidates into ready-to-use plugins.
The initializer initializes plugins from the metadata defined on the @Plugin..
The initializer initializes the plugin application context with plugin providers which provides access to the Pineapple runtime system. These providers are initialized:
The package com.alpha.pineapple.execution contains classes for storage of information about execution of operation on modules:
Repository for storage of execution contexts. The execution context for an operation is stored and removed from the repository during execution by OperationTaskImpl.
The algorithm for looking up a execution context in get(ExecutionResult) is:
The repository is accessed during execution by the plugin providers:
When an operation is started by the core component then the repository is used to create a ExecutionInfo meta data object which described the executed operation. In addition to the meta data, the execution info object also contains a reference to the root ExecutionResult object which contains the state of the execution.
The result repository implements the role of subscriber in the publish-subscriber pattern. Subscribers are all the registered result listeners, which implements the ResultListener interface.
When a execution result changes it state all registered listeners are notified of the event. The notification is done by invoking the ResultListener.notify(ExecutionResultNotification notification) on each listener with the state changing result as argument.
Besides notifying all registered listeners, repository also captures the events for the executing operations. The event capture is used by the REST controller in the web application to be able to deliver updated results when polled by a client (e.g. a controlling Pineapple instance).
The repository is implemented with a fixed capacity to limit the amount for stored executions in the repository. The repository has capacity for a fixed number of completed executions and any number of running executions. The fixed number of completed executions is defined by the the hard coded constant CoreConstants.EXECUTION_HISTORY_CAPACITY which initially is set to 5.
The implementation of the asynchronous execution is implemented by the usage of the Spring annotation @Async:
@Async @Override public void execute(ExecutionInfo info) { operationTask.execute(info); } @Override public ExecutionInfo execute(String operation, String environment, String module) { String message = messageProvider.getMessage("aot.unspported_method"); throw new UnsupportedOperationException(message); } @Override public ExecutionInfo executeComposite(String operation, String environment, String module, String description, ExecutionResult result) { String message = messageProvider.getMessage("aot.unspported_method"); throw new UnsupportedOperationException(message); }
The configuration of the underlying task executor is defined in the Spring application context com.alpha.pineapple.core-config.xml by the operationExecutor bean.
The class forwards requests to the OperationTaskImpl which implements synchronous execution of a operation.
Only the method execute(ExecutionInfo info) support asynchronous execution of an operation. The other two methods defined in the interface throws an UnsupportedOperationException.
The object implements the interface ExecutionInfoProvider is used by plugins to look up runtime info.
The algorithm for looking up up runtime info in the ExecutionInfoProvider.get(ExecutionResult result) method is:
The class is made available to plugins through usage of the @Resource dependency injection with either the bean or field id "coreExecutionInfoProvider". The class is made available to the core component through definition in the core component application context.
The package com.alpha.pineapple.execution.scheduled contains the classes which defines the scheduled execution of operations:
The package com.alpha.pineapple.execution.trigger contains the classes which defines the synchronous execution of triggers:
Resolves whether a trigger should be executed. The resolution is based on the algorithm:
Resolves whether a trigger should be executed. The resolution is based on the algorithm:
The package com.alpha.pineapple.io.file contains helper classes for resolution of directories:
Default implementation of the RuntimeDirectoryProvider interface which is used to resolve the default resolve runtime directories where Pineapple will run from.
The class is made available to plugins through usage of the @Resource dependency injection with either the bean or field id coreRuntimeDirectoryProvider.
The home directory is resolved using the algorithm:
The operating system is determined on the value of the os.name system property.
${user.home} is the value of the user.home system property.
The temp directory is resolved from the system property java.io.tempdir.
The location of the password file is resolved using the algorithm:
To support location independent model element path (MEP) within module models the provider can resolve these path prefixes:
The package com.alpha.pineapple.plugin.activation contains classes for invocation of plugins at runtime:
The class is defined as a bean in the application context for the core component. The instance created by the the context is not initialized. The activator is initialized by invoking the method: PluginActivator.initialize( CredentialProvider provider, ResourceRepository resourceRepository, PluginRepository pluginRepository );.
The method is invoked in the InitializePluginActivatorCommand.
The package com.alpha.pineapple.plugin.session contains classes for handling session management during invocation of plugin operations:
The purpose of the session handler is to connect the plugin session prior to invoking the operation and to disconnect afterwards. The session handler achieves its goal by proxying all plugin operations which supports input marshalling. Proxying a plugin operation with a session handler is done by the PluginActivator.
To support retry logic on a session operations, all sessions are proxied using the SessionRetryProxyFactoryImpl.
When an exception occurs in the session handler then the exception is caught and rethrown immediately in a RuntimeException. The purpose of the runtime exception is to channel the exception to the invoking InvokePluginsCommand. The InvokePluginsCommand will catch the exception and process the embedded exception appropriately by setting an error message and completing the operation result with the Error state.
The RetrySessionHandlerImpl supports tunneling of these exceptions:
Implementation note: The InvokePluginsCommand exception clause must mirror these exception to be able to handle them.
Plugin operation classes are proxied at runtime by the RetrySessionHandlerImpl class. The session handler class implements session handling for an operation and retry logic.
To enable usage of the RetrySessionHandlerImpl as a Spring managed bean for a which a new instance (e.g. a Spring prototype bean) is created every time an operation is proxied, several beans are defined. The beans defined to support the usage of RetrySessionHandlerImpl are:
<bean id="unintializedRetrySessionHandler" class="com.alpha.pineapple.plugin.session.SessionRetryHandlerImpl" scope="prototype"/> <bean id="springRetrySessionHandlerFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref local="unintializedRetrySessionHandler"/> </property> </bean>
The springRetrySessionHandlerFactory bean is a factory for creation of Spring managed objects. The factory supports creation of instances of the referenced bean unintializedSessionHandler.
The unintializedRetrySessionHandler bean is defined to define a target object for the factory. The bean is defined as a prototype. It should NOT be used directly as a dependency.
The package com.alpha.pineapple.plugin.session.retry contains classes session retry support implemented using the Spring Retry project:
The listener class logs method retries and add the information to the registered execution result. The listener is able to differentiate between cause of the retry, e.g. whether it is caused by a connect, usage or disconnect error.
The class is defined as a bean named sessionRetryListener in the application context for the core component. The bean is registered with the RetryTemplate which implements the retry logic. The bean is registered as part of constructing a session proxy done by the SessionRetryProxyFactoryImpl class.
Factory for creation of session retry proxy instances. The factory supports creation of JDK dynamic proxies using the Spring ProxyFactory. The proxy factory doesn't support creation of proxies for session classes with NO no-arg constructor (e.g. the defined constructors which requires arguments).
The factory creates the session proxy programmatically by:
The core component application context defines retry support with these beans:
<bean id="sessionRetryProxyFactory" class="com.alpha.pineapple.plugin.session.retry.SessionRetryProxyFactoryImpl" /> <bean id="sessionRetryListener" class=" com.alpha.pineapple.plugin.session.retry.SessionRetryListenerImpl" scope="prototype" /> <bean id="simpleRetryPolicy" class="org.springframework.retry.policy.SimpleRetryPolicy"> <property name="maxAttempts" value="4"/> </bean> <bean id="exponentialBackOffPolicy" class="org.springframework.retry.backoff.ExponentialBackOffPolicy" > <property name="initialInterval" value="100"/> <property name="maxInterval" value="5000"/> <property name="multiplier" value="2"/> </bean> <bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate" scope="prototype"> <property name="retryPolicy" ref="simpleRetryPolicy" /> <property name="backOffPolicy" ref="exponentialBackOffPolicy" /> </bean> <bean id="retryOperationsInterceptor" class="org.springframework.retry.interceptor.RetryOperationsInterceptor" scope="prototype"> </bean> <bean id="springRetryTemplateFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref local="retryTemplate"/> </property> </bean> <bean id="sessionRetryListenerFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref local="sessionRetryListener"/> </property> </bean> <bean id="springRetryOperationsInterceptorFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref local="retryOperationsInterceptor"/> </property> </bean>
The defined beans are:
The package com.alpha.pineapple.command contains classes which implements the org.apache.commons.chain.Command interface. The commands are implementations of the GOF design pattern and the serve many purposes:
The facade object provides a facade for the command objects. Thus the facade has references to the commands objects. But the individual command objects also uses the facade to invoke other commands. Thus the command objects has references to the facade.
This introduces circular dependencies between the facade and the command objects.
To enable Spring to handle the circular dependencies, the command objects are on the facade set using using setter injection. The same applies to the usage of setter injection on the commands to set the facade.
The command supports capture of tunneled exception thrown in a session handler.
The InvokePluginsCommand will catch the exception and process the embedded exception appropriately by setting an error message and completing the operation result with the Error state.
The InvokePluginsCommand supports capture of these tunneled exceptions:
The package com.alpha.pineapple.substitution contains classes for variable substitution in files and models:
The package com.alpha.pineapple.substitution.proxy contains classes for proxying a unmarshalled model object graph to support variable substitution of models:
The package com.alpha.pineapple.substitution.variables contains classes for collecting variables from different sources which constitutes the input to the variable substitution process:
Variable builders are registered by name in the order of their precedence, i.e. the variables defined by the first registered builder takes precedence of the variables defined in the secondly registered builder. When building the variables instance, then variables from the first registered builder are added first, then variables from the second registered builder are added afterwards.
If a builder defines a variable which is already defined through the processing of an earlier builder (maybe with the same value or not) then it ignored, e.g. a variable takes presence from the order of which the builders are registered.
Builders are added by name. When the variables instance is built then all variables in a builder will also be registered with the builder name as a prefix. Example: If a builder is registered with the name "model" and it defines two variables v1=a and v2=b. Then the building will result in the registration of four variables: model.v1=a, v1=a, model.v2=b and v2=b. Prefixed variables are processed for all builder builders prior to processing non-prefixed variables. This is ensure prefixed variables takes precedence over non preficed variables.
Implements the ModelVariableSubstitutor interface and provides the variable substitution capabilities in models.
The main idea behind the variable substitution is to proxy the object graph which represents the marshalled XML model and to intercept all calls to the object graph:
The responsibility of the ModelVariableSubstitutorImpl is to:
This class is a Spring AOP advice. It it used by the proxy factory VariableSubstitutedProxyFactoryImpl to intercept all method calls to objects which are proxied by JDK dynamic proxies.
When it intercepts a method it implements the algorithm:
This class is a Spring CGLIB AOP advice. It it used by the proxy factory VariableSubstitutedProxyFactoryImpl to intercept all method calls to objects which are proxied by JDK dynamic proxies.
It implements the same algorithm as VariableSubstitutionInterceptor for variable processing.
Factory for creation of proxy instances depending on the implementation of the target class which should be proxied.
The factory implements the algorithm:
Commonly used constants in the unit tests are defined in the class com.alpha.testutils.CoreTestConstants.
Helper classes:
The package com.alpha.spring.config contains Spring helper classes.
The class IntegrationTestSpringConfig defines a Java based spring configuration which is used by integration tests.
The configuration is used by including it in the test class:
@RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles("integration-test") @ContextHierarchy({ @ContextConfiguration(locations = { "/com.alpha.pineapple.core-config.xml" }), @ContextConfiguration(classes = IntegrationTestSpringConfig.class) }) public class PluginActivatorImplIntegrationTest { /** * Object mother for credential provider */ @Resource ObjectMotherCredentialProvider providerMother; .... }
The file src/test/resources/resources.xml contains and environment configuration file which defines a resource using the TestResource:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <environments> <environment> <identifier>localdomain</identifier> <resources> <test-resource> <identifier>test-resource-resourceidentifier</identifier> <credential-identifier>test-resource-credentialidentifier</credential-identifier> <plugin>com.alpha.pineapple.resource.test.TestFactoryImpl</plugin> </test-resource> </resources> </environment> </environments> </configuration>