Amsterdam - User Guide

This is the no fuzz guide to Amsterdam. It is first and foremost indented for system administrators and applications assemblers. Developers of Amsterdam components should also read this guide, but must also read javadoc, check the demos and the source code for existing components.

What is Amsterdam?

Amsterdam may be called an integration container. It's a container into which one or more so called channels (or an application with one or more services) may be deployed. Think of it much like a Servlet Filter - but more generic.

A channel consist of three parts: an invocation layer that puts or receives messages, a chain of filter which works on the message, and a publisher which handles the message to some other system.

An amsterdam channel may be used to do a lot of different types of works, but the most typical situation is where two different systems (with different views on how to reach other system and understand the data of other systems) should be connected.

Amsterdam is meant to contain most of the common components one need to set up a channel. This means that development of a channel most often will involve no coding, but instead is about configuration already existing components - wiring them together in an amsterdam deployment descriptor.

The component API:s for the most common components (filters and publishers) are also simple. A programmer used to work with JSP, Servlets and XML should have no problem developing custom Amsterdam components.

Installing Amsterdam

Begin by downloading a binary release, or get a source release or from CVS and follow the build instructions.

Currently Amsterdam only works in JBoss. It has been run in all JBoss environments since JBoss 2.0, but this release is only for the 3.2 series. Download JBoss and unpack it somewhere (the the examples Amsterdam and JBoss was unpacked in the same directory).

Amsterdam consists of a JBoss sar file which must be deployed into a JBoss server. Its possible to get it to work in minimal, default and all configurations, but the recommended configuration is default.

Start by copying the Amsterdam server sar file into the deploy directory of your JBoss installation. For a binary release do this.

	cp amsterdam-X.X/server/amsterdam-server-X.X.sar \
	jboss-3.2.3/server/default/deploy
	cd jboss-3.2.3/bin
	sh run.sh
      

If you build from source the Amsterdam sar will be located in amsterdam/server/target.

To install new channel/service into the Amsterdam container a channel must be packed and deployed much like a sar, ejb jar or webapp. Amsterdam uses JBoss built in deployment system. There are however often quite hard to get these deployments to deploy in the correct order. To solve this problem Amsterdam channels should be deployed in a special deploy directory. When the Amsterdam server component is deployed it will automatically create the correct directory structure under the JBoss configuration it is deployed in. A channels/deploy/ directory will be created. This is the directory where new channels should be deployed.

Reconfiguring Amsterdam server

The setup of Amsterdam server is done in an ordinary jboss-service.xml file. There are a couple of things worth mentioning there.

First: its possible to change the location of the Amsterdam deploy directory. Just change the DeploymentDirectory attribute.

	<mbean
	  code="org.backsource.amsterdam.service.jboss.JBossServiceFactory"
	  name="Amsterdam:service=ServiceFactory">
	  <depends
	  optional-attribute-name="CronDelegateName">amsterdam:name=Cron</depends>
	  <attribute name="DeploymentDirectory">${jboss.server.home.dir}/channels/deploy/</attribute>
	</mbean>
	  

This directory is also specified in a special deployment scanner:

	<mbean code="org.jboss.deployment.scanner.URLDeploymentScanner"
	  name="Amsterdam:type=DeploymentScanner,flavor=URL">
	  <!-- We MUST wait one something late in chain to give a ready env
	  for our channels-->
	  <depends>jboss.j2ee:service=ClientDeployer</depends>
	  <depends optional-attribute-name="Deployer">jboss.system:service=MainDeployer</depends>
	  
	  <attribute name="URLComparator">org.jboss.deployment.DeploymentSorter</attribute>
	  <attribute name="Filter">org.jboss.deployment.scanner.DeploymentFilter</attribute>
	  
	  <attribute name="ScanPeriod">5000</attribute>
	  <attribute name="URLs">
	  ${jboss.server.home.dir}/channels/deploy/</attribute>
	</mbean> 
	  

Edit the URLs attribute. To get the scanner to start late in the deploynent cycle (so that we know that stuff like JMS, JDBC, EJB-container and Web-container is started) we also use a depends element to get it to wait on a component we know JBoss will deploy late. The default is:

	  <depends>jboss.j2ee:service=ClientDeployer</depends>
	  

If you deploy in a JBoss configuration that does not have the jboss.j2ee:service=ClientDeployer component this must be changed to another component or removed altogether.

