Pentaho ObjectFactory and Spring Enhancements
Pentaho ObjectFactory and Spring Enhancements in 5.0
Introduction
Version 5.0 of the Pentaho Suite introduces several enhancements to core architecture of Platform. These enhancements are designed to aid the development of new features as well as allowing more customization options for existing features.
Background Architecture
The Pentaho Platform started with a pure service-locator facility to provide plugability of implementations and factoring of objects. Static access to this service-locator was provided through PentahoSystem.get() calls and it was backed by a rudimentary Object Factory and registery.
A typical call to locate or instance a class looked something like this:
ISolutionRepository repo = (ISolutionRepository) PentahoSystem.get("solutionRepository");
The PentahoSystem ObjectFactory was later replaced by a wrapper around a Spring XML ApplicationContext. At this time the system was modified so you pass a class literal instead of a String. The resulting object from the ObjectFactory was cast to the type of the literal. Here's the same call using this new functionality
<bean id="ISolutionRepository" class="org.pentaho.SomeImpl"/> ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class);
This really wasn't much different from the first system. The SimpleName was extracted from the passed-in literal. A bean had to exist with an ID matching this Simple Name. A downside as a result of this was that you could only have one implementation for a given class. To work around this this limitation another enhancement was made allowing you to pass a String second parameter corresponding to the id of the bean in the Spring ApplicationContext.
<bean id="ISolutionRepository" class="org.pentaho.SomeImpl"/> <bean id="CustomSolutionRepository" class="org.pentaho.SomeImpl"/> ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, "CustomSolutionRepository");
This is essentially the state of the system up until 5.0. Beans were simple Classes which were internally expected to call PentahoSystem.get() to retrieve their own dependencies. Any bean referencing another directly in Spring was discouraged. That lack of expressiveness in the Spring XML kept the internals of the system very opaque. Additionally every class was still very dependent of the PentahoSystem Service Locator.
Anyone trying to customize the system prior to 5.0 had to drop jars into the WAR and modify core Spring files. Maintainability was difficult.
New Enhancements
To start with, the ObjectFactory backing PentahoSystem is no longer based on a single Spring ApplicationContext. There can now be any number of registered ObjectFactories registered with the system. Queries for a particular type will find the most appropriate implementation from the registered ObjectFactories.
All Plugins plugin.spring.xml files are registered with the system. It is now possible with the enhancements detailed below to override a core system object from within a plugin or contribute to a list of implementations. Plugin Spring files can also now reference beans from the core system eliminating the need to make calls to PentahoSystem.
In a lot of ways these enhancements are designed to deprecate the PentahoSystem service locator and the IPluginManager. Implementations should be registered from plugins and discovered the same as ones from the core code.
5.0 brings features both to the PentahoSystem service locator as well as Spring. Though as stated, usage of PentahoSystem should be avoided where possible.
PentahoSystem:
Call |
Description |
---|---|
PentahoSystem.getAll() |
Retrieve a list of implementations for a given type (i.e. all IContentGenerators). Ordered by priority |
PentahoSystem.get() |
Now returns the highest priority implementation registered in the system. |
PentahoSystem.get(Type, Map<String, String>) |
Query for an implementation with the given properties. Ordered by priority. |
PentahoSystem.getObjectReference(Type) |
Returns a descriptor for a registered implementation containing all attributes and a method getObject() to instantiate the referenced type. |
Spring:
Spring Element |
Description |
---|---|
<pen:bean class="com.foo.Bar"/> |
Retrieve the highest priority implementation for the given type. Published beans are given highest priority when searching for an implementation. If no beans have been published for the type, each registered ObjectFactory will be checked to see if they have an implementation (simple <bean>). The first one found will be returned. |
<pen:list class="com.foo.Bar"/> |
Retrieve a list of implementations for a given type (i.e. all IContentGenerators). Ordered by priority. unlike <pen:bean> only published implementations will be included in the list. |
<pen:attributes> |
Provide attribute metadata about a published bean, or use in conjuction with <pen:bean> or <pen:list> to query for specific implementations. |
Any Spring file can take advantage of these extensions. <pen:bean> and <pen:list> can be used like any other bean in the Spring file. They can be given IDs and references, embedded in <constructor-arg> and <property> elements. Anything you can do with a normal Spring <bean> can be done with the Pentaho additions.
If you want your Spring ApplicationContext to be able to provide implementations for others to find, you must register the ApplicationContext by placing the following in your XML file.
<bean class="org.pentaho.platform.engine.core.system.objfac.spring.ApplicationContextPentahoSystemRegisterer" scope="singleton"/>
Once a Spring ApplicationContext is registered it can then publish beans into the system using the following:
<bean class="..."> <pen:publish as-Type="com.foo.Bar"/> </bean>
Once published the bean can be found by others calling <pen:bean> or <pen:list>. the as-Type attribute controls how the bean is registered. The options are INTERFACES, CLASSES, ALL or a specific class as seen above. INTERFACES will publish the bean registering it as an implementation of all interfaces for which the bean implements. CLASSES will publish it as an implementation of all classes from which it inherits, including itself. ALL publishes as both Classes and Interfaces. The default is to publish as the class itself.
Usage Examples
Register a single implementation of a given interface org.pentaho.IDatasource.
<?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:pen="http://www.pentaho.com/schema/pentaho-system" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://jax-ws.dev.java.net/spring/core http://jax-ws.dev.java.net/spring/core.xsd http://www.pentaho.com/schema/pentaho-system http://www.pentaho.com/schema/pentaho-system.xsd"> <!-- Register this ApplicationContext so it can publish beans --> <bean class="org.pentaho.platform.engine.core.system.objfac.spring.ApplicationContextPentahoSystemRegisterer" scope="singleton"/> <bean class="org.pentaho.MyDatasource"> <pen:publish as-type="INTERFACES"/> </bean> </beans>
PentahoSystem now has one registered implementation of the org.pentaho.IDatasource interface. It can be retrieved by calling PentahoSystem directly:
IDatasource datasource = PentahoSystem.get(IDatasource.class);
or through Spring:
<bean class="com.foo.MyObject"> <property name="datasource"> <pen:bean class="org.pentaho.IDatasource"/> </property> </bean>
Register multiple implementations, using Priority to determine which will be provided
By default all beans are given a priority of 20. You control which will be returned by PentahoSystem.get() and <pen:bean> by specifying a higher priority in one of the published beans
<bean class="org.pentaho.MyDatasource"> <pen:publish as-type="INTERFACES"/> </bean> <bean class="org.pentaho.MyCustomDatasource"> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="priority" value="30"/> </pen:attributes> </pen:publish> </bean>
Even though two implementations of IDatasource are registered, the second will be returned by PentahoSystem.get() and <pen:bean> as it's now the highest priority. Calls to PentahoSystem.getAll() or <pen:list> will return both, ordered by priority.
Register multiple implementations, using attributes to determine which will be provided
Lets say you know need an implementation of IDatasource which provides connections to a particular system. You could create a sub-type and query for that, or you can use the Attribute metadata feature to return the one you need.
<bean class="org.pentaho.MyDatasource"> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="type" value="jdbc"/> </pen:attributes> </pen:publish> </bean> <bean class="org.pentaho.MyOlapDatasource"> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="type" value="olap"/> </pen:attributes> </pen:publish> </bean>
You can now query for the highest priority implementation of IDatasource matching the type=olap using the following
IDatasource olapDs = PentahoSystem.get(IDatasource.class, Collections.singletonMap("type, "olap"));
or in Spring using:
<pen:bean class="org.pentaho.IDatasource"> <pen:attributes> <pen:attr key="type" value="olap"/> </pen:attributes> </pen:bean>
Attribute-based queries are possible with <pen:list> as well
<pen:list class="org.pentaho.IDatasource"> <pen:attributes> <pen:attr key="type" value="olap"/> </pen:attributes> </pen:bean>
Of course in this example the list will contain only one entry.
Republish a bean based on configuration.
There are times when you want the implementation found to be based on configuration and not priority, but updating every PentahoSystem.get() call or <pen:bean> reference is burdensome. To avoid this you can combine the concepts above to achieve the same effect without having to modify every area. This technique is used in several places in 5.0
Say you have two implementations of com.foo.IWidgetProvider, one with aggressive caching and the other without. You setup a property file using the ISystemConfig detailed in the next section which you use to drive which one is used in the system.
<bean class="com.foo.StandardWidgetProvider"> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="caching" value="false"/> </pen:attributes> </pen:publish> </bean> <bean class="com.foo.CachingWidgetProvider"> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="caching" value="true"/> </pen:attributes> </pen:publish> </bean> <!-- re-publish the correct one at a higher priority --> <pen:bean class="com.foo.IWidgetProvider"> <!-- find the one by confguration --> <pen:attributes> <pen:attr key="caching" value="${widgetSettings.cache}"/> </pen:attributes> <!-- re-publish at higher priority --> <pen:publish as-type="INTERFACES"> <pen:attributes> <pen:attr key="priority" value="50"/> </pen:attributes> </pen:publish> </pen:bean>
Now that we've re-published the correct bean, the following calls will return the correct implementation without any knowledge of the configuration itself.
PentahoSystem.get(com.foo.IWidgetProvider.class) <pen:bean class="com.foo.IWidgetProvider"/>
ISystemConfig
Prior to 5.0 configuration of objects was accomplished by either the SystemSettings object (pentaho.xml), through Spring property replacement, or if within a plugin by way of the settings.xml file. Unfortunately, these systems didn't interoperate well at all. ISystemConfig has been designed to unify configuration with access both programmatically and through Spring.
ISystemConfig holds a collection of IConfiguration(s) objects by name. You can gain a reference to a named configuration with the following:
ISystemConfig sysConfig = PentahoSystem.get(ISystemConfig.class); IConfiguration aConfig = sysConfig.getConfiguration("someConfig");
ISystemConfig also supports programmatically registering new IConfiguration(s) as well as listing all available configurations.
IConfiguration provides two methods to the user. getProperties() returning a standard java Properties object and update(Properties) which will write-back to the underlying storage if supported.
Programmatic Usage:
ISystemConfig sysConfig = PentahoSystem.get(ISystemConfig.class); IConfiguration aConfig = sysConfig.getConfiguration("someConfig"); Properties props = aConfig.getProperties(); String someStr = props.getProperty("someProperty"); // update a config props.setProperty("someProperty", "newValue"); aConfig.update(props);
If you're simply accessing properties, you can call a convenience method directly on ISystemConfig using a CONFIG.PROPEPRTY notation.
ISystemConfig sysConfig = PentahoSystem.get(ISystemConfig.class); String someStr = sysConfig.getProperty("someConfig.someProperty");
Interaction with Spring
If you're familiar with Spring Property Replacement system the following will look familiar. You can inject values from the ISystemConfig into your beans by adding the following to your ApplicationContent XML document
<bean class="org.pentaho.platform.config.PentahoPropertyPlaceholderConfigurer" > <constructor-arg> <pen:bean class="org.pentaho.platform.api.engine.ISystemConfig"/> </constructor-arg> </bean>
Injection is the same as with the Spring Property replacements using the ${CONFIG.PROP} notation
<bean class="MyClass"> <property name="someProperty" value="${someConfig.someProperty}"/> </bean>
You can provide new IConfiguration(s) from within Spring as well. The ISystemConfig is leveraging the <pen:list/> to collect all available IConfiguration implementations in the system. Below is an example from 5.0 registering the security.properties file as a "security" configuration.
<bean class="org.pentaho.platform.config.SolutionPropertiesFileConfiguration"> <constructor-arg value="security"/> <constructor-arg value="security.properties"/> <pen:publish as-type="INTERFACES"/> </bean>
Other built-in configurations are available for "repository" corresponding to the pentaho-solutions/system/repository.spring.xml and "system" corresponding to the pentaho-solutions/system/system.properties
The intent of the system is to supplant the usage of pentaho.xml and Plugin settings.xml files. An IConfiguration implementation wrapping the pentaho.xml is available though not installed by default (SystemSettingsConfiguration). The decision was made to scrap pentaho.xml in a later release instead of hiding it.
Plugins should transition to this new system and provide a configuration for their internal use as well as from the system:
<bean class="org.pentaho.platform.config.SolutionPropertiesFileConfiguration"> <constructor-arg value="myPlugin"/> <constructor-arg value="myPlugin/plugin.properties"/> <pen:publish as-type="INTERFACES"/> </bean>