Movatterモバイル変換


[0]ホーム

URL:


In: content-servicesIn: All docs
Close
Alfresco Content Services

Java Foundation API reference

The Alfresco Java Foundation API provides the ability to build server-side extensions that runs in the sameprocess as Content Services. This API is used to build extensions for thePlatform (Repository).

Getting started

When we want to use one of the public Java APIs from an implementation of one of the Platform/RepositoryExtension Points, it follows a best practice. First acquire a reference to theServiceRegistry. The service registry is basically a database of services, their instances and their locations.

Clients of a service, such as theNodeService, then query the service registry to find the available instance of that service. When making calls to theNodeService we use theRetryingTransactionHelper for transaction management and redundancy.

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

<bean>      <property name="serviceRegistry">          <ref bean="ServiceRegistry" />      </property></bean>

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

public class AcmeContentServiceImpl implements AcmeContentService {    private static Logger logger = LoggerFactory.getLogger(AcmeContentServiceImpl.class);    /**     * The Alfresco Service Registry that gives access to all public content services in Alfresco.     */    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 theTransactionService,NodeService,NodeLocatorService, andContentService. All the calls to these services are done withinRetryingTransactionHelper, 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 theNodeService to create a node in a folder that you don’t have write access to. Note that theRetryingTransactionCallback class is parameterized so you can pass in any type that represent the response from the operations done in theexecute method. For example, if the operations in theexecute method should result in eithertrue orfalse 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 Alfresco 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 theexecute 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 thedoInTransaction 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 clusteraware. 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 theJobLockService to lock the cluster so another node does not start executing the same job. For more information about this seeScheduled 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

It is not likely that you will deploy Java extensions directly into a Tomcat application server as classes and Spring context files. You would instead use anAlfresco In-Process SDK project.

Put the Java source code in theaio/platform-jar/src/main/java/{domain specific directory path} path. And the Spring bean configuration inaio/platform-jar/src/main/resources/alfresco/module/platform-jar/context/service-context.xmlfile.

ActionService

An Action is a unit of work that can be carried out on a node. Actions are commonly used in conjunction with Rules, but that is not mandatory. When you create Rules for a folder, you can specify certain Actions to occur to nodes added to the folder. For example, when a Word document is added to a folder, you may want a PDF to be automatically generated, or a notification email to be sent. There are a number of built-in Actions available by default (there IDs in parentheses):

  • Execute Script (script)
  • Copy (copy)
  • Move (move)
  • Checkin (check-in)
  • Checkout (check-out)
  • Link to category (link-category)
  • Add Aspect (add-features)
  • Remove Aspect (remove-features)
  • Add simple workflow (simple-workflow)
  • Start workflow (start-workflow)
  • Cancel workflow (cancel-workflow)
  • Send email (mail)
  • Transform and copy content (transform)
  • Transform and copy image (transform-image)
  • Extract common metadata fields (extract-metadata)
  • Import (import)
  • Export (export)
  • Specialise type (specialise-type)
  • Create version (create-version)
  • Increment counter (counter)
  • Set property value (set-property-value)
  • Create thumbnail (create-thumbnail)
  • Execute all rules on node (execute-all-rules)

You can also create custom Actions to do whatever you want when content is added to a folder.

While Actions are typically triggered byRules, you can also invoke them directly by selecting them from a menu item. TheActionService also allows you to call them directly from code. Any piece of code that can access theActionService can invoke the Action, for example:

  • JavaScript
  • Workflow
  • Web script
  • Java

The following example shows how to invoke the Add Aspect out-of-the-box action with theActionService:

boolean executeAsync = true;Map<String, Serializable> params = new HashMap<String, Serializable>();params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, "cm:titled");Action addAspectAction = serviceRegistry.getActionService().createAction("add-features", params);if (a != null) {    serviceRegistry.getActionService().executeAction(addAspectAction, docNodeRef, true, executeAsync);} else {    throw new RuntimeException("Could not create add aspect action");}

Another example of how to execute a script:

boolean executeAsync = true;// JavaScript file node referenceNodeRef scriptRef = ...Action action = serviceRegistry.getActionService().createAction("script");action.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, scriptRef);if (action != null) {       serviceRegistry.getActionService().executeAction(action, docNodeRef, true, executeAsync);} else {    throw new RuntimeException("Could not create execute script action");}

In this example we invoke a custom action with the Spring bean idsend-as-email (more information about this repository action implementation can be foundhere:

import org.alfresco.service.cmr.action.Action;public void sendEmailWithDoc(String to, String subject, String bodyText, NodeRef docNodeRef) {    boolean executeAsync = true;    Map<String, Serializable> aParams = new HashMap<String, Serializable>();    aParams.put("to", to);    aParams.put("subject", subject);    aParams.put("body_text", bodyText);    Action a = serviceRegistry.getActionService().createAction("send-as-email", aParams);    if (a != null) {       serviceRegistry.getActionService().executeAction(a, docNodeRef, true, executeAsync);    } else {       throw new RuntimeException("Could not create send-as-email action");    }}

See also:

ActivityService

TheActivityService is responsible for generating activity feeds for each member of a Share site. The activities generated include such events as a document was added, a document was previewed, the wiki was updated.

What is an activity?

  • Activity represents an action that has taken place within a client interface (app/tool)
  • Activity is typically initiated by the app/tool/component/service on behalf of a user (it is not necessarily initiated by the underlying repository)
  • Activity is of a given/named type specified by the app/tool (for example document added)
  • Activity is performed at a particular point in time (post date)
  • Activity may have associated data dependent on type of activity
  • Activity may be performed within a given site/network context
  • Activity may be performed within a given app/tool context
  • Activity may be sensitive, that is, associated with data that is permission controlled, therefore, the activity itself may be permission controlled (can or can’t be read)
  • Activity may be rendered into one or more UI views (activity summary)

Activities may be raised by one or more Content Services applications. The posted activity must have a uniquely named activity type.

Examples of activity types include:

  • Added, updated, and deleted documents
  • Triggered on versioning
  • Includes changes to metadata (explicitly denoted in feed)
  • Does not include updates to tags
  • Uploaded and expanded ZIP
  • Added and deleted folders
  • Added and removed members (person joined/left site)
  • User role changes (change of user role for a site)
  • New comments (on any artifact in a site, including documents, blog entries, and so on.)
  • Workflow-generated activities (requires explicit posting via customizing workflow definition)
  • Added, updated, and deleted events (calendar entries)
  • Published, updated, and deleted wiki pages
  • Published, updated, and deleted blog entries
  • Blog entry published to external blog engine

Sample code showing posting an activity:

private void postActivityUpdated(NodeRef nodeRef) {    SiteInfo siteInfo = serviceRegistry.getSiteService().getSite(nodeRef);    String jsonActivityData = "";    try {        JSONWriter jsonWriter = new JSONStringer().object();        jsonWriter.key("title").value((Object)serviceRegistry.getNodeService().getProperty(                nodeRef, ContentModel.PROP_NAME).toString());        jsonWriter.key("nodeRef").value((Object)nodeRef.toString());        StringBuilder sb = new StringBuilder("document-details?nodeRef=");        sb.append(URLEncoder.encode(nodeRef.toString(), "UTF-8"));        jsonWriter.key("page").value((Object)sb.toString());        jsonActivityData = jsonWriter.endObject().toString();    }    catch (Exception e) {        throw new RuntimeException(e);    }    FileInfo fileInfo = serviceRegistry.getFileFolderService().getFileInfo(nodeRef);    serviceRegistry.getActivityService().postActivity("org.alfresco.documentlibrary.file-updated",         siteInfo == null ? null : siteInfo.getShortName(), siteInfo == null ? null : "documentLibrary",         jsonActivityData, null, fileInfo);}

See also:

AttributeService

This provides services for reading, writing, and querying global attributes. TheAttributeService is used to get and set global, arbitrary attributes. Attributes typically have a key and a value, where the key consists of three segments (known as a key set) and a value. Attributes are stored in the database so they persist over server restarts.

An example of use is for persisting system-wide JMX configuration properties in Alfresco Content Services. TheAttributeService class provides a Java interface for creating and managing attributes, including methods such as:

  • Serializable getAttribute(Serializable ... keys) - get an attribute using a list of unique keys
  • getAttributes(AttributeQueryCallback callback, Serializable ... keys) - Getting a collection of attributes
  • Serializable getAttribute(Serializable ... keys) - Getting a single attribute
  • setAttribute(Serializable value, Serializable ... keys) - Set attribute or create attribute if doesn’t exist
  • removeAttribute(Serializable ... keys) - Removing an attribute
  • removeAttributes(Serializable ... keys) - Removing a collection of attributes

Collections of Attributes can be processed on retrieval by implementing a callback handler object. The callback handler object’shandleAttribute method is invoked for each attribute retrieved.

Note. TheAttributeService is not what you would use to get the attributes (more correctly, “properties”) of a node. Use theNodeService class for that.

The following example shows how you could map a unique document identifier to an Alfresco node reference independently of nodes:

public class DocId2NodeRefMapper {/*** The Alfresco Service Registry that gives access to all public content services in Alfresco.*/private ServiceRegistry serviceRegistry;    public void setServiceRegistry(ServiceRegistry serviceRegistry) {        this.serviceRegistry = serviceRegistry;    }    public static final String ROOT_ATTR_PATH = "docId2NodeRefMappings";    public static final String DOC_ID_ATTR_NAME = "documentId";    public void mapDocId2NodeRef(String doc_id, NodeRef nodeRef) {         // Check if mapping to node ref is already set up        if (this.serviceRegistry.getAttributeService().exists(ROOT_ATTR_PATH, DOC_ID_ATTR_NAME, doc_id)) {             // Check to see if this node has already been registered            if (!this.serviceRegistry.getAttributeService().getAttribute(ROOT_ATTR_PATH, DOC_ID_ATTR_NAME, doc_id).equals(nodeRef)) {                throw new RuntimeException("Duplicate entry id:" + doc_id);            }        }        // Register node reference under document identifier        this.serviceRegistry.getAttributeService().setAttribute(nodeRef, ROOT_ATTR_PATH, DOC_ID_ATTR_NAME, doc_id);    }}

Notice how when you set the attribute value the value is the first parameter of thesetAttribute method.

See also:

AuditService

TheAuditService API provides facilities to query audit data. There are also methods to clear audit data, enable and disable auditing, and check auditing status.

/** * Returns content changes. */public ObjectList getContentChanges(Holder<String> changeLogToken, BigInteger maxItems) {    final ObjectListImpl result = new ObjectListImpl();    result.setObjects(new ArrayList<ObjectData>());    EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) {        @Override        public boolean handleAuditEntry(Long entryId, String user, long time, Map<String, Serializable> values) {            result.getObjects().addAll(createChangeEvents(time, values));            return super.handleAuditEntry(entryId, user, time, values);        }    };    Long from = null;    if ((changeLogToken != null) && (changeLogToken.getValue() != null)) {        try {            from = Long.parseLong(changeLogToken.getValue());        } catch (NumberFormatException e) {            throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken);        }    }    AuditQueryParameters params = new AuditQueryParameters();    params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);    params.setForward(true);    params.setFromId(from);    int maxResults = (maxItems == null ? 0 : maxItems.intValue());    maxResults = (maxResults < 1 ? 0 : maxResults + 1);    serviceRegistry.getAuditService().auditQuery(changeLogCollectingCallback, params, maxResults);    String newChangeLogToken = null;    if (maxResults > 0) {        if (result.getObjects().size() >= maxResults) {            StringBuilder clt = new StringBuilder();            newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString());            result.getObjects().remove(result.getObjects().size() - 1).getId();            result.setHasMoreItems(true);        } else {            result.setHasMoreItems(false);        }    }    if (changeLogToken != null) {        changeLogToken.setValue(newChangeLogToken);    }    return result;}

