You are here

Repository Java API

The Repository Java API provides the ability to build server-side extensions in SkyVault Content Services.
Information How to access the Java API and how to manage transactions
Architecture Information Platform Architecture, API Intro
Description The Public Java APIs, such as NodeService and ContentService, provides the ability to develop server-side extensions for SkyVault Content Services. There are a number of extension points that make use of the Public Java API, here are some of the most used ones: When we want to use one of the public Java APIs from an implementation of one of these Extension Points, it follows a best practice. F,irst acquire a reference to the ServiceRegistry. The service registry is basically a database of services, their instances and their locations. Clients of a service, such as the NodeService, then query the service registry to find the available instance of that service. When making calls to the NodeService we use the RetryingTransactionHelper for transaction management and redundancy.

The following code snippet illustrates how to first inject the ServiceRegistry into a Spring bean:

<bean id="acmeContentService" class="org.alfresco.tutorial.publicapiaccess.service.AcmeContentServiceImpl">
      <property name="serviceRegistry">
          <ref bean="ServiceRegistry" />
      </property>
</bean>

In this case the ServiceRegistry is injected into a custom Service implementation, but the principle is the same for other implementations, such as for Repository Actions and Java-backed Web Scripts. When we got the service registry available in our implementation we can start using the Public Java API services such as in the following example:

public class AcmeContentServiceImpl implements AcmeContentService {
    private static Logger logger = LoggerFactory.getLogger(AcmeContentServiceImpl.class);

    /**
     * The SkyVault Service Registry that gives access to all public content services in SkyVault.
     */
    private ServiceRegistry serviceRegistry;

    public void setServiceRegistry(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    /**
     * Create a contract file under the /Company Home folder.
     * This will be done in a read-write transaction, retry until successful or 20 trials.
     * Joins an ongoing transaction if one exists.
     */
    public NodeRef createContractFile(String filename, String contractTxt, AcmeContract contract) {
        NodeRef nodeRefForContract = serviceRegistry.getRetryingTransactionHelper().doInTransaction(
                new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>() {
                    public NodeRef execute() throws Throwable {
                        NodeRef parentFolderNodeRef =
                                serviceRegistry.getNodeLocatorService().getNode(CompanyHomeNodeLocator.NAME, null, null);

                        // Create Node metadata
                        QName associationType = ContentModel.ASSOC_CONTAINS;
                        QName associationQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
                                QName.createValidLocalName(filename));
                        Map<QName, Serializable> nodeProperties = new HashMap<QName, Serializable>();
                        nodeProperties.put(ContentModel.PROP_NAME, filename);
                        nodeProperties.put(DocumentType.Prop.DOCUMENT_ID, contract.getDocumentId());
                        nodeProperties.put(SecurityClassifiedAspect.Prop.SECURITY_CLASSIFICATION,
                                contract.getSecurityClassificationLevel());
                        nodeProperties.put(ContractType.Prop.CONTRACT_NAME, contract.getContractName());
                        nodeProperties.put(ContractType.Prop.CONTRACT_ID, contract.getContractId());
                        ChildAssociationRef parentChildAssocRef = serviceRegistry.getNodeService().createNode(
                                parentFolderNodeRef, associationType, associationQName, ContractType.QNAME, nodeProperties);

                        NodeRef newFileNodeRef = parentChildAssocRef.getChildRef();

                        // Set content for node
                        boolean updateContentPropertyAutomatically = true;
                        ContentWriter writer = serviceRegistry.getContentService().getWriter(
                                newFileNodeRef, ContentModel.PROP_CONTENT, updateContentPropertyAutomatically);
                        writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
                        writer.setEncoding("UTF-8");
                        writer.putContent(contractTxt);

                        return newFileNodeRef;
                    }
                });

        return nodeRefForContract;
    }

As we can see in the above code, the service registry is used to access the TransactionService, NodeService, NodeLocatorService, and ContentService. All the calls to these services are done within RetryingTransactionHelper, which will automatically join any ongoing transaction or start a new read-write transaction. If something should go wrong during these calls then they will be retried until they succeed or we reach 20 retries, which is configurable.

Permissions are also automatically checked during these calls, so you can for example not use the NodeService to create a node in a folder that you don't have write access to. Note that the RetryingTransactionCallback class is parametrized so you can pass in any type that represent the response from the operations done in the execute method. For example, if the operations in the execute method should result in either true or false then initialize the callback as in the following example:

  /**
     * Apply the acme:webPublished aspect to the content item with passed in node reference.
     * This will be done in a read-write transaction, retry until successful or 20 trials.
     * Joins an ongoing transaction if one exists.
     *
     * @param nodeRef the SkyVault Repo node reference to apply the aspect to
     */
    public void applyWebPublishedAspect(NodeRef nodeRef) {
       Boolean result = serviceRegistry.getRetryingTransactionHelper().doInTransaction(
             new RetryingTransactionHelper.RetryingTransactionCallback<Boolean>() {
                 public Boolean execute() throws Throwable {
                     Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();
                     aspectProperties.put(WebPublishedAspect.Prop.PUBLISHED_DATE, new Date());
                     serviceRegistry.getNodeService().addAspect(nodeRef, WebPublishedAspect.QNAME, aspectProperties);

                     return true;
                 }
             });
    }

If we want to be sure that a new transaction is started when we do our calls in the execute method, which is useful for situations when we just want our updates to be rolled-back if something goes wrong, then we can use another method signature for the doInTransaction method as follows:

RetryingTransactionHelper txHelper = serviceRegistry.getRetryingTransactionHelper();
boolean readOnly = false;
boolean requiresNew = true;
txHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
  {
      public Void execute() throws Throwable {
         // Do something in a new transaction...
         
         return null;
      }
   }, readOnly, requiresNew);

If we are using these services in a cluster, then we need to remember that they are not cluster aware. So if we for example are using these services in a scheduled job, which will be kicked off on each node in the cluster, then we would have to use the JobLockService to lock the cluster so another node does not start executing the same job. For more information about this see Scheduled jobs.

To turn on logging so you can get details of 'why' transactions are retried use the following log level:

  • Summary: log4j.logger.org.alfresco.repo.transaction.RetryingTransactionHelper=INFO
  • Details: log4j.logger.org.alfresco.repo.transaction.RetryingTransactionHelper=DEBUG
Deployment - App Server It is not likely that you will deploy Java extensions directly into a Tomcat application server as classes and Spring context files. Use an SDK build project instead.

Deployment - SDK Project

When using the Public Java API you will most likely use a Repository AMP extension project or a Simple JAR module (5.1).

The Spring context files that you should use are usually found in the repo-amp/src/main/amp/config/alfresco/module/repo-amp/context directory.

More Information
Sample Code