If you only use the modern approach to channel deployment, its possible to completely remove the specific Amsterdam Deployer. Just uncomment the following mbean:

	<mbean code="org.backsource.amsterdam.deployment.jboss.AmsterdamDeployer"
	  name="Amsterdam:service=AmsterdamDeployer">
	  <depends optional-attribute-name="ServiceContainerFactory">Amsterdam:service=ServiceFactory</depends>
	</mbean>
	  

Creating a channel

Many channels may be put together with already existing components. The work to create such a channel is mostly a question of application assembly (and perhaps some XSL coding). A channel is assembled by writing an Amsterdam deployment descriptor following the amsterdam3.dtd. Depending on deployment strategy it might have to be packed in a special archive and then be deployed.

There are currently two possible ways to assemble a deployment descriptor.

There is the so called modern method. Here an ordinary JBoss service file is used and the Amsterdam deployment descriptor is just a configuration element in an AmsterdamChannel mbean, much like a JMS destination is deployed in JBoss. There are many advantages in using this method since one gets access to all modern stuff in what can be done in a jboss-service.xml file. It also makes it a lot easier to use external mbean when setting up channels. Last but no least, if all classes are available globally one may deploy channel just by deploying an xml file.This is the method described here.

There is also an older method where one writes an amsterdam.xml deployment descriptor. Puts that in META-INF in a jar file which ends with .jar or .aar. Amsterdam channels have been deployed this way for three years, and there might still be situations where this is the preferred method. Its however not documented here.

There are basically four steps involved in creating a channel.

  1. Set up an amsterdam channel project.
  2. Create/edit the jboss-service.xml for the channel.
  3. Pack the channel, with any dependencies and configuration files in a sar-archive.
  4. Deploy the channel in JBoss.

Create a channel project

There are really no special requirements on an Amsterdam channel projects that separates it from an ordinary JBoss sar project. To ease setup there is however a maven genapp template included in Amsterdam, that will create a maven project with stubs for the jboss-service.xml file and build of the sar-file.

The requisite for using a project setup with the Amsterdam's template is that the maven-amsterdam-plugin is installed. If you are using the bin distribution you must copy the plugin to your maven home directory:

	  cp
	  amsterdam/maven-plugins/amsterdam/maven-amsterdam-plugin-X.X.jar\
	  $MAVEN_HOME/plugins
	

If you have build from source, it should have been automatically installed.

Lets create a demo project. We begin by creating the directory and then we run genapp:

	  mkdir mychannel
	  cd mychannel/
	  maven -Dmaven.genapp.template.dir=\
	  ../amsterdam/maven-plugins/genapp/amsterdam/ genapp
	

Fill in values for the questions asked.

The following tree structure will be created:

	  |-- maven.xml
	  |-- project.properties
	  |-- project.xml
	  `-- src
	  `-- channel
	      |-- META-INF
	      |   `-- jboss-service.xml
	      `-- data
	

The most important part of this is the jboss-service.xml where the channel configuration should be filled in. More on that in next section.

The channel may be build just by invoking maven.

	  maven
	

The channel is build with the maven-amsterdam-plugin. See the documentation for it for further customization.

Configuring the channel

The genapp template will have created a jboss-service.xml stub for the channel. There are two parts in editing that file. First, its is an ordinary JBoss service deployment descriptor. If you need a scoped deployment, need to deploy additional mbean, or need external libs or need to automatically create a directory structure in the JBoss data dir, thats fine.

One common pattern to use in Amsterdam is to have a special directory for each channel where such things as backups, variable data, examples and configuration files may reside. In the directory tree created by genapp a data dir is created under the channel. Here you typically build a tree structure with directories and files you want to have in such a data directory. These will be included in the resulting sar-file, and through the use of the local-directory will be copied to a directory in JBoss data directory

	  <!-- Setup a local dir structure -->
	  <local-directory path="channels/mychannel/"/>
	  

The second part is the Amsterdam xml configuration. A channel is deployed through the use of the org.backsource.amsterdam.deployment.jboss.AmsterdamChannel MBean. This MBean takes an XML attribute named AmsterdamConf:

	<mbean code="org.backsource.amsterdam.deployment.jboss.AmsterdamChannel"
	  name="amsterdam:service=AmsterdamChannel,name=mychannel">
	  <depends
	  optional-attribute-name="ServiceContainerFactory">Amsterdam:service=ServiceFactory</depends>
	  <attribute name="AmsterdamConf"></attribute>
	</mbean>
	  