See also:

AuthenticationService

This service provides an API to allow authentication of users using various methods, such as username and password and authentication tickets. Authentication is required at various access points into the repository. For example web scripts, CMIS, FTP, WebDAV, and web clients represent access points where authentication needs to take place.

Authentication can be via a ticket, a username and password pair, or some other mechanism. TheAuthenticationService provides an API to:

  • Authenticate using a username and password
  • Authenticate using a ticket
  • Create, update and delete authentication information
  • Clear the current authentication
  • Invalidate a ticket
  • Get the username for who is currently authenticated
  • Get a ticket for subsequent re-authentication
  • Determine if the current user is “the system user”

Not all implementations will support creating, updating and deleting authentication information.

The authenticated username is used as the key to obtain other security information such as group membership, the details about the person, to record a user as the owner of an object. It is one of the identifiers against which permissions may be assigned.

TheAuthenticationService does not provide any details about a user other than authentication. It stores authentication information on the calling thread. Application developers should ensure that this information is cleared.

TheAuthenticationService brings together three components:

  • The authentication component
  • The authentication DAO
  • The ticket component

The authentication component supports authentication only. The authentication DAO provides an API to create, delete and update authentication information. The ticket component is responsible for managing and storing tickets that may be obtained after authentication and used in place of authentication.

// Get servicesAuthenticationService authService = (AuthenticationService)serviceRegistry.getAuthenticationService();PersonService personService = (PersonService)serviceRegistry.getPersonService();// Get current userNodeRef person = personService.getPerson(authService.getCurrentUserName());

See also:

AuthorityService

The service that encapsulates authorities granted to users. This service will refuse to create any user authorities. These should be managed using the AuthenticationService and PersonService. Methods that try to change alter users will throw an exception. A string key is used to identify the authority. These follow the contract defined in AuthorityType. If there are entities linked to these authorities this key should be used to find them, as userName is used to link user and person.

Authority is a general term to describe a group, user, or role. The AuthorityService provides an API to:

  • Add and delete authorities.
  • Get authorities.
  • Retrieve authority details such as short name.
/** * Search the root groups, those without a parent group. *  * @param paging Paging object with max number to return, and items to skip * @param sortBy What to sort on (authorityName, shortName or displayName) * @return The root groups (empty if there are no root groups) */public ScriptGroup[] searchRootGroupsInZone(        String displayNamePattern, String zone, ScriptPagingDetails paging, String sortBy) {    Set<String> authorities;        try {        authorities = serviceRegistry.getAuthorityService().findAuthorities(AuthorityType.GROUP,                null, true, displayNamePattern, zone);    } catch (UnknownAuthorityException e) {        authorities = Collections.emptySet();    }        return makeScriptGroups(authorities, paging, sortBy, serviceRegistry, this.getScope());}

Add an authority:

String knightGroup = serviceRegistry.getAuthorityService().createAuthority(AuthorityType.GROUP, "KNIGHTS");serviceRegistry.getAuthorityService().addAuthority(knightGroup, ADMIN_USER_NAME);

See also

CategoryService

Provides an API for creating and managing categories of nodes. Categories provide a system for organizing content. Unlike tags, which have no hierarchical structure, and which can be created and applied by anyone, categories are created by the Administrator, and are hierarchical in nature.

For example, You might have a Europe category, and then sub-categories such as France, Germany, Spain, and so on. The top Category in the hierarchical structure is known as the Root Category. TheCategoryService API provides methods to perform actions such as the following:

  • Create a Category
  • Create a root Category
  • Delete a Category
  • Create a Classification (a grouping of Categories)
  • Delete a Classification
  • Get most popular Categories
// To create a root category:NodeRef newRootCat = serviceRegistry.getCategoryService().createRootCategory(spacesStore,         ContentModel.ASPECT_GEN_CLASSIFIABLE, "newRootCat");// To create a categoryNodeRef newCategory = serviceRegistry.getCategoryService().createCategory(newRootCat, "newCategory");

See also:

CheckOutCheckInService

Service to provide document locking. If a document is locked, other users cannot change its content, until it is unlocked.

Check out locks the item and creates a working copy that can be edited. The locked item can be viewed by others, but not changed. When the item is checked in, the working copy replaces the original item and removes the lock.

The followingCheckOutCheckInService methods are provided:

  • Check out a node
  • Check in a node
  • Check if a node is a working copy
  • Check if a node is locked (checked out)
  • Cancel a check out for a given working copy
  • Get a working copy
  • Get the original checked out node
CheckOutCheckInService checkOutCheckInService = serviceRegistry.getCheckOutCheckInService();NodeRef checkedOutCopy = checkOutCheckInService.checkout(nodeRef);

ContentService

TheContentService provides an API for setting, accessing, and transforming content. You may want to read the content associated with a node, or transform the content from one format to another, for example from.ppt to.pdf. Methods provided by the API includes functionality to:

  • Get obtainable transformers (to convert one mimetype to another)
  • Get a suitable reader for a content type. The returnedContentReader will have agetContent method to actually read the content to a specified file.
  • Get a suitable writer for a content type. The returnedContentWriter will have aputContent method to write the content to a specified file.
  • Transform content from one mimetype to another.
  • Get a transformer suitable for transforming images.
  • Utility methods (for example to check size of content and free space in the content store).

Read plain text associated with a contentNodeRef:

ContentReader reader = serviceRegistry.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT);

Reading binary content of aNodeRef:

ContentReader reader = serviceRegistry.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT);InputStream originalInputStream = reader.getContentInputStream();ByteArrayOutputStream outputStream = new ByteArrayOutputStream();final int BUF_SIZE = 1 << 8; //1KiB bufferbyte[] buffer = new byte[BUF_SIZE];int bytesRead = -1;while((bytesRead = originalInputStream.read(buffer)) > -1) {    outputStream.write(buffer, 0, bytesRead);}originalInputStream.close();byte[] binaryData = outputStream.toByteArray();

Writing data to a node’s content:

ContentWriter writer = serviceRegistry.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true);writer.putContent(new ByteArrayInputStream(content));

Writing a file’s data to a node’s content:

ContentWriter writer = serviceRegistry.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true);writer.setLocale(CONTENT_LOCALE);File file = new File("c:/temp/images/BigCheese1.bmp");writer.setMimetype("image/bmp");writer.putContent(file);

Transforming a PPT to PDF (also works for other file formats):

ContentReader srcReader = serviceRegistry.getContentService().getReader(srcNodeRef, ContentModel.PROP_CONTENT);ContentWriter pdfWriter = serviceRegistry.getContentService().getWriter(pdfNodeRef, ContentModel.PROP_CONTENT, true);ContentTransformer pptToPdfTransformer = serviceRegistry.getContentService().getTransformer(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_PDF);pptToPdfTransformer.transform(pptReader, pdfWriter);

Example of creating a new node and setting provided text content:

