Extending the Multi Site Manager

You are reading the AEM 6.1 version of Extending the Multi Site Manager.
This documentation is also available for the following versions:  AEM 6.2  AEM 6.0  AEM 5.6.1  AEM 5.6  CQ 5.5  CQ 5.4 

This page helps you extend the functionalities of the Multi Site Manager:

  • Learn about the main members of the MSM Java API. 
  • Create a new synchronisation action that can be used in a rollout configuration.
  • Remove the "Chapters" step in the Create Site wizard.
  • Modify the default language and country codes.

This page should be read in conjunction with Managing Blueprints and Live Copies.

Caution

The Multi Site Manager and its API are only meant to be used on an author environment.

Overview of the Java API

The Multisite Management Java API consists of the following package:

The main MSM API objects interact as follows:

Blueprint

A Blueprint defines the pages from which a live copy can inherit content. The use of a blueprint is optional but is required to push modifications to live copies that are inheriting from this blueprint or to define the default rollout configuration for the live copy that is in relation with the blueprint.

LiveCopy

A LiveCopy is the configuration of the relationship (LiveRelationship) between a page and its blueprint page. Use the LiveCopy class to access to the path of the page, the path of the blueprint page, the Rollout Configs, and whether child pages are also included in the LiveCopy.

LiveRelationship

The LiveRelationship represents the relationship between a resource and a blueprint. LiveRelationship objects provide access to the Rollout Configs, LiveCopy, and LiveStatus objects of the relationship. It also provides access to the paths of the target and source pages.

For example, a Live Copy is created in /content/copy from the blueprint at /content/geometrixx. The resources /content/geometrixx/en/jcr:content and /content/copy/en/jcr:content form a relationship. 

LiveStatus

LiveStatus objects provide access to the runtime status of a LiveRelationship. Use to query the synchronization status of a Live Copy.

LiveAction

A LiveAction is an action that is executed on each resource that is involved in the rollout.

LiveActionFactory

Creates LiveAction objects given a LiveAction configuration. Configurations are stored as resources in the repository.

Creating a New Synchronization Action

Create custom synchronisation actions to use with your Rollout Configs. Create a syncrhonization action when the installed actions do not meet your specific application requirements. To do so, create two classes:

The LiveAction class is not registered as an OSGi service. Typically, the LiveAction class is used only by one LiveActionFactory so it is convenient to define the LiveAction class as a static nested class of the LiveActionFactory class.

LiveAction classes include the following methods:

  • getName: Returns the name of the action The name is used to refer to the action, for example in rollout configurations.
  • execute: Performs the tasks of the action.

LiveActionFactory classes include the following members:

  • LIVE_ACTION_NAME: A field that contains the name of the associated LiveAction. This name must coincide with the value that is returned by the getName method of the LiveAction class.
  • createAction: Creates an instance of the LiveAction. The optional Resource parameter can be used to provide configuration information.
  • createsAction: Returns the name of the associated LiveAction.

Note that when creating a rollout configuration, the name of the LiveAction is the name used for the cq:LiveSyncAction node that you add to a rollout configuration. (See Creating a Rollout Configuration.)

Accessing the LiveAction Configuration Node

Use the LiveAction configuration node in the repository to store information that affects the runtime behaviour of the LiveAction instance. The node in the repository that stores the LiveAction configuration is available to the LIveActionFactory object at runtime. Therefore, you can add properties to the configuration node to and use them in your LiveActionFactory implementation as needed.

For example, a LiveAction needs to store the name of the blueprint author. A property of the configuration node includes the property name of the blueprint page that stores the information. At runtime, the LiveAction retrieves the property name from the configuration, then obtains the property value.

The parameter of the LiveActionFactory.createAction method is a Resource object. This Resource object represents the cq:LiveSyncAction node for this Live Action in the Rollout Config. (See Creating a Rollout Configuration.) As usual when using a configuration node, you should adapt it to a ValueMap object:

public LiveAction createAction(Resource resource) throws WCMException {
        ValueMap config;
        if (resource == null || resource.adaptTo(ValueMap.class) == null) {
            config = new ValueMapDecorator(Collections.<String, Object>emptyMap());
        } else {
            config = resource.adaptTo(ValueMap.class);
        }
        return new MyLiveAction(config, this);
}
        

Code samples are intended for illustration purposes only.

Accessing Target Nodes, Source Nodes, and the LiveRelationship