The Amsterdam conf should be placed inside the AmsterdamConf amsterdam conf attribute element.

Architectur of Amsterdam conf

Lets start by looking at the general structure of an Amsterdam deployment descriptor.

<application>
  <name/>

  <service>
    <name/>

    <content-map>
      <content-map-entry>
        <url/>    
	<mime/>
	<protocol/>
	<root-element/>
      </content-map-entry>
    </content-map>

    <get-service-invoker>
      <worker-queue>
	<worker-que-conf/>
      </worker-queue>
      <protocol-factory>
	<protocol-handler-factory>
	  <protocol-conf>
	    <conf/>
	  </protocol-conf>
	</protocol-handler-factory>
      </protocol-factory>
      <handler-strategy/>
      <cron>
	<cron-entry>
	  <schedule-at/>
	  <job/>
	</cron-entry>
      </cron>
    </get-service-invoker>

    <filters>
      <filter>
	<filter-conf/>
      </filter>
    </filters>
    <publisher/>
  </service>
</application>

	    

An Amsterdam deployment unit is an application. An application may consist of one or more service elements. Each such service is actually an individual channel. The application element just makes it possible to group several channels together.

A channel (from here on named service) should have a name:

	    <service>
	    <name>mychannel</name>
	    

Logically a service consists of three parts.

  • The invocation layer. This is where messages get put into the system. The invocation layer may be a GET invoker, which fetches data, or a REC invoker, which received data.
  • A chain of filter, which may check or alter data.
  • A publisher, which marks the end of the chain, and should logically put the messages into some other system (a publisher may actually do nothing).

There is however also something called the ContentMap. A content map is a meta-configuration of the types of jobs that a service should do. A content map contains entries, which each describes the data for a particular work. The name attribute in the content-map-entry is an ID and may be used by other elements in the descriptor to reference that specific content-map-entry.

A content-map is required for get-based invoker service, but not for rec-based. If left out in a rec-base one content-map-entry will be automatically crated with the same name as the service it belongs to.

A content-map-entry holds a specific configuration for a data entity that is to be references inside the service configurations. The attribute name must be unique within the complete XML file.

Here is the content map created by genapp:

	  <content-map>
	     <content-map-entry name="mychannel">
	       <url>.</url>     
	       <mime>text/xml</mime>
	       <protocol>DIR</protocol>
	       <root-element></root-element>
	     </content-map-entry>
	   </content-map>
	    

The Service invoker

Almost all component in Amsterdam are pluggable. Normally you must therefore specify a className attribute for these components. Each type takes a class that either implements a certain interface or extends a base class of some type. Some element may instead reference an existing mbean. More on that further down.

A service contains either a get-service-invoker or a rec-service-invoker. A GET service must contain a worker-queue and a protocol-factory. The attribute className must point to a valid class that implements the org.backsource.amsterdam.service.service.ServiceInvoker interface.

For a REC service invoker it is possible to get it registered as an MBean, by specifying a full ObjectName as the name attribute. If a jndi-name attribute is given a proxy will also be bound in JNDI that is both locally and remotely available.

If a use-bean attribute is used together with a name attribute the invoker will register itself with that MBean. This feature is for example supported by the org.backsource.amsterdam.service.jboss.RecMBeanServiceInvoker.

Currently available invokers are: org.backsource.amsterdam.service.GETServiceInvoker, org.backsource.amsterdam.service.RECServiceInvoker and org.backsource.amsterdam.service.jboss.RecMBeanServiceInvoker

Here's how GET would look:

	    <get-service-invoker 
	       className="org.backsource.amsterdam.service.GETServiceInvoker">
	    

And here's how usage of an external MBean may look:

	    <rec-service-invoker
	    className="org.backsource.amsterdam.service.jboss.RecMBeanServiceInvoker"
	    name="amsterdam.channel:service=Service,name=jmsout"
	    use-bean="true"
	    />
	    

A service invoker may (or must depending on type) contain the following sub elements: worker-queue,protocol-factory,handler-strategy,and cron