/*** Creates a new content node setting the content provided.** @param  parent   the parent node reference* @param  name     the name of the newly created content object* @param  text     the content text to be set on the newly created node* @return NodeRef  node reference to the newly created content node*/private NodeRef createContentNode(NodeRef parent, String name, String text) {    // Create a map to contain the values of the properties of the node    Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);    props.put(ContentModel.PROP_NAME, name);        // use the node service to create a new node    NodeRef node = serviceRegistry.getNodeService().createNode(parent, ContentModel.ASSOC_CONTAINS,         QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), ContentModel.TYPE_CONTENT, props).getChildRef();        // Use the content service to set the content onto the newly created node    ContentWriter writer = serviceRegistry.getContentService().getWriter(node, ContentModel.PROP_CONTENT, true);    writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);    writer.setEncoding("UTF-8");    writer.putContent(text);        // Return a node reference to the newly created node    return node;}

CopyService

This service provides methods to copy nodes within and across folders. It also provides support to update the state of a node, with that of another node, within and across folders.

The service is very useful for managing the copy of nodes. When copying container nodes (folders) you also have the option to copy child nodes. Operations provided by the service include:

  • Copy a node, along with (optionally) its children.
  • Copy and rename a node.
  • Get the copies of a specified node (with paged results).
  • Check if the name of a top-level node will be changed during copy, due to policies in place.
  • Given the copied node, obtain the original node.

The following code is from the Alfresco source code implementation of thecopy action that shows usage of theCopyService service:

public class CopyActionExecuter extends ActionExecuterAbstractBase {    public static final String ERR_OVERWRITE = "Unable to overwrite copy because more than one have been found.";        public static final String NAME = "copy";    public static final String PARAM_DESTINATION_FOLDER = "destination-folder";    public static final String PARAM_DEEP_COPY = "deep-copy";    public static final String PARAM_OVERWRITE_COPY = "overwrite-copy";        private CopyService copyService;    private NodeService nodeService;    private CheckOutCheckInService checkOutCheckInService;    public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; }    public void setCopyService(CopyService copyService) { this.copyService = copyService; }    public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) { this.checkOutCheckInService = checkOutCheckInService; }    @Override    protected void addParameterDefinitions(List<ParameterDefinition> paramList) {        paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_DESTINATION_FOLDER)));        paramList.add(new ParameterDefinitionImpl(PARAM_DEEP_COPY, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_DEEP_COPY)));        paramList.add(new ParameterDefinitionImpl(PARAM_OVERWRITE_COPY, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_OVERWRITE_COPY)));    }    @Override    public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) {        if (!nodeService.exists(actionedUponNodeRef)) {            return;        }        NodeRef destinationParent = (NodeRef) ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER);                Set<QName> destinationAspects = nodeService.getAspects(destinationParent);        if (destinationAspects.contains(ContentModel.ASPECT_PENDING_DELETE)) {            return;        }                boolean deepCopy = false;        Boolean deepCopyValue = (Boolean)ruleAction.getParameterValue(PARAM_DEEP_COPY);        if (deepCopyValue != null) {            deepCopy = deepCopyValue.booleanValue();        }                boolean overwrite = true;        Boolean overwriteValue = (Boolean)ruleAction.getParameterValue(PARAM_OVERWRITE_COPY);        if (overwriteValue != null) {            overwrite = overwriteValue.booleanValue();        }            // Since we are overwriting we need to figure out whether the destination node exists        NodeRef copyNodeRef = null;        if (overwrite == true) {            // Try and find copies of the actioned upon node reference.            // Include the parent folder because that's where the copy will be if this action            // had done the first copy.            PagingResults<CopyInfo> copies = copyService.getCopies(actionedUponNodeRef, destinationParent,            new PagingRequest(1000));                        for (CopyInfo copyInfo : copies.getPage()) {                NodeRef copy = copyInfo.getNodeRef();                // We know that it is in the destination parent, but avoid working copies                if (checkOutCheckInService.isWorkingCopy(copy)) {                    continue;                }                                if (copyNodeRef == null) {                    copyNodeRef = copy;                } else {                    throw new RuleServiceException(ERR_OVERWRITE);                }            }        }            if (copyNodeRef != null) {        // Overwrite the state of the destination node ref with the actioned upon node state            this.copyService.copy(actionedUponNodeRef, copyNodeRef);        } else {            ChildAssociationRef originalAssoc = nodeService.getPrimaryParent(actionedUponNodeRef);            // Create a new copy of the node            this.copyService.copyAndRename(actionedUponNodeRef, destinationParent, originalAssoc.getTypeQName(),                     originalAssoc.getQName(), deepCopy);        }    }}

DictionaryService

This service gives you access to theContent Model Dictionary. This dictionary provides access to content model meta-data, such as Type and Aspect descriptions. Content model metadata is organized into models where each model is given a qualified name. This means that it is safe to develop independent models and bring them together into the same Repository without name clashes (as long their namespace is different).

TheDictionaryService provides access to the entire content model meta-model. The content model meta-model contains information of Types, DataTypes, Properties, Aspects, Associations and Constraints. Operations supported include:

  • Getting DataTypes, Types, Associations, Properties, Constraints, Classes from a Content Model.
  • Check if a class (i.e. type or aspect) is a sub-class.
  • Get sub-types and sub-aspects.

Get all content model types and put in a map keyed on type name prefix string:

Collection<QName> types = serviceRegistry.getDictionaryService().getAllTypes();Map<String, String> result = new LinkedHashMap<String, String>(types.size());for (QName type : types) {    TypeDefinition typeDef = serviceRegistry.getDictionaryService().getType(type);    if (typeDef != null && typeDef.getTitle(serviceRegistry.getDictionaryService()) != null) {        result.put(type.toPrefixString(), typeDef.getTitle(dictionaryService));    }}

Get a content model type and check if it has an aspect:

TypeDefinition typeDef = serviceRegistry.getDictionaryService().getType(typeQName);if (typeDef != null) {    boolean hasAspect = typeDef.getDefaultAspectNames().contains("cm:titled");}

Check if a node is of a certain type or sub-type, in this casecm:content:

