REST-style architectures consist of clients and servers. Clients initiate requests to servers; servers process requests and return appropriate responses. Requests and responses are built around the transfer of "representations" of "resources". A resource can be essentially any coherent and meaningful concept that may be addressed. A representation of a resource is typically a document that captures the current or intended state of a resource.
At any particular time, a client can either be in transition between application states or "at rest". A client in a REST state is able to interact with its user, but creates no load and consumes no per-client storage on the set of servers or on the network.
The client begins sending requests when it is ready to make the transition to a new state. While one or more requests are outstanding, the client is considered to be in transition. The representation of each application state contains links that may be used the next time, the client chooses to initiate a new state transition.
REST is initially described in the context of HTTP, but is not limited to that protocol. RESTful architectures can be based on other Application Layer protocols if they already provide a rich and uniform vocabulary for applications based on the transfer of meaningful representational state. RESTful applications maximize the use of the pre-existing, well-defined interface and other built-in capabilities provided by the chosen network protocol, and minimize the addition of new application-specific features on its top.
Here is the convention we should follow:
| Method | Definition |
|---|---|
| GET | Get a Resource. It should never modify a state |
| POST | Create a Resource (or anything that don't fit elsewhere) |
| PUT | Update a Resource |
| DELETE | Delete a Resource |
We should support this format for all our APIs:
listener
The default format is JSON.
The format of the response can be specified by a parameter in the request: "format" Specify the format requested.
First, we have to register REST service class to the configuration file in the package named conf.portal
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd http://www.exoplaform.org/xml/ns/kernel_1_1.xsd" xmlns="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd"> <component> <type>org.exoplatform.services.ecm.publication.REST.presentation.document.publication.PublicationGetDocumentRESTService</type> </component> </configuration>
We start to create GetEditedDocumentRESTService that implements from the ResourceContainer interface.
@Path("/presentation/document/edit/")
public class GetEditedDocumentRESTService implements ResourceContainer {
@Path("/{repository}/")
@GET
public Response getLastEditedDoc(@PathParam("repository") String repository,
@QueryParam("showItems") String showItems) throws Exception {
........
}
}
| Parameters | Definition |
|---|---|
| @Path("/presentation/document/edit/") | Specify the URI path that a resource or class method will serve requests for. |
| @PathParam("repository") | Bind the value repository of a URI parameter or a path segment containing the template parameter to a resource method parameter, resource class field, or resource class bean property. |
| @QueryParam("showItems") | Bind the value showItems of a HTTP query parameter to a resource method parameter, resource class field, or resource class bean property. |
This part describes how to create a sample UI extension.
Since DMS 2.5, it is possible to extend the File Explorer and the ECM Administration with the UI Extension Framework. Indeed, you can add your own action buttons to the File Explorer and/or add your own managers to the ECM Administration.
Create a pom.xml from the following content:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.exoplatform.ecms</groupId>
<artifactId>exo-ecms-examples-uiextension-framework</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
<artifactId>exo-ecms-examples-uiextension-framework-manage-wcm-cache</artifactId>
<name>eXo WCM Cache Examples </name>
<description>eXo WCM Cache Examples </description>
<dependencies>
<dependency>
<groupId>org.exoplatform.kernel</groupId>
<artifactId>exo.kernel.container</artifactId>
<version>2.2.13-GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.exoplatform.commons</groupId>
<artifactId>exo.platform.commons.webui.ext</artifactId>
<version>1.0.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.exoplatform.ecms</groupId>
<artifactId>exo-ecms-core-webui</artifactId>
<version>2.1.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.exoplatform.ecms</groupId>
<artifactId>exo-ecms-core-webui-administration</artifactId>
<version>2.1.10</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.jar</include>
<include>**/*.pom</include>
<include>**/*.conf</include>
<include>**/*.gtmpl</include>
<include>**/*.gif</include>
<include>**/*.jpg</include>
<include>**/*.png</include>
</includes>
</resource>
</resources>
</build>
</project>Create the directories src/main/java and start launching mvn eclipse:eclipse_. You can then, launch your eclipse and import this new project.
Create a new class called org.exoplatform.wcm.component.cache.UIWCMCacheComponent that extends org.exoplatform.ecm.webui.component.admin.manager.UIAbstractManagerComponent.
The webui framework allows you to be notified when a given action has been triggered, you just need to call your own action listener as follows (ACTIONNAME) ActionListener. For example: You create your own action listener name CacheView, so your action listener then will be named as CacheViewActionListener.
First, add a static inner class called CacheViewActionListener that extends org.exoplatform.ecm.webui.component.admin.listener.UIECMAdminControlPanelActionListener .
See the expected code below:
public class CacheViewComponent extends UIAbstractManagerComponent {
public static class CacheViewActionListener extends UIECMAdminControlPanelActionListener<UIWCMCacheComponent> {
public void processEvent(Event<UIWCMCacheComponent> event) throws Exception {UIECMAdminPortlet portlet = event.getSource().getAncestorOfType(UIECMAdminPortlet.class);
UIECMAdminWorkingArea uiWorkingArea = portlet.getChild(UIECMAdminWorkingArea.class);
uiWorkingArea.setChild(UIWCMCachePanel.class) ;
event.getRequestContext().addUIComponentToUpdateByAjax(uiWorkingArea);
}
}Create the directories src/main/java and launch mvn eclipse:eclipse_. You can then, launch your eclipse and import this new project.
Create a new configuration file conf/portal/configuration.xml to register your action (see 6.2.2.3) with the service org.exoplatform.webui.ext.UIExtensionManager.
<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd http://www.exoplaform.org/xml/ns/kernel_1_1.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd">
<external-component-plugins>
<target-component>org.exoplatform.webui.ext.UIExtensionManager</target-component>
<component-plugin>
<name>Add.Actions</name>
<set-method>registerUIExtensionPlugin</set-method>
<type>org.exoplatform.webui.ext.UIExtensionPlugin</type>
<init-params>
<object-param>
<name>CacheView</name>
<object type="org.exoplatform.webui.ext.UIExtension">
<field name="type"><string>org.exoplatform.ecm.dms.UIECMAdminControlPanel</string></field>
<field name="name"><string>CacheView</string></field>
<field name="category"><string>GlobalAdministration</string></field>
<field name="component"><string>org.exoplatform.wcm.component.cache.UIWCMCacheComponent</string></field>
</object>
</object-param>
<object-param>
<name>UIWCMCacheManager</name>
<object type="org.exoplatform.webui.ext.UIExtension">
<field name="type"><string>org.exoplatform.ecm.dms.UIECMAdminControlPanel</string></field>
<field name="name"><string>UIWCMCacheManager</string></field>
<field name="category"><string>GlobalAdministration</string></field>
<field name="component"><string>org.exoplatform.wcm.manager.cache.UIWCMCacheManagerComponent</string></field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins>
</configuration>
Launch mvn clean install and copy the file target/exo-ecms-examples-webui-2.1.0-SNAPSHOT.jar into (TOMCATHOME)/lib
All resources can be located in the src/main/resource package because the resources (*.xml, images, conf file) and the code are separated. This is very useful in a hierarchical structure.
Create ExamplePortleten.xml with the following content and add it to src/main/resource package:
<?xml version="1.0" encoding="UTF-8"?>
<bundle>
<!--
################################################################################
# org.exoplatform.wcm.component.cache.UIWCMCacheForm #
################################################################################
-->
<UIWCMCacheForm>
<action>
<Cancel>Cancel</Cancel>
<Save>Save</Save>
<Clear>Clear the cache</Clear>
</action>
<label>
<maxsize>Max size :</maxsize>
<livetime>Live time in sec :</livetime>
<isCacheEnable>Cache enabled(should always be on production enviroment)</isCacheEnable>
<hit>Hit count :</hit>
<currentSize>Current size</currentSize>
<miss>Miss count :</miss>
</label>
</UIWCMCacheForm>
<!--
################################################################################
# org.exoplatform.wcm.manager.cache.UIWCMCacheManagerForm #
################################################################################
-->
<UIWCMCacheManagerForm>
<action>
<Cancel>Cancel</Cancel>
<Save>Save</Save>
<Clear>Clear the cache</Clear>
</action>
<label>
<cacheModify>Cache to modify :</cacheModify>
<maxsize>Max size :</maxsize>
<livetime>Live time in sec :</livetime>
<isCacheEnable>Cache enabled(should always be on production enviroment)</isCacheEnable>
<hit>Hit count :</hit>
<currentSize>Current size</currentSize>
<miss>Miss count :</miss>
</label>
</UIWCMCacheManagerForm>
<UIECMAdminControlPanel>
<tab>
<label>
<GlobalAdministration>Global Administration</GlobalAdministration>
</label>
</tab>
<label>
<UIWCMCache>WCM Cache</UIWCMCache>
<UIWCMCachePanel>WCM Cache Administration</UIWCMCachePanel>
<UIWCMCacheManager>Managing Caches</UIWCMCacheManager>
<UIWCMCacheManagerPanel>WCM Cache Management</UIWCMCacheManagerPanel>
</label>
</UIECMAdminControlPanel>
</bundle>You must add the following content to configuration.xml- to register the resource bundle.
By being added this configuration, the resource bundle has been completely separated the resource bundle from the original system, this is so clearly useful, you get an independent plugin.
<external-component-plugins>
<!-- The full qualified name of the ResourceBundleService -->
<target-component>org.exoplatform.services.resources.ResourceBundleService</target-component>
<component-plugin>
<!-- The name of the plugin -->
<name>ResourceBundle Plugin</name>
<!-- The name of the method to call on the ResourceBundleService in order to register the ResourceBundles -->
<set-method>addResourceBundle</set-method>
<!-- The full qualified name of the BaseResourceBundlePlugin -->
<type>org.exoplatform.services.resources.impl.BaseResourceBundlePlugin</type>
<init-params>
<values-param>
<name>init.resources</name>
<description>Store the following resources into the db for the first launch </description>
<value>locale.portlet.cache.ExamplePortlet</value>
</values-param>
<values-param>
<name>portal.resource.names</name>
<description>The properties files of the portal , those file will be merged
into one ResoruceBundle properties </description>
<value>locale.portlet.cache.ExamplePortlet</value>
</values-param>
</init-params>
</component-plugin>
</external-component-plugins>
Publication addons for WCM 2.1.0
In this extended publication, we will allow new states and new profiles. These states already exist in WCM but not used and we will enable them.
Profiles
Author : This profile can edit a content and mark this content as redacted
Approver : This profile approves a pending content (marked by the Author)
Publisher : This profile publishes contents or marks them as "Ready for publication" in multi-server mode
Archiver : An administrative profile which moves contents to an archive storage.
States
enrolled : It's a pure technical state, generally used for content creation.
draft (Author) : Content is in editing phase
pending (Author) : Author validates the content
approved (Approver) : Content is approved by the Manager
inreview (Manager) : This state can be used when a second approval state is needed (for i18 translation for example)
staged (Publisher) : Content is ready for publication (multi-server mode)
published (Publisher or Automatic) : Content is published and visible in Live mode
unpublished (Publisher or Automatic) : Content is not visible in Live mode
obsolete : Content can still be published but we consider it's not in an editing lifecycle anymore
archived (Automatic) : Content is archived and ready to be moved in archive workspace if enabled.
In most cases, Customers don't want to publish directly a content but at a defined date and they also want to unpublish automatically the content after that. In the new publication plugin, we add new properties to manage this:
publication:startPublishedDate
publication:endPublishedDate
The WCM 2.0 rendering engine doesn't know anything about publication dates, so another service needs to manage that. When the publisher sets start/end publication dates, he can "stage" the content. The content will go automatically to "published" state when start date arrives and to "unpublished" state after end date. A cron job checks every hour (or less) all contents which need to be published (start date in the past + "staged" state) or unpublished (end date in the past + "published" state).
Because of that, publication dates are not mandatory and a content can go to:
staged : in multi-server mode, publisher can only put the content to staged and wait for auto-publication
published : in single-server mode, publisher can directly publish a content (with or without publication dates).
<nodeType hasOrderableChildNodes="false" isMixin="true" name="publication:authoringPublication" primaryItemName="">
<supertypes>
<supertype>publication:stateAndVersionBasedPublication</supertype>
</supertypes>
<propertyDefinitions>
<propertyDefinition autoCreated="false" mandatory="true" multiple="false" name="publication:startPublishedDate" onParentVersion="IGNORE" protected="false" requiredType="Date">
<valueConstraints/>
</propertyDefinition>
<propertyDefinition autoCreated="false" mandatory="true" multiple="false" name="publication:endPublishedDate" onParentVersion="IGNORE" protected="false" requiredType="Date">
<valueConstraints/>
</propertyDefinition>
</propertyDefinitions>
</nodeType>
Note that some labels like "Pr�t � publier" could be not well displayed in the publication UI. You can extend the current UI State button width by adding :
.UIPublicationPanel .StatusTable .ActiveStatus {
width:75px !important;
}
Another quick note about publication date inputs. UIPublicationPanel should not initialize the dates to any default value. The publishing and unpublish cron jobs will do this :
a staged document with null publication start date is published instantly
a document with null publication end date is published forever
See export section for more info about cron jobs.
The Publication Manager manages lifecycles and contexts in the WCM Platform. It allows to manages different lifecycles based on different publication plugin in the platform.
public interface PublicationManager {
public List<Lifecycle> getLifecycles();
public List<Context> getContexts();
public Context getContext(String name);
public Lifecycle getLifecycle(String name);
public List<Lifecycle> getLifecyclesFromUser(String remoteUser, String state);
}
getLifecycles : returns a list of lifecycles (see below), with lifecycle name, publication plugin involved and possible states
getContexts : return a list of Context, with name, related Lifecycle and other properties (see below)
getContext : return a context by its name
getLifecycle : return a lifecycle by its name
getLifecycleFromUser : returns a list of Lifecycles in which the user has rights (based on membership property)
A lifecycle is defined by a simple vertical workflow with steps (states) and profiles (membership). Each lifecycle is related to a Publication plugin (to be compliant with JBPM or Bonita business processes). Example : Two lifecycles with/without states
<external-component-plugins>
<target-component>org.exoplatform.services.wcm.publication.PublicationManager</target-component>
<component-plugin>
<name>AddLifecycle</name>
<set-method>addLifecycle</set-method>
<type>org.exoplatform.services.wcm.publication.lifecycles.StatesLifecyclePlugin</type>
<init-params>
<object-param>
<name>lifecyles</name>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig">
<field name="lifecycles">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$Lifecycle">
<field name="name"><string>lifecycle1</string></field>
<field name="publicationPlugin"><string>States and versions based publication</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$Lifecycle">
<field name="name"><string>lifecycle2</string></field>
<field name="publicationPlugin"><string>Authoring publication</string></field>
<field name="states">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>draft</string></field>
<field name="memberships">
<collection type="java.util.ArrayList">
<value><string>author:/CA/communicationDG</string></value>
<value><string>author:/CA/alerteSanitaire</string></value>
<value><string>author:/CA/alerteInformatique</string></value>
<value><string>author:/CA/informations</string></value>
</collection>
</field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>pending</string></field>
<field name="membership"><string>author:/platform/web-contributors</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>approved</string></field>
<field name="membership"><string>manager:/platform/web-contributors</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>staged</string></field>
<field name="membership"><string>publisher:/platform/web-contributors</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>published</string></field>
<field name="membership"><string>automatic</string></field>
</object>
</value>
</collection>
</field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$Lifecycle">
<field name="name"><string>lifecycle3</string></field>
<field name="publicationPlugin"><string>Authoring publication</string></field>
<field name="states">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>draft</string></field>
<field name="membership"><string>author:/platform/web-contributors</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.wcm.publication.lifecycles.impl.LifecyclesConfig$State">
<field name="state"><string>published</string></field>
<field name="memberships">
<collection type="java.util.ArrayList">
<value><string>publisher:/CA/communicationDG</string></value>
<value><string>publisher:/CA/alerteSanitaire</string></value>
<value><string>publisher:/CA/alerteInformatique</string></value>
<value><string>publisher:/CA/informations</string></value>
</collection>
</field>
</object>
</value>
</collection>
</field>
</object>
</value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins>
In the last example, we have two lifecycles:
lifecycle 1 : based on States and versions based publication#* This allows to be backward compliant with older WCM releases. If all your site contents are using an existing plugin, you can create a lifecycle for it and it will work.
For new instances, we recommend to use the new plugin with dynamic states capabilities
lifecycle 2 : based on new Authoring publication#* Visibility : we define only the "visible" steps. In this example, there's no step for 'enrolled'. Even if this step exists, it will not be displayed in the UI.
Automatic : we can set a step as "automatic". In this mode, the step will be visible in the UI but it will be managed by the system (a cron job for example).
lifecycle 3 : simulates the States and versions based publication plugin
Note that this simple lifecycle will work in a single server configuration
When we change state, we broadcast an event in order for partners to add features if they want. Event could look like this:
listenerService.broadcast(AuthoringPlugin.POST_UPDATE_STATE_EVENT, null, node);
Listener declaration could look like this:
<external-component-plugins>
<target-component>org.exoplatform.services.listener.ListenerService</target-component>
<component-plugin>
<name>PublicationService.event.postUpdateState</name>
<set-method>addListener</set-method>
<type>org.exoplatform.services.wcm.publication.listener.post.PostUpdateStateEventListener</type>
<description>this listener will be called every time a content changes its current state</description>
</component-plugin>
</external-component-plugins>
A context is defined by simple rules. In WCM 2.1+, we can choose to enroll the content in a specific lifecycle (i.e. publication plugin) based on context parameters. We have 3 parameters we can use to define contexts :
Remote User : The current user who create/edit the content.
Current sitename : The Site from where the content is created (not the storage but the navigation)
Node : The node which we want to enroll.
From these parameters, we can easily connect and define contexts based on :
Membership : Does the current user have this membership ?
Site : On this particular site, we want to enroll contents in a specific lifecycle
Path : We can enroll content in lifecycles based on their path (from the Node).
Type of content : We can enroll content in lifecycles based on their nodetype (from the Node).
Because each site has a content storage (categories + physical storage), we can choose the right lifecycle for the right storage/site. We can have conflicts on contexts and we will avoid that by setting a priority (less is best)
Example : Different Contexts
<external-component-plugins>
<target-component>org.exoplatform.services.wcm.publication.PublicationManager</target-component>
<component-plugin>
<name>AddContext</name>
<set-method>addContext</set-method>
<type>org.exoplatform.services.wcm.publication.context.ContextPlugin</type>
<init-params>
<object-param>
<name>contexts</name>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig">
<field name="contexts">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig$Context">
<field name="name"><string>contextdefault</string></field>
<field name="priority"><string>200</string></field>
<field name="lifecycle"><string>lifecycle1</string></field>
</object>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig$Context">
<field name="name"><string>context1</string></field>
<field name="priority"><string>100</string></field>
<field name="lifecycle"><string>lifecycle1</string></field>
<field name="membership"><string>*:/platform/web-contributors</string></field>
<field name="site"><string>acme</string></field>
<field name="path"><string>repository:collaboration:/sites content/live/acme/categories</string></field>
</object>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig$Context">
<field name="name"><string>context2</string></field>
<field name="priority"><string>100</string></field>
<field name="lifecycle"><string>lifecycle1</string></field>
<field name="site"><string>classic</string></field>
</object>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig$Context">
<field name="name"><string>context3</string></field>
<field name="priority"><string>80</string></field>
<field name="lifecycle"><string>lifecycle3</string></field>
<field name="membership"><string>manager:/company/finances</string></field>
<field name="path"><string>repository:collaboration:/documents/company/finances</string></field>
</object>
<object type="org.exoplatform.services.wcm.publication.context.impl.ContextConfig$Context">
<field name="name"><string>context4</string></field>
<field name="priority"><string>50</string></field>
<field name="lifecycle"><string>lifecycle4</string></field>
<field name="memberships">
<collection type="java.util.ArrayList">
<value><string>manager:/CA/communicationDG</string></value>
<value><string>manager:/CA/alerteSanitaire</string></value>
<value><string>manager:/CA/alerteInformatique</string></value>
<value><string>manager:/CA/informations</string></value>
</collection>
</field>
<field name="path"><string>repository:collaboration:/documents/company/finances</string></field>
<field name="nodetype"><string>exo:article</string></field>
</object>
</value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins>
The logic is very simple. When we create a content, we will go lifecycle by lifecycle starting with the better priority :
context4 is the most important (priority=50) : we will enroll the content in the lifecycle 'lifecycle4' if :
the content creator has the 'manager:/company/finances' membership
the content is stored in 'repository:collaboration:/documents/company/finances' or any subfolders
the content is a 'exo:article'
if not we will continue with context3
etc
We can see that context1 and context2 have the same priority. We can do that if the contexts are different (ex : different sites)
It's important to notice that contexts will be used only when the content is created and when we want to enroll it in a lifecycle for the first time. Once we have the corresponding lifecycle, we will set the lifecycle inside the content (see New authoring Mixin) and the context service will not be called again for this content.
<nodeType hasOrderableChildNodes="false" isMixin="true" name="publication:authoring" primaryItemName="">
<propertyDefinitions>
<propertyDefinition autoCreated="false" mandatory="false" multiple="false" name="publication:lastUser" onParentVersion="IGNORE" protected="false" requiredType="String">
<valueConstraints/>
</propertyDefinition>
<propertyDefinition autoCreated="false" mandatory="false" multiple="false" name="publication:lifecycle" onParentVersion="IGNORE" protected="false" requiredType="String">
<valueConstraints/>
</propertyDefinition>
</propertyDefinitions>
</nodeType>
When adding the content in a lifecycle, we set the publication:lifecycle_ property with the corresponding lifecycle.
A content can be in one lifecycle only.
Each time, we change from one state to another, we set the user who changed the state in publication:lastUser_.
By adding this mixin to contents, we can access contents by simple queries based on the current user profile. For example:
All my draft contents
query : select * from nt:base where publication:currentState='draft' and publication:lastUser='benjamin';
All the contents I have to approve
call : PublicationManager.getLifecycles('benjamin', 'approved') => returns lifecycles where I can go to the 'approved' state.
query : select * from nt:base where publication:currentState='pending' and (publication:lifecycle='lifecycle1' or publication:lifecycle='lifecycle3');
All the content that will be published tomorrow
query : select * from nt:base where publication:currentState='staged' and publication:startPublishedDate>'xxxx';