The following objects are provided as parameters of the execute method of the LiveAction object:

  • A Resource object that represents the source of the Live Copy.
  • A Resource object that represents the target of the Live Copy.
  • The LiveRelationship object for the Live Copy.
  • The autoSave value indicates whether your LiveAction should save changes that are made to the repository.
  • The reset value indicates the Rollout reset mode.

From these objects you can obtain all of the information about the LiveCopy. You can also use the Resource objects to obtain ResourceResolver, Session, and Node objects. These objects are useful for manipulating repository content:

In the first line of the following code, source is the Resource object of the source page:

ResourceResolver resolver = source.getResourceResolver();
Session session = resolver.adaptTo(javax.jcr.Session.class);
Node sourcenode = source.adaptTo(javax.jcr.Node.class);

Note

The Resource arguments may be Null or Resources objects that do not adapt to Node objects, such as  NonExistingResource objects.

Creating and Using a Simple LiveActionFactory Class

Follow the procedures in this section to develop a LiveActionFactory and use it in a Rollout Configuration. The procedures use Maven and Eclipse to develop and deploy the LiveActionFactory:

  1. Create the maven project and import it into Eclipse.
  2. Add dependencies to the POM file.
  3. Implement the LiveActionFactory inteface and deploy the OSGi bundle.
  4. Create the rollout configuration.
  5. Create the Live Copy.

Note

The Maven project and the source code of the Java class is available in the https://github.com/Adobe-Marketing-Cloud/experiencemanager-java-msmrollout public Git repository.

Create the Maven Project

The following procedure requires that you have added the adobe-public profile to your Maven settings file.

  1. Open a terminal or command-line prompt and change the directory to the location to create the project.

  2. Enter the following command:

    mvn archetype:generate -DarchetypeGroupId=com.day.jcr.vault -DarchetypeArtifactId=multimodule-content-package-archetype -DarchetypeVersion=1.0.0 -DarchetypeRepository=adobe-public-releases
            

    Code samples are intended for illustration purposes only.

  3. Specify the following values at interactive prompt:

    • groupId: com.adobe.example.msm 
    • artifactId: MyLiveActionFactory
    • version: 1.0-SNAPSHOT
    • package: MyPackage
    • appsFolderName: myapp 
    • artifactName: MyLiveActionFactory package  
    • packageGroup: myPackages
  4. Start Eclipse and import the Maven project.

Add Dependencies to the POM File

Add dependencies so that the Eclipse compiler can reference the classes that are used in the LiveActionFactory code.

  1. From the Eclipse Project Explorer, open the MyLiveActionFactory/pom.xml file.

  2. In the editor, click the pom.xml tab and locate the project/dependencyManagement/dependencies section.

  3. Add the following XML inside the dependencyManagement element and then save the file.

    	<dependency>
    		<groupId>com.day.cq.wcm</groupId>
    		<artifactId>cq-msm-api</artifactId>
    		<version>5.6.2</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.api</artifactId>
    		<version>2.4.3-R1488084</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq.wcm</groupId>
    		<artifactId>cq-wcm-api</artifactId>
    		<version>5.6.6</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.commons.json</artifactId>
    		<version>2.0.6</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq</groupId>
    		<artifactId>cq-commons</artifactId>
    		<version>5.6.4</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.jcr.jcr-wrapper</artifactId>
    		<version>2.0.0</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq</groupId>
    		<artifactId>cq-commons</artifactId>
    		<version>5.6.4</version>
    		<scope>provided</scope>
    	</dependency>
            

    Code samples are intended for illustration purposes only.

  4. Open the POM file for the bundle from Project Explorer at MyLiveActionFactory-bundle/pom.xml.

  5. In the editor, click the pom.xml tab and locate the project/dependencies section. Add the following XML inside the dependencies element and then save the file:

    	<dependency>
    		<groupId>com.day.cq.wcm</groupId>
    		<artifactId>cq-msm-api</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.api</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq.wcm</groupId>
    		<artifactId>cq-wcm-api</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.commons.json</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq</groupId>
    		<artifactId>cq-commons</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.apache.sling</groupId>
    		<artifactId>org.apache.sling.jcr.jcr-wrapper</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>com.day.cq</groupId>
    		<artifactId>cq-commons</artifactId>
    	</dependency>
            

    Code samples are intended for illustration purposes only.

Implement LiveActionFactory