QName nodeType = serviceRegistry.getNodeService().getType(nodeRef);if (serviceRegistry.getDictionaryService().isSubClass(nodeType, ContentModel.TYPE_CONTENT)) {    // This is a file...}

FileFolderService

Provides methods specific to manipulating files and folders. This service provides a simple way of accessing simple trees of files and folders in the content repository. TheFileFolderService provides methods for dealing with files and folders. This class is an abstraction of theNodeService class, which you should look at if you want more control when creating folder and file nodes.

With theFileFolderService class the following type of operations are available:

  • Create a file or folder
  • Copy a file or folder
  • Move a file or folder
  • Delete a file or folder
  • Get Readers and Writers for a file
  • List files and folders (with paged results)

The methods typically work with aNodeRef for the node that represents the target file or folder.

The following example uses theFileFolderService to create a folder and then a file in this new folder. The example code is executed inside aWeb Script so it will automatically be part of a transaction using theRetryingTransactionHelper, same thing if the code was executed from aRepository Action.

import org.alfresco.model.ContentModel;import org.alfresco.repo.content.MimetypeMap;import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;import org.alfresco.service.ServiceRegistry;import org.alfresco.service.cmr.model.FileExistsException;import org.alfresco.service.cmr.model.FileInfo;import org.alfresco.service.cmr.repository.ContentWriter;import org.alfresco.service.cmr.repository.NodeRef;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.extensions.webscripts.Cache;import org.springframework.extensions.webscripts.DeclarativeWebScript;import org.springframework.extensions.webscripts.Status;import org.springframework.extensions.webscripts.WebScriptRequest;import java.util.HashMap;import java.util.Map;/** * A Web Script that uses the FileFolderService to create a folder and a file. * * @author martin.bergljung@alfresco.com */public class FileFolderServiceTestWebScript extends DeclarativeWebScript {    private static Log logger = LogFactory.getLog(FileFolderServiceTestWebScript.class);    /**     * The Alfresco Service Registry that gives access to all public content services in Alfresco.     */    private ServiceRegistry serviceRegistry;    public void setServiceRegistry(ServiceRegistry serviceRegistry) {        this.serviceRegistry = serviceRegistry;    }    protected Map<String, Object> executeImpl(            WebScriptRequest req, Status status, Cache cache) {        Map<String, Object> model = new HashMap<String, Object>();        String message = "Your 'FileFolderServiceTestWebScript' Web Script was called ";        FileInfo newFolderInfo = null;        try {            newFolderInfo = createFolder("Some Folder");            message += "and a folder was created: " + newFolderInfo;        } catch (FileExistsException fee) {            message += "and there was a problem creating a folder: " + fee.getMessage();        }        if (newFolderInfo != null) {            FileInfo newFileInfo = null;            try {                newFileInfo = createFile(newFolderInfo,"some.txt", "Some text content...");                message += ", a text file was then created in this folder: " + newFileInfo;            } catch (FileExistsException fee) {                message += ", there was a problem creating a file in the new folder: " + fee.getMessage();            }        }        logger.info(message);        model.put("message", message);        return model;    }    /**     * Create a folder under the /Company Home folder.     *     * @param folderName the name of the folder     * @return a FileInfo object with data about the new folder, such as NodeRef     */    private FileInfo createFolder(String folderName) throws FileExistsException {        // Get a NodeRef for /Company Home folder        NodeRef parentFolderNodeRef = serviceRegistry.getNodeLocatorService().getNode(                CompanyHomeNodeLocator.NAME, null, null);        // Create the folder under /Company Home        FileInfo folderInfo = serviceRegistry.getFileFolderService().create(                parentFolderNodeRef, folderName, ContentModel.TYPE_FOLDER);        return folderInfo;    }    /**     * Create a file under the passed in folder.     *     * @param folderInfo the folder that the file should be created in     * @param filename the name of the file     * @param fileTxt the content of the file     * @return a FileInfo object with data about the new file, such as NodeRef     */    private FileInfo createFile(FileInfo folderInfo, String filename, String fileTxt) throws FileExistsException {        // Create the file under passed in folder, the file will be empty to start with        FileInfo fileInfo = serviceRegistry.getFileFolderService().create(                folderInfo.getNodeRef(), filename, ContentModel.TYPE_CONTENT);        // Get the NodeRef for the new file from the FileInfo object        NodeRef newFileNodeRef = fileInfo.getNodeRef();        // Add some content to the file        ContentWriter writer = serviceRegistry.getFileFolderService().getWriter(newFileNodeRef);        writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);        writer.setEncoding("UTF-8");        writer.putContent(fileTxt);        return fileInfo;    }}

We use theServiceRegistry to get to theFileFolderService. TheServiceRegistry bean is injected into the Web Script controller bean as follows:

<bean   parent="webscript"><property name="serviceRegistry"><ref bean="ServiceRegistry" /></property></bean>

Note how we catch theFileExistsException to deal with the situations when the folder or file already exists. This is a runtime exception so we are not forced to deal with it, but it’s good practice to catch it and display a nice message to the end user.

If we complete the Web Script with a descriptor and template as follows:

/extension/templates/webscripts/alfresco/tutorials/filefolderservicetest.get.desc.xml:

<webscript>    <shortname>FileFolderService Test Sample Webscript</shortname>    <description>Uses the FileFolderService to create a folder and a file</description>    <url>/sample/filefolderservicetest</url>    <authentication>user</authentication>    <format default="html"></format>    <lifecycle>sample</lifecycle>    </webscript>

/extension/templates/webscripts/alfresco/tutorials/filefolderservicetest.get.html.ftl:

Message: '${message}'

Then, the first time we execute the Web Script (http://localhost:8080/alfresco/s/sample/filefolderservicetest) we will get a response looking something like this:

Message: 'Your 'FileFolderServiceTestWebScript' Web Script was called and a folder was created: FileInfo[name=Some Folder, isFolder=true, nodeRef=workspace://SpacesStore/91b0932a-5056-4607-a1bd-849ec655d16e], a text file was then created in this folder: FileInfo[name=some.txt, isFolder=false, nodeRef=workspace://SpacesStore/5b17ba0a-b0b5-4df1-bd37-91098cac7263]'

If we now run the Web Script again, when the folder and file exist, the following response is returned:

Message: 'Your 'FileFolderServiceTestWebScript' Web Script was called and there was a problem creating a folder: 00270021 File or folder Some Folder already exists'

JobLockService

This service ensures that a scheduled job can only run on one node of a cluster at a time. A scheduled job could be, for example, an Activities feed job that generates an email to send to everyone every night, or a content cleaner job that cleans up orphaned content.

TheJobLockService is used to provide a locking service at the job level, rather than the node level. It’s for example used indirectly via theAbstractScheduledLockedJobQuarzJobBean.

For an example of using theJobLockService see theContent Store Cleaner code on GitHub.

See also theScheduled Jobs extension point

LockService

A node-level locking service, used by theCheckOutCheckIn service. Does not create a working copy. If you need a node-level locking system, then theLockService can provide this. Functionality provided by the service includes:

  • Checking for a lock on a node
  • Obtaining lock information
  • Locking and unlocking a node
  • Suspend and enable locks

Example checking if a node is locked:

/*** Return whether a Node is currently locked* @param node             The Node wrapper to test against* @param lockService      The LockService to use* @return whether a Node is currently locked*/public static Boolean isNodeLocked(Node node,LockService lockService) {  Boolean locked = Boolean.FALSE;    if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) {      LockStatus lockStatus = serviceRegistry.getLockService().getLockStatus(node.getNodeRef());      if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) {          locked = Boolean.TRUE;      }  }  return locked;}

MessageService

Provides methods to access the locale of the current thread and to get localised strings. These strings may be loaded from resource bundles deployed in the repository. TheMessageService provides functionality around Internationalization (i18n). It provides facilities to:

  • Get a message based on a key from a localized properties file
  • Get and set the locale
  • Register and unregister resource bundles

All user displayed strings that originate in the repository should be externalised into resource bundles to ensure that the repository is fully localisable. Examples of strings requiring extraction include:

  • Descriptive display labels used by a client
  • Error messages

Extracted strings should be gathered into resource bundles by functional area. This enables functional areas to remain distinct within the repository. The base bundle should be named by functional area and have the.properties extension. All base bundles should be in US English. If a message needs to be parameterised the JavaMessageFormatter style should be used.

The keys used in the resource bundles should be scoped by functional area to avoid clashes (this is important since at runtime the contents of the various resource bundles is combined, any names clashes will result in message values being overwritten).

A resource bundle can be placed anywhere in the source tree, but in general repository resource bundles should be placed in thealfresco.messages package.

Example resource bundle contents:

# User displayed string for the rule service functional arearuleservice.error=There has been an error executing rule {0}.ruleservice.confimation_all=All rules have been executed.

Before a resource bundle can be used by the repository it must be registered. Suitable methods are provided by the service to support this. And more commonly theorg.alfresco.i18n.ResourceBundleBootstrapComponent class can be used as a Spring bean to register resource bundles.

The following example uses aWeb Script to test registered resource bundles as follows:

import org.alfresco.service.ServiceRegistry;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.extensions.webscripts.Cache;import org.springframework.extensions.webscripts.DeclarativeWebScript;import org.springframework.extensions.webscripts.Status;import org.springframework.extensions.webscripts.WebScriptRequest;import java.util.HashMap;import java.util.Locale;import java.util.Map;/*** A Web Script that can be used to test the MessageService class.** @author martin.bergljung@alfresco.com*/public class MessageServiceTestWebscript extends DeclarativeWebScript {  private static Log logger = LogFactory.getLog(MessageServiceTestWebscript.class); /**  * The Alfresco Service Registry that gives access to all public content services in Alfresco.  */  private ServiceRegistry serviceRegistry;  public void setServiceRegistry(ServiceRegistry serviceRegistry) {      this.serviceRegistry = serviceRegistry;  }  protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) {      String key = req.getParameter("key");      String language = req.getParameter("language");      Locale locale = Locale.forLanguageTag(language);      Map<String, Object> model = new HashMap<String, Object>();      String message = "Your 'MessageServiceTestWebscript' Web Script was called: <br/>";      message += "Locale: " + locale.getDisplayName() + "<br/>";      message += "Translation of " + key + ": " + this.serviceRegistry.getMessageService().getMessage(key, locale);      logger.info(message);      model.put("message", message);      return model;  }}

This Web Script is called with two parameters, one specifies the resource string we want (i.e.key) and one specifies the language we want the resource string text in (i.e.language).

We then use theServiceRegistry to get to theMessageService, and then thegetMessage method is called to get the requested message in correct locale.

TheServiceRegistry bean is injected into the Web Script controller bean as follows:

<bean       parent="webscript">        <property name="serviceRegistry">        <ref bean="ServiceRegistry" />    </property></bean>

If we complete the Web Script with a descriptor and template as follows:

/extension/templates/webscripts/alfresco/tutorials/messageservicetest.get.desc.xml:

<webscript>    <shortname>MessageService Test Sample Webscript</shortname>    <description>Get a message for a specific key and language, uses the MessageService</description>    <url>/sample/messageservicetest?key={key}&amp;language={language}</url>    <authentication>user</authentication>    <format default="html"></format>    <lifecycle>sample</lifecycle>    </webscript>

/extension/templates/webscripts/alfresco/tutorials/messageservicetest.get.html.ftl:

${message}

And add two resource files as follows:

platform-jar/src/main/resources/alfresco/module/platform-jar/messages/test-messages.properties:

alfresco.tutorial.hello=Hello

platform-jar/src/main/resources/alfresco/module/platform-jar/messages/test-messages_sv.properties:

alfresco.tutorial.hello=Hej

These two resource files can be loaded by defining the following Spring bean:

<bean         >    <property name="resourceBundles">        <list>            <value>alfresco.module.${project.artifactId}.messages.test-messages</value>        </list>    </property></bean>

Then we can call the Web Script with the following URL:

http://localhost:8080/alfresco/s/sample/messageservicetest?key=alfresco.tutorial.hello&language=en

The response in the browser will look something like this:

Your 'MessageServiceTestWebscript' Web Script was called:Locale: EnglishTranslation of alfresco.tutorial.hello: Hello

If we call it with the other locale (sv) the response looks like this (http://localhost:8080/alfresco/s/sample/messageservicetest?key=alfresco.tutorial.hello&language=sv):

Your 'MessageServiceTestWebscript' Web Script was called:Locale: SwedishTranslation of alfresco.tutorial.hello: Hej

MimetypeService

Provides support related to content mimetype. For example, provides methods to retrieve the extension for the specified mimetype. Content Services supports numerous mimetypes out-of-the-box. However, it is also possible to add your own custom mimetypes. TheMimetypeService provides an API for managing mimetypes. For example, you can obtain a list of current mimetypes, mimetype extensions, and guess mimetypes using a specified file and content reader.

Using the mimetype service when writing content:

ContentWriter contentWriter = serviceRegistry.getContentService().getWriter(node, ContentModel.PROP_CONTENT, true);contentWriter.setMimetype(serviceRegistry.getMimetypeService().guessMimetype(filename));contentWriter.putContent(field.getInputStream());

See also theMimetype platform extension point

ModuleService

A service to control and provide information about the currently-installedAlfresco Module Packages (AMPs).

A module is an extension to Content Services that is developed with a particular project structure and packaging. Modules can be registered and loaded as part of the boot process. In Share Admin Tools, you canview the currently installed Modules. TheModuleService provides functionality to programmatically start up and shut down modules, and get module information.

Get all modules and shut down:

List<ModuleDetails> modules = serviceRegistry.getModuleService().getAllModules();loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size()));for (ModuleDetails module : modules) {    Map<String, ModuleComponent> components = getComponents(module.getId());    for (ModuleComponent component : components.values()) {        component.shutdown();    }}

NamespaceService

Provides access to content model namespaces and their definition of namespace URIs and Prefixes. TheNamespaceService has constants defined for the major namespaces used by internal Alfresco content models, including the prefixes for those.

Content Services namespaces start withhttp://www.alfresco.org. The top-level namespace sub-divisions are:

  • model - identify a data model
  • view - identify a view of content held in the repository
  • ws - identify a Web Service definition
  • test - identify a test definition

Each namespace typically ends with its version number.

Here is a list of some of the out-of-box content models that are good to know about:

