XOE Services provide the basic building block for all extensions to the core XOE architecture. A service is a registered object that provides a specific functionality through a specific interface. Information about the functionality and interface are provided by the object itself, through the IService interface.
package org.xoe.core.services import org.xoe.core.Constants; import org.w3c.dom.Document; public interface IService extends Constants { /** * Services can be configued with a DOM Document. * configDoc may be null. */ public void init (Document configDoc) throws Exception; /** * Functionalities are Strings that can be used to differentiate * between different kinds of IServices. * This function must return all the Functionalities implemented by * the IService. * This function may return null. */ public String[] getFunctionalities (); /** * Returns the String names of all the Java interfaces that the * IService implements for the given functionality. * This function may return null. */ public String[] getInterfaces (String functionality); /** * This function may return null. */ public String[] getFeatures (String functionality); /** * Returns extra meta-information about the service as an array of * Strings. * This function may return null. */ public String[] getInformation (); }
Besides allowing the XOE core to be extended in pre-defined ways, the XOE Service Architecture provides a framework for you to build extensibility into your own services and applications. To make certain tasks extensible, you can perform a service query to locate external services to perform these tasks. Since those services can be installed from separate packages and distributed separately, the behavior of your software can be customized and modified after release.
XOE services are distributed inside XOE packages. Besides writing the service implementation, you must also include a file inside your package listing all the included services, and specifying their configuration parameters (if any). This file must be named services.xml and be located in the root of your package resources directory. It has the following format:
<services>
<service class='fully.qualified.class.Name'/>
<service class='com.foo.bar.Widget' config='widget.config'/>
</services>
The document must be well-formed XML, with a document element having the tag-name <services>. Inside the services tag there may be an arbitrary number of <service> tags. Each service tag must specify the class name of the service. This class must be defined either in the package containing the services.xml file or a package upon which that package depends. The service tag may also specify an optional "config" attribute. The value of this attribute is a relative path from the root of the package's resource directory (the directory containing services.xml) to an XML document. This document will be parsed and passed as the parameter to the "init" method of the service. If no configuration document is specified, the parameter to "init" will be null.
Upon package installation, the services.xml file is parsed by the ServiceInstaller. An instance of each listed class is instantiated and initialized. Next the instance is registered in the service registry. The service registry calls the IService methods getFunctionalities, getInterfaces, getFeatures, and getInformation. The details returned from these functions are published in the registry, to allow the service to be looked up. If any of these steps fail, the service installation fails and the package installation is aborted.
The following demonstrates the implementation of an HTTPS extension using XOE. When a new java.net.URL is created, an URLStreamHandler must be located for the specified protocol. XOE overrides the default behavior for this lookup and attempts to locate a "protocol-handler" service to build the handler. The protocol handler service must implement the IProtocolHandlerFactory interface described here:
package org.xoe.core.protocol; import java.net.URLStreamHandler; import org.xoe.core.services.IService; public interface IProtocolHandlerFactory extends IService { /** * constant used for service function lookup */ public static final String INTERFACE = IProtocolHandlerFactory.class.getName (); public static final String FUNCTIONALITY = "protocolhandler"; public static final String FEATURE = "protocol"; public URLStreamHandler newInstance(); }
As you can see, the interface specifies one function: newInstance, which returns the URLStreamHandler. In addition, three constants are defined. INTERFACE provides a convenient way to reference the interface class name. FUNCTIONALITY is the string that must be returned from the getFunctionalities function of all protocol handler implementations. FEATURE is the feature-name to be used when specifying the protocol for which a specific implementation provides support.
In order to implement an HTTPS protocol handler, one must first appropriately subclass java.net.URLStreamHandler and java.net.URLConnection. This is standard Java practice and will not be described here. In order to make the protocol handler available to XOE, one must implement the IProtocolHandlerFactory interface. The following is the HttpsProtocolHandlerFactory provided as part of the 'libhttps' XOE package.
package com.transvirtual.protocol.https; import org.xoe.core.protocol.IProtocolHandlerFactory; import java.net.URLStreamHandler; import org.w3c.dom.Document; public class HttpsProtocolHandlerFactory implements IProtocolHandlerFactory { public void init (Document configDoc) { // No configuration necessary } public String[] getFunctionalities () { String[] ret = { FUNCTIONALITY }; return ret; } public String[] getInterfaces (String func) { if ( func.equals ( FUNCTIONALITY ) ) { String[] ret = { INTERFACE }; return ret; } return null; } public String[] getFeatures (String func) { if ( func.equals ( FUNCTIONALITY ) ) { String[] ret = { FEATURE, "https" }; return ret; } return null; } public String[] getInformation () { return null; } public URLStreamHandler newInstance() { return new Handler(); } }
Notice the getFunctionalities, getInterfaces, and getFeatures functions. In order to advertise your service, it's important to implement these properly.
The service implementation is extremely simple. When a client calls newInstance, an instance of com.transvirtual.protocol.https.Handler is instantiated and returned. That's it! Assuming you've implemented your handler properly, this service allows other XOE services and applications to transparently create Urls of the form "https://..." and retrieve the content of the URL.
The virtual-input package presents another example of XOE service design. The virtual input system provides support for on-screen keyboards and handwriting input areas. Each available keyboard/input-mechanism is represented by a registered service implementing the IVirtualInput interface.
The base virtual-input package provides an implementation in VirtualKeyboard that builds a keyboard out of an XML description and a collection of images. This implementation is sufficient for many languages and keyboard types, however it would be wasteful to include all of these keyboards with the base package since most users will only ever use one or two. Likewise, it would be cumbersome to include the implementation with all the keyboards since this would lead to code duplication and maintanence hassles.
The solution is to ship the Java service implementation in a common base package but include the service registration and configuration files in a separate package. Take a look at the virtual-keyboard-us package:
|-- package-in.xml `-- resources |-- images | |-- abc-caps.gif | |-- abc.gif | |-- handwriting.gif | |-- int-caps.gif | |-- int.gif | `-- num.gif |-- layouts.xml `-- services.xml
First let's look at the package-in.xml.
<package name="virtual-keyboard-us" version="0.1">
...
<requires>
<dep name="virtual-input" predepends="true" ns='http://www.xoe.org/installer/base/package' />
</requires>
</package>
Notice that there is an explicit dependency on the virtual-input package. This guarantees that the classes provided by virtual-input (including VirtualKeyboard) will be available to this package.
The resource file services.xml contains the following:
<services>
<service class='org.xoe.input.virtual.VirtualKeyboard' config='layouts.xml'/>
</services>
This causes an instance of VirtualKeyboard to be created, and initialized using the layouts.xml file. Let's take a quick look at the implementation of VirtualKeyboard:
public class VirtualKeyboard extends Component implements IService, IVirtualInput, MouseHook { ... public void init (Document doc) throws Exception { XDocument xdoc = (XDocument) doc; NodeList list = XPath.findNodes (doc,"/virtualkeyboard[1]"); language = ((Element)list.item (0)).getAttribute ("language").toUpperCase(); ... } public String[] getFeatures (String functionality) { if (functionality.equals (IVirtualInput.FUNCTIONALITY)) { return (new String[] {IVirtualInput.LANGUAGE_FEATURE, getInputLanguage(), IVirtualInput.LOCALE_FEATURE, getInputLocale()}); } else { return null; } } ... public String getInputLanguage () { return language; } ... }
Notice that the values of the language and locale features are not hardcoded in the Java, but derived directly from the layouts.xml file used to configure a specific instance.
The IconManager is responsible for selecting an appropriate icon to represent a file in a file browser. For the majority of cases, this is managed using a mapping of extension (or mimetype) to an image URL. For some cases, however, this mapping is not sufficient. In the case of shortcut files (*.lnk) for example, it's desirable to dereference the shortcut and look up an icon for the target file. For XoeServlet based applications (represented in the file system by *.xoe-config files) the URL of the appropriate icon is embedded in the xoe-config file. Rather than build this special logic into the IconManager itself, and in order to allow for future extensions, the IconManager uses services in its operation. Look at this snippet of IconManager.java:
package org.xoe.display; ... public class IconManager implements Constants { ... private static IService[] getCustomHandlers (ContentType type) { SimpleServiceQuery q = new SimpleServiceQuery (IIconResolver.FUNCTIONALITY, IIconResolver.INTERFACE); q.addFeature (FEATURE_MIMETYPE, type.getMimetype ()); return ServiceLocator.findServices (q); } ... public static URL getIconURL (ContentElement resource) throws IOException { ContentType type = resource.getType (); IService[] handlers = getCustomHandlers (type); URL iconURL = null; for (int i = 0; handlers != null && i < handlers.length; i++) { // 1) allow a custom handler to determine the icon // (useful for text/vnd.tvt.lnk and text/xml) iconURL = ((IIconResolver)handlers[i]).getIconURL (resource); if (iconURL != null) { return (iconURL); } } // 2) return the icon for this mimetype iconURL = (URL)m_hash.get (type.getMimetype ()); if (iconURL != null) { return (iconURL); } // 3) return the icon for this suffix iconURL = (URL)m_hash.get (type.getSuffix()); if (iconURL != null) { return (iconURL); } // 4) return default file icon return (getDefaultIcon ()); } ... }
As you can see the function getIconURL (ContentElement) is responsible for determining an appropriate icon for a specific resource (a ContentElement indicates a resource with a specific URL and provides methods for determining the content type of the resource). Before looking in its local map of suffixes and mimetypes to URLs, it first looks for services implementing the IIconResolver interface. This interface is defined in the xoe-interfaces package. It's a good design practice to provide interfaces in their own packages. This makes upgrading packages easier (see the packaging documentation for more information).
The IIconResolver service for shortcuts is represented by the class org.xoe.content.text.lnk (provided in the xhtml-services package). Look at this snippet from its source code:
package org.xoe.content.text; ... public class lnk extends xml { ... public String[] getFeatures (String func) { if (func.equals (FUNCTION_CONTENT_HANDLER) || func.equals (IIconResolver.FUNCTIONALITY)) { return (new String[]{ FEATURE_EXTENSION, EXTENSION_LNK, FEATURE_MIMETYPE, MIMETYPE_TEXT_VND_TVT_LNK }); } return (null); } ... public XDocument getViewableDocument (ContentElement content) throws IOException { content = getRedirectedContent (content); if (content == null) { return (null); } IViewableDocumentBuilder vdb = (IViewableDocumentBuilder)content.getType ().getService (IViewableDocumentBuilder.INTERFACE); if (vdb == null) { Logger.log (this, "getViewableDocument: no viewable document builder service found for " + content.getURL ()); return (null); } return (vdb.getViewableDocument (content)); } public URL getIconURL (ContentElement resource) throws IOException { // use the icon for the file that the link represents return (IconManager.getIconURL (getRedirectedContent (resource))); } ... private ContentElement getRedirectedContent (ContentElement content) throws IOException { // get the xml document builder service SimpleServiceQuery q = new SimpleServiceQuery (FUNCTION_CONTENT_HANDLER, IDocumentBuilder.INTERFACE); q.addFeature (FEATURE_MIMETYPE, MIMETYPE_TEXT_XML); IDocumentBuilder db = (IDocumentBuilder) ServiceLocator.findService (q); if (db == null) { return (null); } XDocument d = db.getDocument (content); String lnkSrc = XPath.findString (d, LNK_SRC_PATH); return (ContentCache.fetch (lnkSrc)); } }
As you can see, this class actually provides a number of services for .lnk files. Also, it makes use of other services itself, in particular an IDocumentBuilder, used to turn .xml files into org.w3c.dom.Document objects. In order to determine an icon for .lnk files, it determines the target file of the shortcut and gets a content element for it. Then it calls the IconManager and allows it to resolve the icon for the original file. If you've been paying attention, you know that that call could potentially be passed on to yet another service. XOE is built upon layers of services such as these.