Pre-Introduction
Before the deployer storage layer (JPA) was introduced in the 2011 release, the most common way to extend the deployer was, well, extending the deployer J.
Basically you’d extend the Modules and Processors available within the cd_deployer_conf.xml configuration file. Nowadays it is more common to find Storage Extensions for CDN integrations, search index creation etc, but that doesn’t mean that you cannot still extend the deployer… in fact in some cases you still need to extend it, like for example to extend the metadata of a component when it is published. Anyway this post will contain all the information you need to implement your Deployer extensions together with a Sample Code Download at the end. Please read through it and you’ll find not only an example on how to make your deployer extensions, extendable, but also an idea on how your Storage extensions (or any other delivery extensions) can be implemented.
This is something I have had for a while, but never really thought of sharing. However after attending the MVP retreat 2013, I learnt that I have to SHARE MORE!, so here it is. I hope you find it useful and even inspirational for your future deployer/storage extensions
Note: The project references the 2011 libraries (jar files). It should also work (maybe with minor changes) with 2013.
Introduction
This is always a tough task to extend the default Tridion deployer functionality. To ease this task the Custom Deployer Extension comes to scene. This extension can be considered a framework that allows the developers to easily add functionality to the default deployment mechanism. Some scenarios where the deployer could be extended are:
- Updating Front-end Search Services Information
- Adding Custom Metadata to the components/pages at deploy time
- Reallocating Dynamic Component presentations at deploy time
- Reallocating Binaries at deploy time
- Email Notification after specific publishing tasks
- Other External Systems integration (update external databases, as an example)
- ??
Installation, Configuration & Scalability
The Custom Deployer Framework (extension) is built in a way that most of the actions/logic to be executed during the deployment process can be configured by using xml files. Once the framework is installed, adding new functionality is a pretty straight-forward task. Basically the framework executes what is called “Custom Actions” that can be easily configured within the main configuration xml file.
These “Custom Actions” can be configured in such a way that can be executed either before the actual deployment process takes place or after, allowing the logic to be executed when and only when it’s needed.
The framework consists in one single jar file : sdl.custom.deployer.jar. Within this file we can find the following packages:
- com.tridion.deployer.extensions: contains the main Processor classes both, for deployment and undeployment.
- com.tridion.deployer.extensions.base: contains the base classes with the common functionality needed to execute the actions.
- com.tridion.deployer.extensions.behaviour: contains the Interface that defines the behavior of the actions and a Comparator that allows to execute those actions in an specific order
- com.tridion.deployer.extensions.examples: example classes implementing the framework
- com.tridion.deployer.extensions.utils: some common utilities to be used in the “Action” classes
Installation
To install the extension, the jar file (sdl.custom.deployer.jar) needs to be copied into the lib folder within the Content Delivery installation path, by default: Tridion_Installation_Path/lib.
Once the file is located in the lib folder within the Content Delivery installation location the cd_deployer_conf.xml needs to be updated to enable the new extension in the following way:
Within the ‘Processors’ node, both the Action=”Deploy” and the Action=”Undeploy” processors need to be updated:
<Processors>
<!-- <Processor Action="Deploy" Class="com.tridion.deployer.Processor"> -->
<Processor Action="Deploy" Class="com.tridion.deployer.extensions.CustomDeployProcessor">
<Module Type="SchemaDeploy" Class="com.tridion.deployer.modules.SchemaDeploy"/>
<Module Type="PageDeploy" Class="com.tridion.deployer.modules.PageDeploy">
<Transformer Class="com.tridion.deployer.TCDLTransformer"/>
</Module>
<Module Type="BinaryDeploy" Class="com.tridion.deployer.modules.BinaryDeploy"/>
<Module Type="ComponentDeploy" Class="com.tridion.deployer.modules.ComponentDeploy"/>
<Module Type="TemplateDeploy" Class="com.tridion.deployer.modules.TemplateDeploy"/>
<!-- This module enables deployment of taxonomies -->
<Module Type="TaxonomyDeploy" Class="com.tridion.deployer.modules.TaxonomyDeploy"/>
<Module Type="ComponentPresentationDeploy" Class="com.tridion.deployer.modules.ComponentPresentationDeploy">
<Transformer Class="com.tridion.deployer.TCDLTransformer"/>
</Module>
<Param Name="CustomDeployerConfigFilePath" Value="PATH\cd_deployer_custom_deploy.xml" />
</Processor>
<!-- <Processor Action="Undeploy" Class="com.tridion.deployer.Processor"> -->
<Processor Action="Undeploy" Class="com.tridion.deployer.extensions.CustomUndeployProcessor">
<Module Type="PageUndeploy" Class="com.tridion.deployer.modules.PageUndeploy"/>
<Module Type="ComponentPresentationUndeploy" Class="com.tridion.deployer.modules.ComponentPresentationUndeploy"/>
<!-- This module enables the undeploy of taxonomies -->
<Module Type="TaxonomyUndeploy" Class="com.tridion.deployer.modules.TaxonomyUndeploy"/>
<Param Name="CustomDeployerConfigFilePath" Value="PATH\cd_deployer_custom_undeploy.xml" />
</Processor>
</Processors>
The changes to be considered are that a new class is now used as the “Deploy” and “Undeploy” processors: com.tridion.deployer.extensions.CustomUndeployProcessor and com.tridion.deployer.extensions.CustomUndeployProcessor respectively and a configuration file location file is added per each of those: cd_deployer_custom_deploy.xml and cd_deployer_custom_undeploy.xml
Once the configuration file (cd_deployer_conf.xml) is updated we are ready to use the new framework. To explain how the framework works we’ll be using the Example Log extension available in the package. As mentioned before the framework executes what is called “Custom Actions” which are Java Classes that extend a certain java class and implements a certain Interface, both available in the framework.
The new “Custom” processors will dynamically load those “Custom Actions” based on the information provided on each of the configuration files, one for deployment and another one for undeployment.
Both configuration files follow the same structure and we’ll be using the “deploy” configuration file as the example to understand how to add new functionality to our default deployment process.
cd_deployer_custom_deploy.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- CustomDeployerConfigFilePath -->
<actions>
<action order="1" active="true" type="pre" errorHandling="true" class="com.tridion.deployer.extensions.examples.SampleLogAction">
<description>Logs</description>
<config-location>F:\PROJECTS\QUEST\DEV\Tridion Custom Deployer\config\samplelogaction-config.xml</config-location>
</action>
<action order="2" active="true" type="post" class="com.tridion.deployer.extensions.examples.SampleLogAction">
<description>Logs</description>
</action>
<action order="1" active="true" type="pre" class="com.tridion.deployer.extensions.examples.SampleLogAction">
<description>Logs</description>
<config-location>F:\PROJECTS\QUEST\DEV\Tridion Custom Deployer\config\samplelogaction-config2.xml</config-location>
</action>
</actions>
As shown in the xml file, it contains several action nodes, which will be executed before (“pre”) or after (“post”) the deployment process in the given order (“order”).
For each action we can specify:
- Order: determines in which position the action is executed.
- Active: determines whether the action is active or not. (it it’s not, it won’t be executed)
- Type: “pre” or “post” determining when to execute the action. i.e. before or after the default deployment process.
- Class: the class containing the actual java logic. This class must extend com.tridion.deployer.extensions.base.CustomAction and must implement com.tridion.deployer.extensions.behaviour.ICustomAction
- ErrorHandling: true or false, depending on this value the method “executeOnError” will be exectuted when an error occurs during the default deployment process. This feature is meant to be used only for “pre” actions and allows to execute specific logic in case the default deployment process fails.
- Description: a brief description for logging purposes.
- Config-location: full path to the configuration xml file for the current action, allowing the possibility of having separate/different configuration files per action. We can have also different configuration files for the same action.
Every Class needs to implement at least the two following methods:
public void executeAction() throws ProcessingException
The actual logic for the action
public void executeOnError() throws ProcessingException
The logic to be executed if something fails
Based on this configuration file the whole deployment process would be:
The example “Action” com.tridion.deployer.extensions.examples.SampleLogAction uses a configuration file for Deployment located at: com.tridion.deployer.extensions.examples named as : samplelogaction-config.xml . This action simply writes a log per each “message” node within the configuration file:
<?xml version="1.0" encoding="utf-8"?>
<config>
<message>SampleLogAction: Hello World!</message>
<message>SampleLogAction: Hello World 2!</message>
<message>SampleLogAction: Hello Tridion</message>
<message>SampleLogAction: Hello Content</message>
</config>
The example “Action” com.tridion.deployer.extensions.examples.SampleLogAction code is:
public class SampleLogAction extends CustomAction implements ICustomAction {
/* (non-Javadoc)
* @see com.tridion.deployer.extensions.base.CustomAction#executeAction()
*/
public void executeAction() throws ProcessingException {
try {
log.info("Starts Custom Action: "+this.getClass().getCanonicalName());
NodeList sampleMessages=null;
if(getConfig()!=null){
sampleMessages = XPathAPI.selectNodeList(getConfig(), "//message");
}
if(sampleMessages!=null){
int length = sampleMessages.getLength();
for(int i=0; i<length;i++){
String currentDummyMessage = sampleMessages.item(i).getTextContent();
log.info(currentDummyMessage);
}
}else{
log.info("No messages configured!");
}
} catch (TransformerException e) {
throw new ProcessingException(e);
}finally{
log.info("Ends Custom Action: "+this.getClass().getCanonicalName());
}
}
/* (non-Javadoc)
* @see com.tridion.deployer.extensions.base.CustomAction#executeOnError()
*/
public void executeOnError() throws ProcessingException{
log.info("Custom Action ["+ this.getClass().getCanonicalName() +"] executeOnError method. Error Handling: "+this.isErrorHandlingEnabled());
}
}
The code iterates over the “message” nodes on the configuration file for the specific Action (samplelogaction-config.xml) and will write out a log line per node containing the text within each of those.
Util methods available on the Action classes:
- getConfig() : Returns the XML document Object (org.w3c.Document) with the config file information related to the current action.
- getUtils(): Set of utilities available on the com.tridion.deployer.extensions.CustomUtils class
- getSchemasDocument():Returns the XML document Object (org.w3c.Document) containing the schemas.xml data in the current package
- getComponentsDocument():Returns the XML document Object (org.w3c.Document) containing the components.xml data in the current package
- getPagesDocument():Returns the XML document Object (org.w3c.Document) containing the pages.xml data in the current package
- getComponentPresentationsDocument():Returns the XML document Object (org.w3c.Document) containing the component_presentations.xml data in the current package
- getBinariesDocument():Returns the XML document Object (org.w3c.Document) containing the binaries.xml data in the current package
- actionIsDeploy(): Determines if the current action is deploy. If false, the action is undeploy.
- getTransportPackageLocation(): Returns the full path to the transport package when unpacked.
- getTransportPackageFolderName(): Returns the folder name of the Transport Package.
- cloneTransportFolder(): creates a copy of the Trasnport package folder in the given location.
- deleteDir(): deletes the given directory
- copyDirectory(): copies the given directory to the given location.
- getSchemaURIByTitle() : returns the Schema URI, based on its Title
- getComponentsBySchemaURI() returns a list of Documents (org.w3c.Document) with the xml content per each Component based on the given Schema URI.
- nodeToString() converts a Node to a String.
- stringToNode() converts a String to Node.
Real-World Scenario Setup
One of our customers wanted to extend the deployer in such a way they could create xml files for Endeca (A Search Engine) to be able to index those based on the information coming from the pages in Tridion. So, every time a page is published, an xml file is created containing specific information to be available for searching in the published website. The requirements here are:
- For every page published, an xml file needs to be created
- The file will contain certain information available in the page metadata
- For every page unpublished, an xml file needs to be created
- The file will contain enough information to delete the specific entry from Endeca
This requirements, based on the Deployer mechanism where translated into:
- Custom Deployer Actions (On Deploy)
- Generate Endeca XML Files based on Page metadata in a temporary location (we cannot deploy those files until the default process action is successfully finished)
- If the default process fails, the temporary files, need to be deleted
- Once the default process has successfully finished, Move the xml files from the temporary location into the Endeca Repository
- Custom Deployer Actions (On Undeploy)
- Generate Endeca XML File to remove the reference from Endeca. This file needs to be created in a temporary location and then deleted when the “UnDeploy” action has been successfully finished.
The framework implementation will consist of the following xml files setup:
cd_deployer_custom_deploy.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- CustomDeployerConfigFilePath -->
<actions>
<action order="1" active="true" type="pre" errorHandling="true" class="com.customer.cms.deployer.extensions.actions.GenerateEndecaXMLAction">
<description>Generates Endeca XML Files</description>
<config-location> PATH_TO_CONFIG\generate-endeca-xml-action-config.xml</config-location>
</action>
<action order="1" active="true" type="post" errorHandling="false" class="com.customer.cms.deployer.extensions.actions.DeployEndecaXMLFilesAction">
<description>Deploys Endeca XML Files</description>
<config-location> PATH_TO_CONFIG \deploy-endeca-xml-action-config.xml</config-location>
</action>
</actions>
cd_deployer_custom_undeploy.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- CustomDeployerConfigFilePath -->
<actions>
<action order="1" active="true" type="pre" errorHandling="true" class="com.customer.cms.deployer.extensions.actions.GenerateEndecaXMLAction">
<description>Generates Endeca XML Files</description>
<config-location>PATH_TO_CONFIG\generate-undeploy-endeca-xml-action-config.xml</config-location>
</action>
</actions>
The following actions would be implemented:
com.customer.cms.deployer.extensions.actions.GenerateEndecaXMLAction
com.customer.cms.deployer.extensions.actions.DeployEndecaXMLFilesAction
com.customer.cms.deployer.extensions.actions.GenerateEndecaXMLAction
DEPLOY
The first action will create the xml files for Endeca based on both, the tridion xml files available in the package containing the metadata info for each page. This action will create the files in a temporary location define on its configuration file. If the default deployment process fails, this action will delete that temporary location.
The second action will be executed once the default deployment action has successfully finished and will move the files from the temporary location to the Endeca repository location. If something fails, this class will delete the temporary location also.
UNDEPLOY
The third action will create an xml to notify Endeca in order to remove the entry from the search index once the page is unpublished. If something goes wrong during the default undeployment action, such xml file won’t be generated.
CONSIDERATIONS
The current extension is meant to be the starting point of a deployer extension.
Error handling might be implemented in a different way based on customer’s need
This extension only extends the functionality at a higher level (Processors) but the same approach can be followed at a lower level (Modules)
The CustomUtils comes with some useful methods, but of course, some others could be implemented.
Any Method Implmented on the CustomAction class would be available on each action extending such a class.
Any Method Implemented on the CustomActions would be available on each action by calling
getUtils().newMethod()
Every Action has access to the configuration file defined in the config-location node per each action by calling the getConfig() method. It will return a org.w3c.Document with the configuration.
Jaimito,
ReplyDeleteGood to see you blogging! If only to get that internal MVP thing ;) Say hi to Sr and principal perritos ;)
Irinissima
I am not looking for awards... I just hope some girl find it sexy...
ReplyDeleteGood to see blogger from Tridion....Pls keep us posted Tridion 2011 features...
ReplyDeleteAnyway your blogger theme is nice ;)