NamespaceCommon PrefixDescription
http://www.alfresco.orgalfGeneral Namespace
http://www.alfresco.org/model/dictionary/1.0dData Dictionary model
http://www.alfresco.org/model/system/1.0sysRepository system model
http://www.alfresco.org/model/content/1.0cmContent Domain model
http://www.alfresco.org/model/application/1.0appApplication model
http://www.alfresco.org/model/bpm/1.0bpmBusiness Process Model
http://www.alfresco.org/model/site/1.0stSite Model
http://www.alfresco.org/model/forum/1.0fmForum Model
http://www.alfresco.org/model/user/1.0usrUser model (in repository.jar)
http://www.alfresco.org/view/repository/1.0viewImport / Export View
http://www.alfresco.org/model/action/1.0actAction service model
http://www.alfresco.org/model/rule/1.0ruleRule service model
http://www.alfresco.org/ws/service/authentication/1.0authAuthentication Web Service
http://www.alfresco.org/ws/service/repository/1.0repRepository Web Service
http://www.alfresco.org/ws/service/content/1.0contentContent Web Service
http://www.alfresco.org/ws/service/authoring/1.0authorAuthoring Web Service
http://www.alfresco.org/ws/service/classification/1.0clsClassification Web Service
http://www.alfresco.org/ws/cml/1.0cmlContent Manipulation Language
http://www.alfresco.org/ws/model/content/1.0cmWeb Service Content Domain Model
http://www.alfresco.org/model/workflow/1.0wfWorkflow Model (link is to the simple workflow model, not generally extended)

It’s common to use theNamespaceService to get to prefixes for content models, such as in this example:

String companyHomePath = serviceRegistry.getNodeService().getPath(companyHome)               .toPrefixString(serviceRegistry.getNamespaceService());

This code would result incompanyHomePath being set to/app:company_home.

Another example usage is the following code that uses theNamespaceService when aQName is created:

String name = "aName";QName aQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name));