A worker-queue represents an invocation strategy for the invoker. All work to be done by the invoker should go through the WorkerQueue. The attribute className must point to a valid class that implements the org.backsource.service.amsterdam.service.workerqueue.WorkerQueue interface. If the worker-queue-conf element is present the class must also implement the org.backsource.service.amsterdam.metadata.XmlConfigurable interface.

Here's an example:

	    <worker-queue 
	      className="org.backsource.amsterdam.service.workerqueue.ThreadPoolWorkerQueue">
	      <worker-que-conf>
	        <max-threads>2</max-threads>
	      </worker-que-conf>
	    </worker-queue>
	    
	    

A protocol-factory is a factory for all the protocols that a particular service supports. It consists of one or more entries of protocol-handler-factory, which each knows how to return handlers for a specific protocol. The name of the protocol is governed by the content map entry for the job entry in question. The className attribute must point to a valid class which implements the org.backsource.amsterdam.service.protocol.ProtocolFactory interface.

A protocol handler factory is a class that knows how to create protocol handlers. The attribute className must point to a valid class that implements the org.backsource.amsterdam.service.protocol.ProtocolHandlerFactory interface.

A protocol-handler-factory may either contain a protocol-conf, which consists of one or more conf elements, or a protocol-owns-config element which contains ANY XML data. It is then up to the protocol-handler-factory to implement the org.backsource.service.amsterdam.metadata.XmlConfigurable interface.

Here is a directory protocol handler config that will fetch files from the ${jboss.server.data.dir}/channels/simplejms/data/ directory. The trailing "/" is important.

	    <protocol-factory 
	      className="org.backsource.amsterdam.service.protocol.DefaultProtocolFactory">
	      <protocol-handler-factory 
		className="org.backsource.amsterdam.plugins.file.DirProtocolHandlerFactory">
		<protocol-conf>
		  <conf name="BaseUrl">${jboss.server.data.dir}/channels/simplejms/data/</conf>
		</protocol-conf>
	      </protocol-handler-factory>
	    </protocol-factory>
	    

A handler-strategy represents a strategy that will have the possibility to partake in the protocol handlers action. Its only valid for GET invokers. The className attribute must point to a valid class that implements the org.backsource.amsterdam.service.strategy.HandlerStrategy interface. The element may contain any XML markup, and if it does the HandlerStrategy must be able to interpret it itself by implementing the org.backsource.service.amsterdam.metadata.XmlConfigurable interface. A typical strategy for a GET invoker is a remove strategy that removes files when they have been processed. Other variants is strategies that caches id:s of handled messages and do not allow more invokes that one and so forth.

Here's a strategy that removes files through the directory handler.

	  <handler-strategy 
	    selector="*" 
	    className="org.backsource.amsterdam.service.strategy.SimpleRemoveStrategy">
	  </handler-strategy>
	    

The last element is the cron element. Its only valid for GET invokers. The cron element contains cron-jobs, which is used to configure a CronManager. The CronManager will invoke the ServiceInvoker with the current job. All GET invoker need one or more cron entries to get it to do any work. The schedule-at contains a cron expression and the job element must point to the name of a content map entry.

Here's a cron that will run the jmsin content map entry jobb trough its invoker once every minute.

	    <cron>
	      <cron-entry>
		<schedule-at>* * * * *</schedule-at>
	        <job nameref="jmsin"/>
	      </cron-entry>
	    </cron>
	    

And here is a complete invoker example:

	  <get-service-invoker className="org.backsource.amsterdam.service.GETServiceInvoker">
	    
	    <worker-queue className="org.backsource.amsterdam.service.workerqueue.ThreadPoolWorkerQueue">
	      <worker-que-conf>
		<max-threads>2</max-threads>
	      </worker-que-conf>
	    </worker-queue>
	    
	    <protocol-factory 
	      className="org.backsource.amsterdam.service.protocol.DefaultProtocolFactory">
	      <protocol-handler-factory 
		className="org.backsource.amsterdam.plugins.file.DirProtocolHandlerFactory">
		<protocol-conf>
		  <conf name="BaseUrl">${jboss.server.data.dir}/channels/simplejms/data/</conf>
		</protocol-conf>
	      </protocol-handler-factory>
	    </protocol-factory>
	    
	    <handler-strategy 
	      selector="*" 
	      className="org.backsource.amsterdam.service.strategy.SimpleRemoveStrategy"></handler-strategy>
	    <cron>
	      <cron-entry>
		<schedule-at>* * * * *</schedule-at>
		<job nameref="jmsin"/>
	      </cron-entry>
	    </cron>
	    
	  </get-service-invoker>
	    