The following LiveActionFactory class implements a LiveAction that logs messages about the source and target pages, and copies the cq:lastModifiedBy property from the source node to the target node. The name of the Live Action is exampleLiveAction.

  1. In the Eclipse Project Explorer, right-click the MyLiveActionFactory-bundle/src/main/java/com.adobe.example.msm package and click New > Class. For the Name, enter ExampleLiveActionFactory and then click Finish.

  2. Open the ExampleLiveActionFactory.java file, replace the content with the following code, and save the file.

    package com.adobe.example.msm;
    
    import java.util.Collections;
    
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Property;
    import org.apache.felix.scr.annotations.Service;
    import org.apache.sling.api.resource.Resource;
    import org.apache.sling.api.resource.ResourceResolver;
    import org.apache.sling.api.resource.ValueMap;
    import org.apache.sling.api.wrappers.ValueMapDecorator;
    import org.apache.sling.commons.json.io.JSONWriter;
    import org.apache.sling.commons.json.JSONException;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.jcr.Node;
    import javax.jcr.RepositoryException;
    import javax.jcr.Session;
    
    import com.day.cq.wcm.msm.api.ActionConfig;
    import com.day.cq.wcm.msm.api.LiveAction;
    import com.day.cq.wcm.msm.api.LiveActionFactory;
    import com.day.cq.wcm.msm.api.LiveRelationship;
    import com.day.cq.wcm.api.WCMException;
    
    @Component(metatype = false)
    @Service
    public class ExampleLiveActionFactory implements LiveActionFactory<LiveAction> {
    	@Property(value="exampleLiveAction")
    	static final String actionname = LiveActionFactory.LIVE_ACTION_NAME;
    
    	public LiveAction createAction(Resource config) {
    		ValueMap configs;
    		/* Adapt the config resource to a ValueMap */
            if (config == null || config.adaptTo(ValueMap.class) == null) {
                configs = new ValueMapDecorator(Collections.<String, Object>emptyMap());
            } else {
                configs = config.adaptTo(ValueMap.class);
            }
    		
    		return new ExampleLiveAction(actionname, configs);
    	}
    	public String createsAction() {
    		return actionname;
    	}
    	/*************  LiveAction ****************/
    	private static class ExampleLiveAction implements LiveAction {
    		private String name;
    		private ValueMap configs;
    		private static final Logger log = LoggerFactory.getLogger(ExampleLiveAction.class);
    
    		public ExampleLiveAction(String nm, ValueMap config){
    			name = nm;
    			configs = config;
    		}
    
    		public void execute(Resource source, Resource target,
    				LiveRelationship liverel, boolean autoSave, boolean isResetRollout)
    						throws WCMException {
    			
    			String lastMod = null;
    			
    			log.info(" *** Executing ExampleLiveAction *** ");
    			
    			/* Determine if the LiveAction is configured to copy the cq:lastModifiedBy property */
    			if ((Boolean) configs.get("repLastModBy")){
    				
    				/* get the source's cq:lastModifiedBy property */
    				if (source != null && source.adaptTo(Node.class) !=  null){
    					ValueMap sourcevm = source.adaptTo(ValueMap.class);
    					lastMod = sourcevm.get(com.day.cq.wcm.api.NameConstants.PN_PAGE_LAST_MOD_BY, String.class);	
    				}
    				
    				/* set the target node's la-lastModifiedBy property */
    				Session session = null;
    				if (target != null && target.adaptTo(Node.class) !=  null){
    					ResourceResolver resolver = target.getResourceResolver();
    					session = resolver.adaptTo(javax.jcr.Session.class);
    					Node targetNode;
    					try{
    						targetNode=target.adaptTo(javax.jcr.Node.class);
    						targetNode.setProperty("la-lastModifiedBy", lastMod);				
    						log.info(" *** Target node lastModifiedBy property updated: {} ***",lastMod);
    					}catch(Exception e){
    						log.error(e.getMessage());
    					}	
    				}
    				if(autoSave){
    					try {
    						session.save();
    					} catch (Exception e) {
    						try {
    							session.refresh(true);
    						} catch (RepositoryException e1) {
    							e1.printStackTrace();
    						}
    						e.printStackTrace();
    					} 
    				}			
    			}
    		}
    		public String getName() {
    			return name;
    		}
    
    		/************* Deprecated *************/
    		@Deprecated
    		public void execute(ResourceResolver arg0, LiveRelationship arg1,
    				ActionConfig arg2, boolean arg3) throws WCMException {		
    		}
    		@Deprecated
    		public void execute(ResourceResolver arg0, LiveRelationship arg1,
    				ActionConfig arg2, boolean arg3, boolean arg4)
    						throws WCMException {		
    		}
    		@Deprecated
    		public String getParameterName() {
    			return null;
    		}
    		@Deprecated
    		public String[] getPropertiesNames() {
    			return null;
    		}
    		@Deprecated
    		public int getRank() {
    			return 0;
    		}
    		@Deprecated
    		public String getTitle() {
    			return null;
    		}
    		@Deprecated
    		public void write(JSONWriter arg0) throws JSONException {
    		}
    	}
    }
    
            

    Code samples are intended for illustration purposes only.

  3. Using the terminal or command prompt, change the directory to the MyLiveActionFactory directory (the Maven project directory). Then, enter the following command:

    mvn -PautoInstallPackage clean install
            

    Code samples are intended for illustration purposes only.

    The CQ error.log file should indicate that the bundle is started. (http://localhost:4502/system/console/status-slinglogs)

    13.08.2013 14:34:55.450 *INFO* [OsgiInstallerImpl] com.adobe.example.msm.MyLiveActionFactory-bundle BundleEvent RESOLVED
    13.08.2013 14:34:55.451 *INFO* [OsgiInstallerImpl] com.adobe.example.msm.MyLiveActionFactory-bundle BundleEvent STARTING
    13.08.2013 14:34:55.451 *INFO* [OsgiInstallerImpl] com.adobe.example.msm.MyLiveActionFactory-bundle BundleEvent STARTED
    13.08.2013 14:34:55.453 *INFO* [OsgiInstallerImpl] com.adobe.example.msm.MyLiveActionFactory-bundle Service [com.adobe.example.msm.ExampleLiveActionFactory,2188] ServiceEvent REGISTERED
    13.08.2013 14:34:55.454 *INFO* [OsgiInstallerImpl] org.apache.sling.audit.osgi.installer Started bundle com.adobe.example.msm.MyLiveActionFactory-bundle [316]
    
            

    Code samples are intended for illustration purposes only.

Create the Example Rollout Configuration

Create the MSM rollout configuration that uses the LiveActionFactory that you created.

  1. In your web browser, open the Tools console. (http://localhost:4502/miscadmin#/etc)

  2. Select the Tools/MSM/Rollout Configurations folder and click New > New Page and provide values for the Rollout Configuration properties:

    • Title: Example Rollout Configuration
    • Name: examplerolloutconfig
    • Select RolloutConfig Template.
    file
  3. Click Create.

  4. Open the rollout configuration that you created and then click Edit.

  5. In the Rollout Config dialog, use the Sync-Trigger drop-down menu to select On Activation, and then click OK.

    file

Add the Live Action to the Example Rollout Configuration

Configure the rollout configuration that you created in the previous procedure so that it uses the ExampleLiveActionFactory class. 

  1. Open CRXDE Lite. (http://localhost:4502/crx/de)

  2. Select the jcr:content node below the /etc/msm/rolloutconfigs/examplerolloutconfig/jcr:content node.

  3. Click Create > Create Node, configure the following node properties and click OK:

    • Name: exampleLiveAction
    • Typecq:LiveSyncAction
    file
  4. Click Save All.

  5. Select the exampleLiveAction node and add the following property:

    • Name: repLastModBy
    • Type: Boolean
    • Value: true

    This property indicates to the ExampleLiveAction class that the cq:LastModifiedBy property should be replicated from the source to the target node.

  6. Click Save All.

Create the Live Copy

Create a Live Copy of a branch of the Geometrixx Demo Site using your rollout configuration. Activate a page of the source branch and observe the log messages that the LiveAction class generates.

  1. Open the Websites console. (http://localhost:4502/siteadmin#/content)

  2. Select the Websites folder and click New > New Live Copy. 

  3. For the LiveCopyFrom property, enter /content/geometrixx/en/products.

  4. On the Sync Config tab, for the Rollout Configs property, click Add Item and then select Example Rollout Configuration. Click Create.

    The Products site is created.

  5. Activate the Websites/Geometrixx Demo Site/English Products page. For each page and component that is activated, the log file contains messages similar to the following example:

    16.08.2013 10:53:33.055 *INFO* [Thread-444535] com.adobe.example.msm.ExampleLiveActionFactory$ExampleLiveAction  *** ExampleLiveAction has been executed.*** 
    16.08.2013 10:53:33.055 *INFO* [Thread-444535] com.adobe.example.msm.ExampleLiveActionFactory$ExampleLiveAction  *** Target node lastModifiedBy property updated: admin ***
            

    Code samples are intended for illustration purposes only.

Removing the "Chapters" Step in the "Create Site" Wizard

In some cases, the Chapters selection is not required in the Create Site wizard but only the Languages selection. To remove this step in the default Geometrixx blueprint:

  1. In CRX Explorer, remove the node /etc/blueprints/geometrixx/jcr:content/dialog/items/tabs/items/tab_chap.

  2. Navigate to /libs/wcm/msm/templates/blueprint/defaults/livecopy_tab/items and create a new node. Name = chapters, Type = cq:Widget.

  3. Add following properties to the new node:

    • Name = name, Type = String, Value = msm:chapterPages

    • Name = value, Type = String, Value = all

    • Name = xtype, Type = String, Value = hidden

Changing language names and default countries

AEM uses a default set of language and country codes. 

  • The default language code is the lower-case, two-letter code as defined by ISO-639-1.
  • The default country code is the lower-case or upper-case, two-letter code as defined by ISO 3166.

MSM uses a stored list of language and country codes to determine the name of the country that is associated with the name of the language version of your page. You can change the following aspects of the list if required:

  • Language titles
  • Country names
  • Default countries for languges (for codes such as "en,""de," and so on)

The language list is stored below the  /libs/wcm/core/resources/languages node. Each child node represents a language or a language-country:

  • The name of the node is the languge code (such as en or de), or the language_country code (such as en_us or de_ch).
  • The language property of the node stores the full name of the language for the code.
  • The country property of the node stores the full name of the country for the code.
  • When the node name consists only of a language code (such as en), the country property is *, and an additional defaultCountry property stores the code of the language-country to indicate the country to use.
file

To modify the languages:

  1. Open CRXDE Lite in your web browser. (http://localhost:4502/crx/de)

  2. Select the /apps folder and click Create > Create Folder. Name the folder wcm.

  3. Repeat the previous step to create the /apps/wcm/core folder tree. Create a node of type sling:Folder in core called resources.

    file
  4. Right-click the /libs/wcm/core/resources/languages node and click Copy.

  5. Right-click the /apps/wcm/core/resources folder and click Paste. Modify the child nodes as required.

  6. Click Save All.

  7. Click Tools > Server Config to open the Web Console. Click OSGi > Configuration.

  8. Locate and click Day CQ WCM Language Manager, and change the value of Language List to /apps/wcm/core/resources/languages, then click Save.

    file

Configuring MSM Locks on Page Properties (Touch-Optimized UI)

When creating a custom page property you may need to consider whether the new property should be eligible for roll out to any live copies.

For example, if two new page properties are being added:

  • Contact Email:
    • this property is not required to be rolled out, as it will be different in each country (or brand, etc).
  • Key Visual Style:
    • the project requirement is that this property is to be rolled out as it is (usually) common to all countries (or brands, etc).

Then you need to ensure that:

  • Contact Email:
  • Key Visual Style:
    • make sure you are not allowed to edit this property in the touch-optimized UI unless inheritance is cancelled, also that you can then reinstate inheritance; this is controlled by clicking the chain/broken-chain links that toggle to indicate the status of the connection.

Whether a page property is subject to roll out and therefore, subject to cancelling/reinstating inheritance when editing, is controlled by the dialog property:

  • cq-msm-lockable
    • is applicable to items in a touch-optimized UI dialog
    • will create the chain-link symbol in the dialog
    • only allows editing if inheritance is cancelled (the chain-link is broken)
    • Type: String
    • Value: holds the name of the property under consideration (and is comparable to the value of the property name; for example, see
      /libs/foundation/components/page/cq:dialog/content/items/tabs/items/basic/items/column/items/title/items/title

When cq-msm-lockable has been defined, breaking/closing the chain will interact with MSM in the following way:

  • if the value of cq-msm-lockable is:
    • Relative (e.g. myProperty or ./myProperty)
      • it will add and remove the property from cq:propertyInheritanceCancelled.
      • MSM does not operate with deep properties (e.g. ./image/fileReference), even though the dialog’s logic does. If the chain is opened a rollout of the page will overwrite ./image/fileReference, as the rollout of the image node will not "walk" up to the parent node to check cq:propertyInheritanceCancelled.
    • Absolute (e.g. /image)
      • breaking the chain will cancel inheritance by adding the cq:LiveSyncCancelled mixin to ./image and setting cq:isCancelledForChildren to true.
      • closing the chain will revert inheritance.

Note

When you re-enable inheritance, the live copy page property is not automatically synchronized with the source property. You can manually request a synchronization if this is required.

​