Technical Concept for Pustefix 1.0
Creating custom ApplicationContext instances for each bundle
In order to create a custom ApplicationContext instance we have to supply our own org.springframework.osgi.extender.OsgiApplicationContextCreator. In this class' createApplicationContext method we check whether the bundle is a Pustefix bundle and then create a PustefixOsgiWebApplicationContext (that extends Spring's OsgiBundleXmlWebApplicationContext) for Pustefix application bundles and a PustefixOsgiApplicationContext (that extends Spring's OsgiBundleXmlApplicationContext) for Pustefix module bundles or delegate to Spring's default implementation (org.springframework.osgi.extender.support.DefaultOsgiApplicationContextCreator) for non-Pustefix bundles.
A WebApplicationContext is only used for application bundles, because for other bundles (modules) there is no distinct ServletContext in an environment with more than one application.
This custom creator implementation is then referenced in a file META-INF/spring/extender/pustefix-extender.xml using the special bean name applicationContextCreator.
Resource abstraction layer
The basic concept of the resource abstraction layer is documented in PustefixNewResourceConcept.
Inclusion of bundle resources
We're introducing a new URI scheme for including resources from other bundles. This scheme will replace the old module scheme.
Example:
bundle://[Bundle-SymbolicName]/path/to/resource
The authority part will be the unique bundle name. Initially we will not support specifying a version (this will be part of future Pustefix releases and will be implemented using the Resource selector mechanism). So the default behaviour when having the same bundle in multiple versions will be loading the resource from the bundle with the highest version.
Logging
We should use slf4j for logging. With the right adapters we can keep log4j-style logging while supporting Apache Commons Logging and JDK Logging, too. This task will require some changes in the logging configuration code (configuring the log4j loggers) and probably removing the ProxyLogFactory.
Extension point implementation
Extension points and extensions are read as part of the general configuration parsing/ApplicationContext creation mechanism. For each extension point an according bean will be created and exported as OSGI service. Extension points implement a type specific interface derived from a generic extension point interface.
Extension point interface:
public interface ExtensionPoint {
public String getId();
public Version getVersion();
public String getType();
}
Example:
<extension-point id="myflowstep.ext" type="context.flowstep" version="0.0.0" cardinality="0..n"/>
public interface FlowStepExtensionPoint extends ExtensionPoint {
public void add(FlowStepExtension extension);
public void remove(FlowStepExtension extension);
}
Extension points must specify a unique id and a type supported by Pustefix. Version and cardinality are optional (default values: "0.0.0" and "0..n").
Extensions implement a type specific interface derived from a generic extension interface. For each extension a ServiceTracker is created, which is responsible for registering and unregistering the extension at the according ExtensionPoint.
Extension interface:
public interface Extension {
public String getType();
public List<String> getExtensionPoints();
}
Example:
<extension type="context.flowstep"> <extend extension-point="myflowstep.ext" version="0"/> <flowstep name="baz"/> </extension>
public interface FlowStepExtension extends Extension {
public List<FlowStep> getFlowSteps();
}
Extensions must specify a type supported by Pustefix and must reference one or more extension-points (of the same type) by their ids. The version attribute is optional and defaults to 0 - interpreted as [0.0.0,∞).
Spring beans returned by an Extension should be lazily instantiated (so that unused extensions are lightweights).
Extension points are registered under their specialiced interface. Id, type and version are registered as service properties, which simplifies and accelerates the service lookup.
Pseudo-declaration (Spring DM) of extension service:
<osgi:service id="extension.point.standardpage.main" interface="StandardPageExtensionPoint">
<osgi:service-properties>
<entry key="extension-point" value="standardpage.main"/>
<entry key="type" value="xml.standardpage"/>
<entry key="version" value="1.0"/>
</osgi:service-properties>
<bean class="StandardPageExtensionPointImpl">
...
</bean>
</osgi:service>
Pseudo-declaration of extension-point service retrieval:
<osgi:reference id="..." interface="StandardPageExtensionPoint" filter="(& (extension-point=standardpage.main) (type=xml.standardpage))"/>
<osgi:list id="..." interface="StandardPageExtensionPoint" filter="(type=xml.standardpage)"/>
Because the LDAP filter syntax of the current OSGI spec not yet supports version ranges, selecting the right version has to be done programmtically after retrieving the pre-filtered service references.
TargetGenerator
Pustefix module bundles can provide XML/XSL resources and TargetGenerator configuration fragments. This will be done by contributing to extension points defined in the application bundle or other module bundles.
The extension points are published as OSGI services. The extension points can be arbitrarily defined in the XML structure (at least initial support of navigation, include and standardpage extension points is mandatory).
The definition of extensions is done within the bundle's depend.xml file (which will only consist of extensions as they replace the config include mechanism). They're registered at the according extension points by implementing ServiceTrackers?.
The TargetGenerator can retrieve the extensions registered for an extension point by getting a reference to the extension point service from the OSGI service registry and querying the registered extensions. The service interface has to provide methods to retrieve references to the according configuration objects. Configuration objects, which relate to bundle resources, also have to provide an according Resource object to make it accessible to the TargetGenerator.
This involves that the TargetGenerator no longer works directly on the XML configuration (and translates it to a generic target configuration via XSLT). We will introduce configuration objects (e.g. StandardPage) from which the target structure will be constructed. These configuration objects also have to remember the targets created for them to be able to invalidate these targets if a bundle and the according configuration objects disappear.
Sample extension-points:
<make>
<navigation>
<page name="foo" handler="/xml/config"/>
<extension-point id="navigation.main" type="xml.page"/>
<page name="bar" handler="/xml/config"/>
</navigation>
<standardpage name="foo" xml="xml/frame.xml"/>
<standardpage name="bar" xml="xml/frame.xml"/>
<extension-point id="standardpage.main" type="xml.standardpage"/>
</make>
Sample extension:
<extension type="xml.standardpage"> <extends extension-point="standardpage.main"/> <standardpage name="baz" xml="xml/frame.xml"/> <standardpage name="hey" xml="bundle://mypackage.myapp/xml/frame.xml"/> </extension>
Supported extension-point types (all with cardinality="0..n" by default):
<extension-point type="xml.param"/> <extension-point type="xml.page"/> <extension-point type="xml.standardmaster"/> <extension-point type="xml.standardmetatag"/> <extension-point type="xml.include"/> <extension-point type="xml.standardpage"/> <extension-point type="xml.target"/> <extension-point type="xml.depxml"/> <extension-point type="xml.depxsl"/> <extension-point type="xml.depaux"/>
View extension points
The extension point mechanism should also be supported within the view. Therefor we introduce the <pfx:extension-point/> tag, which can be used to define an extension point within an XML file. Extensions are defined by adding <extends> elements to a part.
Example:
<part name="content">
<theme name="default">
<h1>foo</h1>
<pfx:extension-point id="content.main.ext" version="..." cardinality="..."/>
</theme>
</part>
<part name="xyz">
<extends extension-point="content.main.ext"/>
<theme name="default">
bar
</theme>
</part>
View extension points and extension are also implemented using the Service-Registry/Service?-Tracking mechanism described above. But unlike in prior Pustefix versions, we have to initially parse all XML files to find and register all extension points and extensions.
Extension point definitions will be transformed to XSLT callbacks, which will return the contributed XML fragments (similar to the current include mechanism).
Context-Service
Pustefix module bundles can contribute pagerequests, pageflows and interceptors to an application. This will be done using the extension-point mechanism (will fully replace the config-includes). ContextResources have become obsolete and will be discarded. We also have to support contributing only variants of pagerequests/pageflows, additional wrappers, actions or output resources.
Extension points can be defined inserting an extension-point element at the according location within the context configuration file. Contributions are made by using an extension element referring to the according id and type of the extension point.
Example:
<pageflow name="MyFlow"> <flowstep name="foo"> <extension-point id="pageflow.myflow" type="context.flowsteps"/> <flowstep name="bar"> </pageflow>
<extension id="payflow.myflow.mymodule" type="context.flowsteps"> <extend extension-point="pageflow.myflow"/> <flowstep name="baz"/> </extension>
Supported extension-point types (all with cardinality="0..n" by default):
<extension-point type="context.interceptor"/> <extension-point type="context.scriptedflow"/> <extension-point type="context.pageflow"/> <extension-point type="context.pageflowvariant"/> <extension-point type="context.flowstep"/> <extension-point type="context.pagerequest"/> <extension-point type="context.pagerequestvariant"/> <extension-point type="context.wrapper"/> <extension-point type="context.action"/> <extension-point type="context.resource"/> <extension-point type="context.property"/>
These extension points are implemented using the generic mechanism described above (Service and ServiceTracker registration during ApplicationContext setup/configuration parsing).
The core classes working with the extensible configuration/model have to be adapted to additionally use the data provided by the extension points. Currently Pustefix is mainly working with unmodifiable configuration model objects, e.g. a StateConfig instance returning IWrapperConfig objects, whereas in a dynamic application IWrapperConfig objects can be contributed from another module to an extension point, thus they can appear or disappear at any time.
So the configuration model objects have to be changed when an extension appears or disappears. This could be done by making the extension points responsible for updating the model if an extension is registered or deregistered. Therefor they would need a reference to the according model objects, which could be injected on configuration parsing.
But the nature of extension points, i.e. having the ability to define multiple extension points within an ordered list (e.g. flowsteps) and extending extension-points themselves, makes it necessary that the configuration data structure itself is aware of the possible extension points, e.g. flowsteps contributed by an extension must be inserted at the right position. So it's easier when the configuration object itself queries its extension points for possible contributions.
Therefor we will have to adapt the configuration parsing and the configuration model classes supporting extension-points. We will have to ensure that cached data derived from the current configuration state gets refreshed.
Directoutput-Service
Pustefix module bundles can only contribute directoutputpagerequests. So we need just one extension point type:
<extension-point id="..." type="direct.pagerequest"/>
The extension point is implemented according to the extension points from the Context-Service.
Webservices
Arbitrary Spring beans from application and module bundles should be exportable as Pustefix webservices. Therefor the ServiceRuntime/ServiceProcessors (JSONWSProcessor, JAXWSProcessor) need access to the Spring beans from other bundles.
Instead of one global ServiceRegistry and ServiceConfiguration for each bundle own instances will be created and published as OSGI service. The bundle-specific ServiceRegistries will be created indirectly by the extender (by creating the ApplicationContext).
A ServiceRegistry has to provide methods to retrieve Spring beans exported as webservice and the bundle's ClassLoader for object serialization/deserialization and setting the ContextClassLoader.
In the previous Pustefix version webservices could be configured in a separate file (webservice.conf.xml) or in the Spring configuration (using an own namespace). To make the configuration more concise, we'll discard the webservice.conf.xml. Therefor global settings, which were only configurable in this file, have to be configurable in the Spring configuration.
Support for multiple-webapps-per-OSGI-runtime scenarios requires that you can control if services from a bundle should be available in a webapp. Therefor the generic extension point mechanism will be utilized. The ServiceRegistry will be handled as extension, which can be plugged into extension points defined by other bundles.
Omitting an own configuration file for webservices the only place for the definition is within the Spring configuration. So we'll introduce an own ws:extension-point element and an according ws:service/ws:extends sub-element.
Session-scope support for non-application-bundles
Spring DM doesn't support session-scoped beans in bundles without a web-aware ApplicationContext. As its general usage to have session-scoped beans in module bundles (e.g. the former ContextResources) we'll have to implement our own solution.
The implementation will be based on the pluggable custom scope mechanism available in Spring. The session scope will be realized by binding the current HTTP session to the current Thread using a ThreadLocal variable (when entering the request handling) and storing objects within this session object.
HttpService registration
The current OSGI spec doesn't specify how to run multiple webapps in a single OSGI Runtime. It only provides a standard service to register servlets and resources in the scope of a HttpContext, which only ensures that servlets registered under the same HttpContext will share the same ServletContext.
Because the spec doesn't specify how multiple ServletContexts can be created, Pustefix will provide a bridge to custom implementations. Therefor it defines the HttpContextProvider interface:
public interface HttpContextProvider {
HttpContext getHttpContext(Bundle bundle);
}
Implementors can provide the HttpContext, which should be used for the HttpService registration of a bundle. Thus they can provide an own HttpContext instance, which is bound to an own ServletContext.
The HttpContextProvider interface has to be registered as OSGI service. Pustefix will lookup this service and use the provided HttpContext for HttpService registrations. If no HttpContextProvider service is found or it's deregistered, Pustefix will use the default HttpContext provided by the OSGI runtime.
Build process
Pustefix should support the easy bootstrapping of new application and module bundles. We also need a simple solution for provisioning/launching an application.
We will integrate the bnd-based maven-bundle-plugin into our build-process to simplify the creation of OSGI bundles. Therefor we need to adapt our maven archetypes and pre-configure the plugin.
We will have to adapt our maven plugins to reflect the changed directory structure. Application and modules bundles have to be changed to OSGi-bundle packaging.
We will also implement a simple OSGI-Application provisioning/launching plugin. It will use the application POM dependencies in conjunction with additional dependencies from a provisioning POM (containing additional runtime dependencies) to resolve all necessary bundles and launch them inside an OSGI runtime. The launcher should use the Live-Jar mechanism to look up if bundles can be mapped to a file system folder and deploy the folder instead of the jar (if supported by the chosen OSGI runtime implementation).
Based on the POM-based provisioning we will create a plugin that assembles all bundles of an application into a deployable war archive.
At last we have to provide a maven plugin that generates an Eclipse Target-Platform and a Runtime-Configuration based on the POM dependencies, to simplify development with Eclipse.
Migration of applications
The editor application and the various sample applications have to be migrated to Pustefix 1.0. This mainly means making the applications to application bundles: reorganizung the directory structure, providing OSGi meta information, adapting the POM and the configuration (e.g. migrating ContextResources to spring.xml).
Documentation
The reference documentation and the tutorials have to be updated. At least we need to explain the basic concepts: application and module bundles, the extension point mechanism, building and launching applications.