Filters

The filters element contains zero or more filters, that the arriving message will be put through before beeing published.

A filter is a class that does something with the incoming message. The attribute className must point to a valid class that implements the org.backsource.amsterdam.service.filter.ServiceFilter interface. If the filter is an mbean it is possible to get it registered in the mbean server by specifying a name attribute. This must either be a logical name (such as xslfilter) or a complete JMX ObjectName.

If a use-bean attribute is used together with a name attribute, the className attribute will be ignored and all calles will be delegated to the mbean pointed at by the name attribute. A filter may contain a filter-conf or a filter-map.

A filter-conf element is a container for any configuration the filter needs. It may contain any valid XML markup. If it is added the filter class must be implementing the org.backsource.service.amsterdam.metadata.XmlConfigurable interface.

The filter-map element is a specialized configuration element for xsl handling. Entries in it map a content map entry to an XSL file. A filter-map-entry binds an XSL file to a particular ContentMapEntry; and zero or more parameters that may be used i the XSL. The attribute nameref must contain one or more names for specifics content-map-entry name.

Here's a filter with its own configuration:

	     <filter className="org.backsource.amsterdam.plugins.file.FileHandler">
	      <filter-conf>
		<directory>${jboss.server.data.dir}/channels/axis-demo/raw-backup</directory>
	      </filter-conf>
	    </filter>
	    

Here's a filter that exposes an mbean

	    <filter
	    className="org.backsource.amsterdam.service.filter.DummyFilter"
	    name="ettFilter"
	    />
	    

And here is one that uses an external MBean:

	    <filter
	    name="amsterdam.channel:service=Service,name=tre,filter=treFilter"
	    use-bean="true"
	    />
	    

Where the MBean would have been defined earlier in the jboss-service.xml file:

	    <mbean code="org.backsource.amsterdam.service.filter.DummyFilter"
	    name="amsterdam.channel:service=Service,name=tre,filter=treFilter"/>
	    

Publisher

A publisher is the last component in the chain. The attribute className should point to a valid class which implements the org.backsource.amsterdam.service.publisher.Publisher interface. If the publisher is an mbean it is possible to get it registered in the mbean server by specifying a name attribute. This must either be a logical name (such as jmspublisher) or a complete JMX ObjectName. If a use-bean attribute is used together with a name attribute, the className attribute will be ignored and all called will be delegate to the mbean pointed at by the name attribute.

The element may contain any XML markup, but if it does the class itself must be able to interpret it by implementing the org.backsource.service.amsterdam.metadata.XmlConfigurable interface.

Its important to note that a publisher must always be defined, but what it actually does is up to the implementation. A filter may therefore also "publish".

Here's an axis publisher.

	  <publisher
	    className="org.backsource.amsterdam.plugins.axis.AxisHandler">
	    <ws-url>http://localhost:8080/axis-demo/services/DeliverDocument</ws-url>
	    <ns>http://localhost:8080/axis-demo</ns>
	    <method>handleDocument</method>
	    <user>vv</user>
	    <password>vv</password>
	  </publisher>
	    

And here's a publisher that will also be exposed as an MBean:

	    <publisher
	    className="org.backsource.amsterdam.service.publisher.DummyPublisher"
	    name="amsterdam.channel:service=Service,name=tva,publisher=tvaPublisher"
	    />
	    

Descriptor example

