Home » Docs

Adding custom artifact types

Out of the box, ACE comes with support for bundles and configuration files that follow the Auto Configuration specification. However, it is possible to extend ACE with support for new types of artifacts. Doing so requires you to do three things:

  1. Write a resource processor, according to the Deployment Admin specification.
  2. Write an ArtifactRecognizer service implementation.
  3. Write an ArtifactHelper service implementation.

This document explains how to write each of these, and how to subsequently deploy them appropriately.

Writing a resource processor

A resource processor implements the ResourceProcessor interface as defined in the Deployment Admin specification (OSGi Compendium 114.10). It describes in detail the contract that needs to be implemented and also contains an example. If you want to get started quickly, you can use the following code:

public class Processor implements ResourceProcessor {
    private volatile DeploymentSession m_session;
    private String m_deploymentPackageName;
    private List<String> m_toInstall;
    private List<String> m_toRemove;

    /**
     * Sets up the necessary environment for a deployment session.
     */
    private void startSession(DeploymentSession session) {
        if (m_session != null) {
            throw new IllegalArgumentException("This resource processor is currently processing another deployment session, installing deploymentpackage" + m_session.getTargetDeploymentPackage().getName());
        }
        m_session = session;
        m_toInstall = new ArrayList<String>();
        m_toRemove = new ArrayList<String>();
        String fromSource = session.getSourceDeploymentPackage().getName();
        String fromTarget = session.getTargetDeploymentPackage().getName();
        if (fromSource.equals("")) {
            m_deploymentPackageName = fromTarget;
        }
        else {
            m_deploymentPackageName = fromSource;
        }
    }

    /**
     * Ends a deployment session.
     */
    private void endSession() {
        m_session = null;
        m_deploymentPackageName = null;
        m_toInstall = null;
        m_toRemove = null;
    }

    private void ensureSession()  {
        if (m_session == null) {
            throw new IllegalStateException("This resource processor is currently not part of a deployment session.");
        }
    }

    public void begin(DeploymentSession session) {
        startSession(session);
    }

    public void process(String name, InputStream stream) throws ResourceProcessorException {
        ensureSession();
        m_toInstall.add(name);
    }

    public void dropped(String resource) throws ResourceProcessorException {
        ensureSession();
        m_toRemove.add(resource);
    }

    public void dropAllResources() throws ResourceProcessorException {
        ensureSession();
        m_toRemove.addAll(Collections.EMPTY_LIST /* should be a list of all current resources */);
    }

    public void prepare() throws ResourceProcessorException {
        ensureSession();
    }

    public void commit() {
        ensureSession();
        while (!m_toInstall.isEmpty()) {
            System.out.println("installing: " + m_toInstall.remove(0));
        }
        while (!m_toRemove.isEmpty()) {
            System.out.println("removing: " + m_toRemove.remove(0));
        }
        endSession();
    }

    public void rollback() {
        // nothing special to do.
        ensureSession();
        endSession();
    }

    public void cancel() {
        ensureSession();
        // Nothing to do: we have no long-running operation, we only read the stream.
    }
}

This should be enough to get you started, but the code takes a few shortcuts so please do take some time to read the specification and implement each method correctly.

The next step then is to publish this processor as an OSGi service. Don't forget to add the SERVICE_PID property and set its value, because that is how the processor is later recognized. Package everything up in an OSGi bundle and make sure to include these two headers in its manifest:

Deployment-ProvidesResourceProcessor: org.apache.ace.resourceprocessor.custom
DeploymentPackage-Customizer: true

The first one should match the PID you used earlier.

Writing an ArtifactHelper and ArtifactRecognizer

These services can usually just be implemented by the same class. The artifact recognizer is used to identify an artifact as being of this specific type. How it does that is up to the implementation. It might do something as simple as look at the file extension, but a lot of recognizers will also inspect (part of) the actual file. The artifact helper then contains all kinds of methods to help ACE deal with this specific type of artifact. The API documentation of these two interfaces explains the methods in detail. Again, let's get started with an implementation of both for a very simple artifact: a file with a specific extension:

public class CustomArtifactHelper implements ArtifactRecognizer, ArtifactHelper {
    public static final String KEY_FILENAME = "filename";
    public static final String MIMETYPE = "application/vnd.apache.ace.custom";
    public static final String PROCESSOR = "org.apache.ace.resourceprocessor.custom";

    @Override
    public boolean canUse(ArtifactObject object) {
        return (object == null) ? false : MIMETYPE.equals(object.getMimetype());
    }

    @Override
    public ArtifactPreprocessor getPreprocessor() {
        return null;
    }

    @Override
    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
        return "(" + ArtifactObject.KEY_ARTIFACT_NAME + "=" + obj.getAttribute(ArtifactObject.KEY_ARTIFACT_NAME) + ")";
    }

    @Override
    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
        return Integer.MAX_VALUE;
    }

    @Override
    public Comparator<ArtifactObject> getComparator() {
        return null;
    }

    @Override
    public Map<String, String> checkAttributes(Map<String, String> attributes) {
        return attributes;
    }

    public String[] getDefiningKeys() {
        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
    }

    public String[] getMandatoryAttributes() {
        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
    }

    @Override
    public String recognize(ArtifactResource artifact) {
        // here we can examine the stream to check the contents of the artifact
        // for this demo, our check is just for the extension of the URL, which is
        // a bit simplistic
        if (artifact.getURL().toExternalForm().endsWith(".custom")) {
            return MIMETYPE;
        }
        return null;
    }

    @Override
    public Map<String, String> extractMetaData(ArtifactResource artifact) throws IllegalArgumentException {
        Map<String, String> result = new HashMap<String, String>();
        result.put(ArtifactObject.KEY_PROCESSOR_PID, PROCESSOR);
        result.put(ArtifactObject.KEY_MIMETYPE, MIMETYPE);
        String name = new File(artifact.getURL().getFile()).getName();
        result.put(ArtifactObject.KEY_ARTIFACT_NAME, name);
        result.put(KEY_FILENAME, name);
        return result;
    }

    @Override
    public boolean canHandle(String mimetype) {
        return MIMETYPE.equals(mimetype);
    }

    @Override
    public String getExtension(ArtifactResource artifact) {
        return ".custom";
    }
}

Again, this is no production code, but it should get you started quickly. This code will recognize artifacts that have the ".custom" file extension. No effort is done here to look at the contents of the file just to keep the example simple. Publish this instance as both an ArtifactHelper and an ArtifactRecognizer and make sure to add a service property for the mime type: ArtifactObject.KEY_MIMETYPE, CustomArtifactHelper.MIMETYPE.

Wrap it up in a bundle, and you're ready to use your custom artifact in ACE.

Installing everything in ACE

To add the new artifact type to ACE, you first have to take the bundle that contained the ArtifactHelper and ArtifactRecognizer service and add that to ACE. The bundle should be part of the ACE client. If you use the "all in one" server, add it to that.

The second step is to upload the resource processor to ACE. You can do that like you upload any other bundle. Just add it to the artifact column. After adding it, it will not show up in the column, but ACE will send it to a target if that target needs an artifact of this type.

Should you ever discover that you made a mistake in your resource processor, you can simply upload a newer one. Just make sure you bump the version and ACE will ensure that everybody gets this latest version.