This code would result inaQName being set to{http://www.alfresco.org/model/content/1.0}aName.

Managing multi-value properties

By default, a property supports a single value, but this may be changed to support multiple values via themultiple element in the content model definition. Multiple values are rendered as lists in the various Alfresco APIs. Ad:text property withmultiple set totrue will allow you to store multiple string values for the property and the document, and search for them using advanced search capabilities.

The following content model shows an example of a text property (acme:campaign) defined as having multiple values.The possible values are restricted by the values defined in theacme:campaignList constraint. If you wanted to allow any campaign name, then remove this constraint definition.

<constraints>    <constraint name="acme:campaignList" type="LIST">        <parameter name="allowedValues">            <list>                <value>Campaign A</value>                <value>Campaign B</value>                <value>Campaign C</value>                <value>Campaign D</value>                <value>Campaign E</value>            </list>        </parameter>    </constraint></constraints><types><type name="acme:marketingDoc">    <title>Acme Marketing Document</title>    <parent>acme:doc</parent>    <properties>        <property name="acme:campaign">            <type>d:text</type>            <multiple>true</multiple>            <constraints>                <constraint ref="acme:campaignList" />            </constraints>        </property>    </properties></type></types>

Note: Thedefault element in the content model can be used to set a single value, it’s not designed to set multiple values. However, you can achieve this by implementing a rule/behavior, which can set the multiple values at the time of node creation.

The data structure to use for multiple values in Java is a list:

ArrayList<Serializable> values = new ArrayList<Serializable>();values.add("Campaign A");values.add("Campaign D");nodeService.setProperty(nodeRef, "cm:campaign", values);

The data structure to use for multiple values in JavaScript is an array:

document.properties["acme:campaign"] = ["Campaign A","Campaign D"];

Note: As long as you are using a simple text data type for the property value, you do not need to use associations.An association will become necessary when you want to store additional data relating to the document, and want toavoid data redundancy. In that case you might want to create individual nodes per document (with a docID + other metadata)and link documents to those nodes via associations.
Keep in mind that searching via associations is not as easyas simple metadata search. You can search for the document via the docID, and then use the association(in a second step) to navigate to all documents associated with that particular metadata.

NodeService

Provides an API for managing nodes, such as folders and files. Nodes are the fundamental data structure in Content Services. All content that is stored is represented by a node data structure, which contains content metadata that is persisted in a database (such as PostgreSQL). The content referenced by the node is stored as a*.bin file in a content store (such as the file system, S3, encrypted or other content store).

Every node in the system is referenced by aNodeRef, which is made up of the content store protocol, the content store name, and the Universal Unique Identifier (UUID) of the content, for example:workspace://SpacesStore/ccb906ba-a768-4ccb-8b26-515119e1efdc. Generally nodes are of two main types, a file node (cm:content), or a folder node (cm:folder). Folders can contain child nodes. Note that each content store will have a root node, and all other nodes in the store will be children of the root node.

TheNodeService provides an extensive API for managing nodes. Functionality includes:

  • Adding aspects, children, properties, associations
  • Getting aspects, children, properties, associations
  • Removing aspects, children, properties, associations
  • Creating and deleting stores
  • Creating and deleting nodes
  • Checking for existence of a node
  • Get available content stores
  • Moving nodes

TheNodeService makes extensive use ofNodeRefs to reference the node of interest.

Note that for creating folders and files you should also have a look atFileFolderService as it is an abstraction of theNodeService service that makes it a bit easier to work with folders and files with the cost of being less flexible.

Creating a file node:

NodeRef parentFolderNodeRef = serviceRegistry.getNodeLocatorService().getNode(CompanyHomeNodeLocator.NAME, null, null);// Create file node metadataQName 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);ChildAssociationRef parentChildAssocRef = serviceRegistry.getNodeService().createNode(        parentFolderNodeRef, associationType, associationQName, ContractType.QNAME, nodeProperties);NodeRef newFileNodeRef = parentChildAssocRef.getChildRef();// Set text content for file nodeboolean updateContentPropertyAutomatically = true;ContentWriter writer = serviceRegistry.getContentService().getWriter(        newFileNodeRef, ContentModel.PROP_CONTENT, updateContentPropertyAutomatically);writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);writer.setEncoding("UTF-8");writer.putContent(someText);

Creating a folder node:

private void createFolder(NodeRef rootRef) {    String folderName = "SampleFolder";    NodeRef parentFolderNodeRef = rootRef;    // Create folder node     QName associationType = ContentModel.ASSOC_CONTAINS;    QName associationQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,            QName.createValidLocalName(folderName));    QName nodeType = ContentModel.TYPE_FOLDER;    Map<QName, Serializable> nodeProperties = new HashMap<QName, Serializable>();    nodeProperties.put(ContentModel.PROP_NAME, folderName);    ChildAssociationRef parentChildAssocRef = serviceRegistry.getNodeService().createNode(            parentFolderNodeRef, associationType, associationQName, nodeType, nodeProperties);    NodeRef newFolderNodeRef = parentChildAssocRef.getChildRef();}

Add an aspect to a node:

NodeRef someNodeRef = ...Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();aspectProperties.put(ContentModel.PROP_TITLE, "A title");aspectProperties.put(ContentModel.PROP_DESCRIPTION, "A description");serviceRegistry.getNodeService().addAspect(someNodeRef, ContentModel.ASPECT_TITLED, aspectProperties);

Getting aNodeRef from the node path:

StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");ResultSet rs = serviceRegistry.getSearchService().query(storeRef, SearchService.LANGUAGE_LUCENE,         "PATH:\"/app:company_home/app:user_homes/sys:boris/cm:mypics\"");NodeRef companyHomeNodeRef = null;try {    if (rs.length() == 0) {        throw new AlfrescoRuntimeException("Didn't find Company Home");    }    companyHomeNodeRef = rs.getNodeRef(0);} finally {    rs.close();}

Getting a file name from aNodeRef:

String fileName = (String) serviceRegistry.getNodeService().getProperty(nodeRef, ContentModel.PROP_NAME);

Reading a node property. The property may come from an aspect or not. You will probably want to cast to the appropriate type:

QName PROP_QNAME_MY_PROPERTY = QName.createQName("custom.model", "myProperty");Object value = serviceRegistry.getNodeService().getProperty(nodeRef, PROP_QNAME_MY_PROPERTY);

Updating a node property. The property may come from an aspect or not:

QName PROP_QNAME_MY_PROPERTY = QName.createQName("custom.model", "myProperty");serviceRegistry.getNodeService().setProperty(nodeRef, PROP_QNAME_MY_PROPERTY, value);

Getting the parent of aNodeRef:

ChildAssociationRef childAssociationRef = serviceRegistry.getNodeService().getPrimaryParent(nodeRef);NodeRef parentFolderNodeRef = childAssociationRef.getParentRef();

Adding an aspect to a node. Supposing the “MyAspect” aspect defines a “myProperty” property in the “custom.model” namespace:

QName CUSTOM_ASPECT_QNAME = QName.createQName("custom.model", "MyAspect");QName PROP_QNAME_MY_PROPERTY = QName.createQName("custom.model", "myProperty");Map<QName,Serializable> aspectValues = new HashMap<QName,Serializable>();aspectValues.put(PROP_QNAME_MY_PROPERTY, value);serviceRegistry.getNodeService().addAspect(nodeRef, CUSTOM_ASPECT_QNAME, aspectValues);

Checking whether a node has a given aspect:

QName CUSTOM_ASPECT_QNAME = QName.createQName("custom.model", "MyAspect");boolean hasAspect = serviceRegistry.getNodeService().hasAspect(node, CUSTOM_ASPECT_QNAME);

Looping through children of aNodeRef:

List<ChildAssociationRef> children = serviceRegistry.getNodeService().getChildAssocs(companyHome);for (ChildAssociationRef childAssoc : children) {    NodeRef childNodeRef = childAssoc.getChildRef();    // Use childNodeRef here.}

Creating a parent-child association between two existingNodeRef:

QName PROP_QNAME_MY_CHILD_ASSOCIATION = QName.createQName("custom.model", "myChildAssociation");serviceRegistry.getNodeService().addChild(parentNodeRef, childNodeRef, PROP_QNAME_MY_CHILD_ASSOCIATION, PROP_QNAME_MY_CHILD_ASSOCIATION);

Creating a peer-2-peer association between two existingNodeRef:

QName PROP_QNAME_MY_ASSOCIATION = QName.createQName("custom.model", "myAssociation");serviceRegistry.getNodeService().createAssociation(sourceNodeRef, targetNodeRef, PROP_QNAME_MY_ASSOCIATION);

Setting the content type of a node:

QName PROP_QNAME_MY_TYPE = QName.createQName("custom.model", "myType");serviceRegistry.getNodeService().setType(finalOriginal, MY_TYPE);

Getting the MIME type of a node:

ContentData contentData = (ContentData) serviceRegistry.getNodeService().getProperty(nodeRef, ContentModel.PROP_CONTENT);String originalMimeType = contentData.getMimetype();

Adding a category to a node:

ArrayList<NodeRef> categories = new ArrayList<NodeRef>(1);categories.add(categoryNode);if (!serviceRegistry.getNodeService().hasAspect(targetNode, ContentModel.ASPECT_GEN_CLASSIFIABLE) {    HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();    props.put(ContentModel.PROP_CATEGORIES, categories);    serviceRegistry.getNodeService().addAspect(targetNode, ContentModel.ASPECT_GEN_CLASSIFIABLE, props);} else {    serviceRegistry.getNodeService().setProperty(targetNode, ContentModel.PROP_CATEGORIES, categories);}

Getting the categories of a node:

List<NodeRef> categories = (List<NodeRef>) serviceRegistry.getNodeService().getProperty(nodeRef, ContentModel.PROP_CATEGORIES);

Deleting a node for real (not recycle bin):

serviceRegistry.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);serviceRegistry.getNodeService().deleteNode(nodeRef);

See alsoCustom Content Store platform extension point

Setting content for node

Since Content Services 6.2 thecm:content property cannot be set using theNodeService. Any code that usesNodeService methodssetProperties,addProperties,createNode,addAspect, orsetProperty to set the content property need to be refactored or designed to use theContentWriter service instead. TheContentWriter can be obtained by using theContentService.

There are ways to get around this security check: Add full class names as a comma separated list to a global property:

contentPropertyRestrictions.whitelist=org.alfresco.Class1,org.alfresco.Class2

Disable theContentPropertyRestrictionInterceptor fully by setting a global property:

contentPropertyRestrictions.enabled=false

However, it’s better to refactor the code by using theContentWriter.

Deleting nodes

Since Content Services 4.1.1 thealf_node.node_deleted database column has been replaced by a system type (sys:deleted) and an aspect (sys:pendingDelete). While thesys:deleted type will never be visible to client code, thesys:pendingDelete aspect will be. Any custom code that attempts to modify behaviour during node deletion may need to be adjusted.

Changes made in 4.1.1 introduced comprehensive behaviour policy callbacks (i.e. event handlers) for all associations during node deletion. The followingnode policies are available for node deletion:

  • BeforeDeleteNodePolicy
  • BeforeArchiveNodePolicy
  • OnDeleteNodePolicy
  • BeforeDeleteChildAssociationPolicy
  • OnDeleteChildAssociationPolicy
  • BeforeDeleteAssociationPolicy
  • OnDeleteAssociationPolicy

The association (peer and child) policies are now fired reliably for all associations within the node hierarchy being deleted. For examples of their usage, see:org.alfresco.repo.model.ml.MultilingualDocumentAspect.

OnceNodeService.deleteNode is called:

  • It is impossible to add or remove associations to or from any node in the hierarchy being deleted. This includes attempted changes from any source including changes attempted by custom code reacting to before- or on-delete callbacks.
  • All nodes in the hierarchy will temporarily have thesys:pendingDelete aspect applied. Custom code can usingNodeService.hasAspect to discover if a node is about to be deleted.
  • It is impossible to add new nodes or link other nodes into any node in the hierarchy being deleted. Any attempt to do so will be treated as a concurrency violation since custom code should not be attempting this from callbacks during the node deletion.
  • All associations, with the notable exception of the primary parent-child links, will be removed even if node archival is taking place. Node archival now only preserves the core parent-child associations and discards all other associations after making the relevant callbacks. Custom code must use the association deletion callbacks to remove nodes or aspects that might violate model integrity constraints in the archived hierarchy.

A good example of the changes is in the handling of thecm:copiedFrom aspect. Copied nodes have an aspectcm:copiedfrom, which has a mandatory association to the original source node. When either the source or copy is deleted the aspect has to be removed. Seeorg.alfresco.repo.copy.CopyServiceImpl.beforeDeleteOriginalAssociation for how the association deletion is detected in order to ensure that the aspect is removed from the copied node.

NodeLocatorService

TheNodeLocatorService looks up node locators registered via Spring configuration by name. The service provides a way to lookup one node from another. This Spring configuration file defines a base bean that can be used to define new node locator implementations. Using this bean will automatically register the node locator with the repository and make it available.

The following table shows the node locators available out-of-the-box, the parameters they accept and their use.

NameClassParametersUsage
companyhomeCompanyHomeNodeLocatorNoneReturns the/Company Home folder node
userhomeUserHomeNodeLocatorNoneReturns the current user’s home folder node
sharedhomeSharedHomeNodeLocatorNoneReturns the Shared Home folder root node
siteshomeSitesHomeNodeLocatorNoneReturns the Sites root node
doclibDocLibNodeLocatorNoneReturns thedocumentLibrary node for the site the source node belongs to
selfSelfNodeLocatorNoneReturns the source node
xpathXPathNodeLocatorquery,store_type andstore_idReturns the node pointed to by the given XPath query. The XPath should be relative to the root of a store. If a source node is provided the Store is taken from the node, otherwise thestore_type andstore_id must be provided.
ancestorAncestorNodeLocatortype and aspectReturns an ancestor node of the source node. If no parameters are provided the immediate parent is returned. If a type parameter is present the first ancestor node of that type is returned. If an aspect parameter is present the first ancestor node with that aspect applied is returned. The type and aspect parameters can be combined thus finding an ancestor node of a certain type and with a specific aspect applied.

The following shows how to use one of the out-of-the-box node locators to get aNodeRef for the/Company Home folder:

NodeRef result = serviceRegistry.getNodeLocatorService().getNode(CompanyHomeNodeLocator.NAME, null, null);

Custom Node Locator

In the following example we will see how to implement a custom node locator, it will allow a named folder to be found. To define the example node locator the following Spring configuration is used (in a custom context file):

<bean parent="baseNodeLocator">   <property name="NodeService" ref="NodeService" />   <property name="FileFolderService" ref="FileFolderService" /></bean>

A node locator must implement theNodeLocator interface, whose definition is shown below:

public interface NodeLocator {    NodeRef getNode(NodeRef source, Map<String, Serializable> params);    public List<ParameterDefinition> getParameterDefinitions(); }

ANodeLocator in its simplest form takes a source node, some optional parameters and returns a node or null if a suitable node could not be found. If a node is not found theNodeLocatorService returns theNodeRef representing the/Company Home folder.

The source node is not mandatory, node locators can be used to return well known nodes,/Company Home,/User Home for example in which case a source node is not required.

If aNodeLocator has parameters they must be defined using the same definition classes (ParameterDefinition) used by theActionService.

A base classAbstractNodeLocator is provided and it’s recommended that yourNodeLocator extends this base class. It provides the functionality to register theNodeLocator with theNodeLocatorService registry. This class also defines an abstract method your implementation must override.

public abstract String getName();

This is the unique name for yourNodeLocator and will be used by theNodeLocatorService in the lookup process. It is also used in thestartLocation configuration.

Our example locator,NamedFolderNodeLocator, will be namednamedfolder and will expect a single parameter calledname which will indicate what folder to locate. The full source for this example is shown below:

public class NamedFolderNodeLocator extends AbstractNodeLocator {    public static final String LOCATOR_NAME = "namedfolder";    public static final String NAME_PARAM = "name";    private ServiceRegistry serviceRegistry;        @Override    public NodeRef getNode(NodeRef source, Map<String, Serializable> params) {        NodeRef node = null;              String folderName = (String)params.get(NAME_PARAM);        if (source != null && folderName != null) {           // Get the parent of the source node           NodeRef parent = serviceRegistry.getNodeService().getPrimaryParent(source).getParentRef();           // Look for a child with the provided name           NodeRef folder = serviceRegistry.getNodeService()NodeService().getChildByName(                   parent, ContentModel.ASSOC_CONTAINS, folderName);           // Make sure it's a folder           if (folder != null && serviceRegistry.getFileFolderService().getFileInfo(folder).isFolder()) {               node = folder;           }        }                return node;    }          public List<ParameterDefinition> getParameterDefinitions() {        List<ParameterDefinition> paramDefs = new ArrayList<ParameterDefinition>(2);        paramDefs.add(new ParameterDefinitionImpl(NAME_PARAM, DataTypeDefinition.TEXT, false, "Name"));        return paramDefs;    }                public String getName() {        return LOCATOR_NAME;    }}

The"source" parameter ingetNode() represents the starting point, in a form association control this will be the node being edited, for a create form it will be the destination node. Our example finds the primary parent of the source node and looks for a child folder with the given name. This is a fairly simple example but it is easy to see how this could be extended to allow for a named folder to be located up or down a folder hierarchy.

startLocation

The main use of theNodeLocatorService is to determine where the forms association control should start when it is first displayed. In some scenarios the picker may need to start in the root of the document library of a Share site or start in the folder where the node being edited is located. See the next section for a list ofNodeLocators provided out-of-the-box.

NodeLocators are configured using form control parameters. The name of theNodeLocator implementation is provided as thestartLocation parameter and the parameters are provided by astartLocationParameters parameter. They should be provided in the form of query string parameters, for examplename=value&name=value.

The configuration for our example node locator is shown below, it will look for a folder named “Example” in the same folder as the node being edited.

<field>   <control>      <control-param name="startLocation">{namedfolder}</control-param>      <control-param name="startLocationParams">name=Example</control-param>   </control></field>

Note: The curly braces are required around the node locator name.

PermissionService

Provides an API for managing the node permissions. Permissions specify users and groups that have access to a node. Each user and group can be assigned a role.

ThePermissionService is responsible for:

  • Providing well known permissions and authorities
  • Providing an API to read, set, and delete permissions for a node
  • Providing an API to query, enable, and disable permission inheritance for a node
  • Determining if the current, authenticated user has a permission for a node

ThePermissionService interface defines constants for well-known permissions and authorities. The default implementation coordinates implementations of two service provider interfaces: aModelDAO and aPermissionsDAO. A permission is simply a name scoped by the fully qualified name of the type or aspect to which it applies. The beans are defined and configured in<installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\public-services-security-context.xml. This file also contains the configuration for security enforcement.

TheModelDAO interface defines an API to access a permissions model. The default permission model is in XML and defines permission sets, and their related permission groups and permissions. Global permissions are part of the permission model. There may be more than one permission model defined in XML; they are in practice merged into one permission model. A module can extend the permission model.

The available permissions are defined in the permission model. This is defined in<installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\model\permissionDefinitions.xml. This configuration is loaded in a bean definition in<installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\public-services-security-context.xml. This file also defines global permissions. The definition file is read once at application start-up. If you make changes to this file, you will have to restart the repository in order to apply the changes.

SetCoordinator role permissions for a usernamejohn on a node:

serviceRegistry.getPermissionService().setPermission(nodeRef, "john", PermissionService.COORDINATOR, true);

See alsoPermissions platform extension point

PersonService

This service encapsulates the management of people and groups. People and groups may be managed entirely in the repository or entirely in some other implementation such as LDAP. Some properties may be in the repository and some in another store. Individual properties may or may not be mutable.

ThePersonService supports various methods relating to users. The methods relating to the Person service include the ability to:

  • Look up people from usernames
  • Create user information
  • Delete user information
  • Modify user information

Create a user that can login/authenticate with password:

if (serviceRegistry.getAuthenticationService().authenticationExists(userName) == false) {    serviceRegistry.getAuthenticationService().createAuthentication(userName, password.toCharArray());    Map user = new Map();    user.put(ContentModel.PROP_USERNAME, userName);    user.put(ContentModel.PROP_FIRSTNAME, "firstName");    user.put(ContentModel.PROP_LASTNAME, "lastName");    user.put(ContentModel.PROP_EMAIL, userName+"@example.com");    user.put(ContentModel.PROP_JOBTITLE, "jobTitle");        NodeRef person = serviceRegistry.getPersonService().createPerson(user);}

RenditionService

Provides support for rendering content nodes into other forms, known as renditions. The rendition nodes are derived from their source node and as such can be updated automatically when their source node’s content (or other properties) are changed. Examples of renditions include reformatted content (essentially a transformation from one MIME-type to another), rescaled images (including thumbnails), and the output of a Freemarker or XSLT template.

Renditions can be performed synchronously or asynchronously and can be created at a specified location within the repository. They are by default created as primary children of their source node, but it is possible to have them created at other nodes specified explicitly or as templated paths.

Rendering Engines

Rendering engines are responsible for performing the transformation on a source node to create a rendition. Different rendering engines will perform different types of transformation. They can be registered with theRenditionService using a unique name.

Rendering engine definitions provide a description of a given rendering engine. Each rendering engine definition exposes parameter definitions for all the parameters which can be provided to the associated rendering engine. Each parameter definition describes the parameter name, type and whether or not it is mandatory.

Rendition definitions encapsulate all the necessary information for rendering a given source node into a rendition. This includes the rendering engine, which is used to perform the rendition and all the parameter values specified. Rendition definitions have unique, qualified names and can be persisted within the repository.

Composite rendition definitions are a special type of rendition definition that allows the creation of renditions that require a sequence of two or more transformation steps. For example, a composite rendition definition could be used to first reformat a PDF document into a PNG image and then resize the image to a small thumbnail. Composite rendition definitions specify an ordered list of other rendition definitions to be sequentially executed, with the output of the previous transformation feeding in as the source node for the next definition. All composite rendition definitions specify the composite rendering engine for their transformations.

Available rendering engines include:

  • Base rendering engine
  • Reformat rendering engine
  • Image rendering engine
  • FreeMarker rendering engine
  • XSLT rendering engine
  • HTML rendering engine
  • Composite rendering engine

Sample code

Rendering engines are registered with theRenditionService through Spring dependency injection. Therendition-services-context.xml declares an abstract bean calledbaseRenderingAction, which is the parent bean for all rendering engines.baseRenderingAction itself is a child bean of theActionServicesaction-executer bean.

In Content Services, there are a number of concrete rendering engine beans, for example, reformat within the same spring context file. To register a new rendering engine, add new spring bean definitions.

Creating a rendition definition:

// Names must be provided for the rendition definition and the rendering engine to use.QName  renditionName       = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myRendDefn");String renderingEngineName = ReformatRenderingEngine.NAME;// Create the Rendition Definition object.RenditionDefinition renditionDef = serviceRegistry.getRenditionService().createRenditionDefinition(renditionName, renderingEngineName);// Set parameters on the rendition definition.renditionDef.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_PDF);

Storing a rendition definition:

// Store the Rendition Definition using the QName// of the Rendition Definition as a unique identifier.serviceRegistry.getRenditionService().saveRenditionDefinition(renditionDef);

Retrieving a rendition definition can be done in the following ways:

// 1. As a list of all stored Rendition DefinitionsList<RenditionDefinition> definitions = serviceRegistry.getRenditionService().loadRenditionDefinitions();// 2. As a list of stored Rendition Definitions filtered by Rendering Engine name.String renderingEngineName = "myEngineName";List<RenditionDefinition> definitions = serviceRegistry.getRenditionService().loadRenditionDefinitions();// 3. As a single Rendition Definition, uniquely identified by its QName.QName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myRendDefn");RenditionDefinition renditionDef = serviceRegistry.getRenditionService().loadRenditionDefinition(renditionName);

Editing an existing rendition definition:

// Retrieve the existing Rendition DefinitionQName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myRendDefn");RenditionDefinition renditionDef = serviceRegistry.getRenditionService().loadRenditionDefinition(renditionName);// Make changes.renditionDef.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_PDF);renditionDef.setParameterValue(serviceRegistry.getRenditionService().PARAM_ORPHAN_EXISTING_RENDITION, true);// Persist the changes.serviceRegistry.getRenditionService().saveRenditionDefinition(renditionDef);

Performing a simple rendition:

// A rendition definition is required to perform any rendition.// The rendition definition can be loaded from the repository or created as shown above.NodeRef sourceNode = // obtained in the usual way e.g. from nodeServiceChildAssociationRef renditionAssoc = serviceRegistry.getRenditionService().render(sourceNode, renditionDef);

Performing a composite rendition:

// First obtain a Composite Rendition Definition// This can be loaded from the repository or created as shown here.QName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myRendDefn");CompositeRenditionDefinition compositeDefinition =         serviceRegistry.getRenditionService().createCompositeRenditionDefinition(renditionName);// Now specify which other renditions are to be performed as part of the composite rendition.RenditionDefinition reformatDefinition = serviceRegistry.getRenditionService().load(reformatRenditionName);RenditionDefinition rescaleImageDefinition = serviceRegistry.getRenditionService().load(rescaleImageRenditionName);compositeDefinition.addAction(reformatDefinition);compositeDefinition.addAction(rescaleImageDefinition);// Perform the composite renditionNodeRef sourceNode = // obtained in the usual way e.g. from nodeServiceChildAssociationRef renditionAssoc = serviceRegistry.getRenditionService().render(sourceNode, compositeDefinition);

Retrieving renditions for a node:

NodeRef sourceNode = // obtained in the usual way e.g. from nodeService// 1. Get all renditions with the specified node as their source.List<ChildAssociationRef> allRenditions = serviceRegistry.getRenditionService().getRenditions(sourceNode);// 2. Get the rendition with the specified source node and the specified rendition definition name.//    If there is no matching rendition, null is returnedQName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myRenditionDef");ChildAssociationRef rendition = serviceRegistry.getRenditionService().getRenditionByName(sourceNode, renditionName);// 3. Get the renditions with the specified source node whose MIME types match a filter//    This example returns renditions whose mimetype starts with "image".List<ChildAssociationRef> imageRenditions = serviceRegistry.getRenditionService().getRenditions(sourceNode, "image");

Specifying a rendition definition as asynchronous or synchronous:

This behaviour is inherited from theActionService - remember thatRenditionDefinition extendsAction. So we can create a rendition definition as shown above and set it to execute asynchronously:

RenditionDefinition renditionDef = // created as shown aboverenditionDef.setExecuteAsynchronously(true);

See also:

RetryingTransactionHelper

A helper that runs a unit of work inside aUserTransaction, transparently retrying the unit of work if the cause of failure is an optimistic locking or deadlock condition.

A description and application of theRetryingTransactionHelper can be found in thegetting started section.

SearchService

This encapsulates the execution of search against different indexing mechanisms. Solr provides indexing of metadata and the plain text of content. This can be queried using various query languages. The query languages supported include:

  • LANGUAGE_FTS_ALFRESCO
  • LANGUAGE_CMIS_ALFRESCO
  • LANGUAGE_CMIS_STRICT
  • LANGUAGE_LUCENE
  • LANGUAGE_SOLR_ALFRESCO
  • LANGUAGE_SOLR_CMIS
  • LANGUAGE_SOLR_FTS_ALFRESCO
  • LANGUAGE_XPATH

Alfresco Full Text Search (FTS) is Alfresco’s native query language. It supports searching by single term, phrase, exact term, term expansion, conjunctions, disjunctions, negation, optional, mandatory, excluded, fields, wildcards, ranges, fuzzy matching, proximity, boosts and grouping. For example:

ASPECT:'cm:titled' AND cm:title:'*Sample*' AND TEXT:'code'

A CMIS query is based upon SQL-92. The query is read-only and presents no data manipulation capabilities. The CMIS specification supports a subset of Alfresco FTS, which can be embedded in CMIS-SQL using thecontains() predicate function. CMIS query example:

SELECT * FROM cm:titledWHERE cm:titlelike '%Sample%' AND CONTAINS('code')

Examples using theSearchService languages FTS and CMIS:

ResultSet results = serviceRegistry.getSearchService().query(storeRef, SearchService.LANGUAGE_FTS_ALFRESCO,         "ASPECT:'cm:titled' AND cm:title:'*Sample*' AND TEXT:'code'");results = serviceRegistry.getSearchService().query(storeRef, SearchService.LANGUAGE_CMIS_ALFRESCO,         "SELECT * FROM cm:titledWHERE cm:titlelike '%Sample%' AND CONTAINS('code')");

UsingSearchService to get aNodeRef for an XPath (as seen in NodeBrowser) using language LUCENE:

/** * Get a NodeRef by its path. *   * @path as displayed by the NodeBrowser. * @return the NodeRef, or null if no NodeRef matches this path. */private NodeRef getNode(String path) {  ResultSet results = null;  try {      StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");      results = serviceRegistry.getSearchService().query(storeRef, SearchService.LANGUAGE_LUCENE,         "PATH:\"" + path + "\"");      if (results.length() == 0) {          logger.debug("Zero matches for path: " + path);          return null;      }            NodeRef nodeRef = results.getNodeRef(0);      logger.debug("NodeRef for \"" + path + "\" is " + nodeRef);            return nodeRef;  } catch(Exception e) {      logger.debug("Exception while searching for path: " + path, e);      return null; // The node does not exist  } finally {      if (results != null) {        results.close();      }  }}

Use this method as in the following example:

NodeRef someNodeRef = getNode("/app:company_home/app:shared/cm:abc/cm:def/cm:My_x0020_Document.txt");

Note that this can also be achieved with theNodeLocatorService:

Map<String,Serializable> params = new HashMap<>();params.put("query", "/app:company_home/app:shared/cm:abc/cm:def/cm:My_x0020_Document.txt");NodeRef nodeRef = serviceRegistry.getNodeLocatorService().getNode("xpath",null,params);

If the search result is getting big you can use paging. There are two ways to query with theSearchService (excluding theselectNodes/selectProperties calls). One way is to specify all your arguments directly to thequery method as seen above. This has the advantage of being concise, but the disadvantage is that you don’t get all the options.Alternately, you can query with aSearchParameters object. This lets you do everything the simple query does, and more. Included in that more aresetLimit,setSkipCount andsetMaxItems, which will allow you to do your paging.

If your query used to be something like:

serviceRegistry.getSearchService().query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, myQuery);

You would instead do something like this:

SearchParameters sp = new SearchParameters();sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);sp.setLanguage(SearchService.LANGUAGE_LUCENE);sp.setQuery(myQuery);sp.setMaxItems(100);sp.setSkipCount(100);// Execute query.ResultSet resultSet = serviceRegistry.getSearchService().query(sp);for (NodeRef result : resultSet.getNodeRefs()) {    // Do stuff}resultSet.close();

Set up search params with unlimited results and sorting oncm:modified property (might not always be a good idea with unlimited result, unless you know approximately how many nodes will be the limit):

SearchParameters sp = new SearchParameters();sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);sp.setLanguage(SearchService.LANGUAGE_LUCENE); // Can be lucene, FTS, CMIS, etc.sp.setQuery(myQuery);sp.setMaxItems(Integer.MAX_VALUE);sp.setMaxPermissionChecks(Integer.MAX_VALUE);sp.addSort("@" + ContentModel.PROP_MODIFIED, false);// Execute query.ResultSet resultSet = serviceRegistry.getSearchService().query(sp);for (NodeRef result : resultSet.getNodeRefs()) {    // Do stuff}resultSet.close();

See alsoAlfresco FTS reference.

SiteService

TheSiteService provides an API for managingShare Sites.

Creating a site is not possible with thesiteService.createSite method, it only creates a site at the repository level, it does not create a fully functional site. To create a fully functional site use theReST API.

Adding a site manager (member):

String siteShortName = "my-test";serviceRegistry.getSiteService().setMembership(siteShortName, "admin", SiteRole.SiteManager.toString());

Creating a folder/container in the site:

String siteShortName = "my-test";NodeRef documentLibraryNodeRef =  serviceRegistry.getSiteService().createContainer(        siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);

Get the Document Library folder/container of a site:

String siteShortName = "my-test";NodeRef documentLibrary = serviceRegistry.getSiteService().getContainer(siteShortName, SiteService.DOCUMENT_LIBRARY);

Get information about a site:

String siteShortName = "my-test";SiteInfo siteInfo = serviceRegistry.getSiteService().getSite(siteShortName);String title = siteInfo.getTitle();String description = siteInfo.getDescription();SiteVisibility visibility = siteInfo.getVisibility();Date created = siteInfo.getCreatedDate();

Get site information for the Share Site that contains the givenNodeRef:

NodeRef someFolderInSiteNodeRef = "node ref.......";SiteInfo siteInfo = serviceRegistry.getSiteService().getSite(someFolderInSiteNodeRef);

TaggingService

It is possible to tag (a text label) any content, including folders. This service provides an API for creating, deleting, and adding tags, and other tag management methods.

Tags are simple text labels that are attached to a piece of content. Each piece of content can have multiple tags. Folders also have aTagScope object which encapsulates information about the tags used on content in that folder. TheTagScope object contains an array that lists Tags in count order. There are methods to find out how many times a particular tag is used.

Get tags applied to node:

List<String> tags = serviceRegistry.getTaggingService().getTags(nodeRef);

TemplateService

Provides an API for executing template engine against a template file and data model. The service provides a configured list of available template engines. The template file can either be in the repository (passed asNodeRef string) or on the classpath. Also a template can be passed directly as a String using theprocessTemplateString() methods. The data model is specified to the template engine. TheFreeMarker template engine is used by default.

// build the email template modelfinal Map<String, Object> model = createEmailTemplateModel(nodeRef);// process the template against the modeltext = serviceRegistry.getTemplateService().processTemplate("freemarker", templateRef.toString(), model);

VersionService

Provides an API for managing file versions (i.e. content of type, or subtype,cm:content). Note that folders are not versionable.

Alfresco has a strong versioning story, which gives you the ability to version any file content stored in the repository, no matter what the file type. Versions are full files and not diffs of the files. Alfresco gives you the ability to have both major and minor versions of content. Versions can be created/updated by checkout/checkin, by rule, through any interface or through script/APIs.

If a content file has the aspectcm:versionable applied to it, then multiple versions of the file can be managed. TheVersionService provides an API to allow you to do this programmatically:

  • createVersion - this creates a new version of the file, which is placed at the end of the appropriate version history. If the file has no version history then one is created and this version is considered to be the initial version.
  • getVersionHistory - this gets the version history that relates to the file.
  • deleteVersionHistory - this deletes the version history for a versioned file.
  • getCurrentVersion - gets the current version for a file.
  • revert - reverts the state of a file to that of a previous version.
  • restore - restores a previously deleted file from a version in its version history.

Alfresco provides the ability to applybehavior policies to content and metadata within the repository. You can think of these as event listeners, that allow you to take custom actions based on what is happening within the repository.

In the following example we are listening to theafterCreateVersion event, and when triggered we check if we have reached the maximum number of versions that we want to store. If we have, then we delete the last one (by default Alfresco has no limit of how many versions it stores):

public class MaxVersionPolicy implements VersionServicePolicies.AfterCreateVersionPolicy {    private static Log logger = LogFactory.getLog(MaxVersionPolicy.class);    private ServiceRegistry serviceRegistry;    private PolicyComponent policyComponent;    private Behaviour afterCreateVersion;    /**     * Max number of versions we will store of a file in the repo     */    private int maxVersions;    public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; }    public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; }    public void setMaxVersions(int maxVersions) {  this.maxVersions = maxVersions; }    /**     * Spring bean init() method     */    public void init() {        this.afterCreateVersion = new JavaBehaviour(this, "afterCreateVersion",                Behaviour.NotificationFrequency.TRANSACTION_COMMIT);        this.policyComponent.bindClassBehaviour(QName.createQName(                NamespaceService.ALFRESCO_URI, "afterCreateVersion"),                MaxVersionPolicy.class, this.afterCreateVersion);    }    @Override    public void afterCreateVersion(NodeRef versionableNode, Version version) {        VersionHistory versionHistory = serviceRegistry.getVersionService().getVersionHistory(versionableNode);        if (versionHistory != null) {            logger.debug("Current number of versions: " + versionHistory.getAllVersions().size());            logger.debug("least recent/root version: " + versionHistory.getRootVersion().getVersionLabel());            // If the current number of versions in the VersionHistory is greater            // than the maxVersions limit, remove the root/least recent version            if (versionHistory.getAllVersions().size() > maxVersions) {                logger.debug("Removing Version: " + versionHistory.getRootVersion().getVersionLabel());                serviceRegistry.getVersionService().deleteVersion(versionableNode, versionHistory.getRootVersion());            }        } else {            logger.debug("versionHistory does not exist");        }    }}

The Spring bean for theMaxVersionPolicy class looks like this:

<bean                init-method="init">    <property name="policyComponent">        <ref bean="policyComponent" />    </property>    <property name="serviceRegistry">        <ref bean="ServiceRegistry" />    </property>    <!-- The max number of versions per versioned file -->    <property name="maxVersions">        <value>10</value>    </property></bean>

WorkflowService

Provides an interface to work with built inworkflows and tasks. TheActiviti workflow engine is built into Content Services. You can create and manage workflows directly from your Dashboard.

With theWorkflowService, you can create and manage these workflows programmatically.

The default workflows out-of-the-box are:

  • New Task
  • Assign a new task to yourself or a colleague
  • Review and approve (group review)
  • Assign a review task to a group
  • Review and Approve (one or more reviewers)
  • Assign a review task to multiple reviewers
  • Review and Approve (pooled review)
  • Assign a review task to multiple reviewers, who can take ownership of the task
  • Review and Approve (single reviewer)
  • Assign a review task to a single reviewer

An extensive example of using the Workflow API is provided in theAlfresco source code.

Note. To build your own custom workflows you need to useAlfresco Process Services

Edit this page

Suggest an edit on GitHub
By clicking "Accept Cookies", you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts.View Cookie Policy.

[8]ページ先頭

©2009-2025 Movatter.jp