And here is the complete jboss-service.xml file generated by genapp, that needs to be filled in:

     <application>
	<service>
	  <name>mychannel</name>
	  <content-map>
	    <content-map-entry name="@ID">
	      <url>.</url>     
	      <mime>text/xml</mime>
	      <protocol>DIR</protocol>
	      <root-element></root-element>
	    </content-map-entry>
	  </content-map>
	  
	  <get-service-invoker className="org.backsource.amsterdam.service.GETServiceInvoker">
	    
	    <worker-queue className="org.backsource.amsterdam.service.workerqueue.ThreadPoolWorkerQueue">
	      <worker-que-conf>
		<max-threads>2</max-threads>
	      </worker-que-conf>
	    </worker-queue>
	    
	    <protocol-factory 
	      className="org.backsource.amsterdam.service.protocol.DefaultProtocolFactory">
	      <protocol-handler-factory 
		className="org.backsource.amsterdam.plugins.file.DirProtocolHandlerFactory">
		<protocol-conf>
		  <conf name="BaseUrl">${jboss.server.data.dir}/channels/mychannel/</conf>
		</protocol-conf>
	      </protocol-handler-factory>
	    </protocol-factory>
	    
	    <handler-strategy 
	      selector="*" 
	      className="org.backsource.amsterdam.service.strategy.SimpleRemoveStrategy"></handler-strategy>
	    <cron>
	      <cron-entry>
		<schedule-at>* * * * *</schedule-at>
		<job nameref="mychannel"/>
	      </cron-entry>
	    </cron>
	    
	  </get-service-invoker>
	  <filters>
	    <filter 
	      className="org.backsource.amsterdam.service.filter.DummyFilter"/>
	  </filters>
	  <publisher
	    className="org.backsource.amsterdam.service.publisher.DummyPublisher">
	  </publisher>
	</service>
      </application>
	    

Packing

When the configuration is done, the channel must be packed in a so called sar-file. This is actually an ordinary jar, with a jboss-service.xml in META-INF that ends with .sar. In a genapp Amsterdam generated project it's very easy to package the component. Just run maven without a goal.

	  maven
	

This might fail the first time. In the generated project.xml a dependancy on amsterdam-plugins is defined. Either you must uncomment that entry or make shure its in your local repository, or edit project.properties and point maven.jar.amsterdam-plugins = ${basedir}/../amsterdam/plugins/target/amsterdam-p lugins-1.0.jar to a correct location.

Since its an ordinary jar-file its possible to use ant jar as well. Amsterdam comes with a special maven-amsterdam-plugin with both a goal and a tag that helps in packing Amsterdam channels. Read the documentation for that plugin.

Deploying

Once packed, its time to deploy the channel. This is done by coping the sar file to Amsterdams deployment directory. In our eaxmple that might look like:

	  cp target/mychannel.sar ../jboss-3.2.3/server/default/channels/deploy/
	

Whatch the deployment happen!

Developing components

A component in Amsterdam is a java class that either implements or extends any of the required interfaces or base classes that each different type of element that has a className attribute requires.

To explain every interface here would only say whats (hopefully) are already available in javadoc. However a couple of notes is given here.

First of all, there are already several plugin implementations available, both in the server project and in the plugins project (and also in the Xindice project). Use them as examples. Apart from the demo channels, there are also some channels in the server: server/src/conf/examples/. Many of the components in plugin also have individual tests where more about their behavior may be discovered

Secondly: use the plugins classes to develop new components; at least new Filters and Publishers.

There are some classes that almost every plugin has to have a relationship with.

There is the org.backsource.amsterdam.service.Servable interface that defines the life-cycle of a component.

	public interface Servable {
	   public void init() throws Exception;
	   public void start() throws Exception;
	   public void stop() throws Exception;
	   public void destroy() throws Exception;
	}
      

There is also the org.backsource.amsterdam.metadata.XmlConfigurable interface that is used by components to indicate that it can handle its own XML configuration data:

	public interface XmlConfigurable {
	   public void importXml(Element element) throws DeploymentException;
	}
      

Almost all components also uses the org.backsource.utils.xml.ElementUtil from the utils package to handle the XML configuration tasks.

Last but not least is the org.backsource.amsterdam.service.ServiceMessage. This class is the message that is created by a ProtocolHandler, and then which carry the data down the chain. The method the ServiceFilter is invoked through looks like this:

	public void handleMessage(ServiceMessage message) throws ServiceException;
      

A ServiceMessage carries the content and payload. It may carry the content as a String, and InputStream or as an Object. If it has an object, the filters and publishers that works on the message must have their own knowledge on how to treat the object. org.backsource.amsterdam.plugins.HandlerBase in plugins has a lot of helper methods to make it easier to work with the ServiceMessage. The org.backsource.amsterdam.plugins.XMLHandlerBase has additional support to handle XML messages as DOM Documents.

Most of the interface have either default implementation or so called Support classes to help in implementing new components. For example to implement a new ProtocolHandlerFactory one would subclass org.backsource.amsterdam.service.protocol.ProtocolHandlerFactorySupport.

Most important; as has been said before: use the javadoc and the source.