Monday, July 2, 2012

Ambient Data Framework in a Nutshell

In this post I want to briefly cover an important part of Tridion that is ignored in several implementations but that is always present because is a key part of most of the Content Delivery related products like UGC, Smart Target, etc.

What is ADF (Ambient Data Framework?

When we refer to ADF we should think about state management, in general words ADF works as a repository of information related to an specific session or to an specific request that can be accessed or updated during a web operation.

How ADF works?

ADF uses a modular design based on Cartridges that are executed depending on dependencies on the input/optput claims configured in the claim processors. ADF will determine the sequence of execution based on dependencies and registration order. Cartridges are used to manipulate state in the form of claims, claims can contain almost any form of data. The only restriction is that if your claim data is used in .Net the data must be serialized from Java to .Net.

Being technology agnostic ADF cannot access to technology specific objects like HttpServletRequest/HttpServletResponse or HttpContext, that is why we need a java filter or a .Net Http Module to start it. Having this in mind depending on the technology we are using we have to register the starting point for ADF as a Java Filter or as a .Net Http Module after that everything would be the same regardless the technology we are using.

Which are the ADF pieces?

Cartridge
A set of Claim Processors grouped sequentially. ADF can contain zero, one or more cartridges.

ClaimProcessor 
A processing entity that will receive the current claim store and will manipulate it. They have 3 important methods that can be implemented. onSessionStart which is executed when a new session starts, onRequestStart at the beginning of each request, onRequestEnd at the ending of each request.

ClaimStore
A set of data in form of claims. It is implemented as a Map object in Java.

Claim
Data being stored, updated or removed from ADF.

Default Web Claims
ADF comes with a set of default claims called Web Claims. Those claims are stored in a claim store called "WebClaims". You can access to that claim store in the following way.

Server Variables: Map variables = (Map)claims.get(WebClaims.SERVER_VARIABLES);
Session Attributes: Map variables = (Map)claims.get(WebClaims.SESSION_ATTRIBUTES);
Request Url: Map variables = (Map)claims.get(WebClaims.REQUEST_FULL_URL);
Request Headers: Map variables = (Map)claims.get(WebClaims.REQUEST_HEADERS);

Those are the most important ones. Please notice that those claims are populated automatically by the ADF initiator (java filter or http module).

 

How ADF is configured?

The main configuration file is cd_ambient_conf.xml which should be located in your class path generally in the classes folder in java or in the bin/config folder in .net

cd_ambient_conf.xml example

<?xml version="1.0" encoding="UTF-8"?>
<Configuration Version="6.1"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="schemas/cd_ambient_conf.xsd">
    <Cartridges>
        <Cartridge File="my_custom_cartridge1_conf.xml"/>
        <Cartridge File="my_custom_cartridge2_conf.xml"/>
    </Cartridges>
</Configuration>

This configuration file contains basic configuration just configuring two cartridges to be executed in sequential order. Each cartridge is configured in its own configuration file.

my_custom_cartridge1_conf.xml

<?xml version="1.0" encoding="UTF-8"?>
<CartridgeDefinition Version="6.1" Uri="taf:extensions:cartridge:mycartridge" Description="My Cartridge"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="schemas/cd_ambient_cartridge_conf.xsd">
    <ClaimDefinitions>
        <ClaimDefinition
                    Uri="taf:extensions:claim:myclaim"
                    Subject="taf:extensions:claim"
                    Scope="SESSION"
                    Description="My Claim" />
    </ClaimDefinitions>
    <ClaimProcessorDefinitions>
        <ClaimProcessorDefinition
                            Uri="taf:extensions:processor:myclaimprocessor"
                            Scope="SESSION" 
                            ImplementationClass="com.tridion.ambientdata.extensions.myclaimprocessor"
                            Description="My claim processor.">
            <RequestStart>
                <InputClaims />
                <OutputClaims>
                    <ClaimDefinition Uri="taf:extensions:claim:myclaim" />
                </OutputClaims>
            </RequestStart>
        </ClaimProcessorDefinition>
    </ClaimProcessorDefinitions>
</CartridgeDefinition>

This cartridge configuration file is defining a SESSION claim called "myclaim" and SESSION claim processor called "myclaimprocessor" which is implemented in the "com.tridion.ambientdata.extensions.myclaimprocessor" class. It is also defining an output claim called my claim which will contain the result of my operations.

Java specific configuration

In java the ADF initiator is configured as a Java filter in the web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    id="WebSite">
    <display-name>WebSite</display-name>
    <description>Web Site</description>

    <filter>
        <filter-name>Ambient Data Framework</filter-name>
        <filter-class>com.tridion.ambientdata.web.AmbientDataServletFilter</filter-class>
    </filter>
   
    <filter-mapping>
        <filter-name>Ambient Data Framework</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

.Net specific configuration

In .Net the ADF initiator is configured as a .Net Http Module in the Web.config file.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.webServer>
      <modules>
        <add name="Tridion.ContentDelivery.AmbientData.HttpModule" type="Tridion.ContentDelivery.AmbientData.HttpModule" />
      </modules>
    </system.webServer>
</configuration>

Input/Output claims verification

ADF does a verification of presence of input claims if they are defined as required in the cartridge configuration file. For instance if you define a claim processor like this.

<RequestStart>
    <InputClaims>
        <ClaimDefinition Uri="taf:extensions:myinputclaim" />
    <InputClaims>
    <OutputClaims />
</RequestStart>

ADF will verify the presence of a claim called "myinputclaim"  in the current claim store, if it is not present it will throw an exception.

How a claim processor is implemented?

 A claim processor is implemented as a java class which extends AbstractClaimProcessor. All the claim processors implementors must override the 3 methods onRequestStart, onRequestEnd and onSessionStart. As you may notice onRequestStart and onRequestEnd apply to REQUEST scope claim processors and onSessionStart applies to SESSION scope claim processors.

Claim Processor sample:

package com.tridion.ambientdata.extensions;

import com.tridion.ambientdata.AmbientDataException;
import com.tridion.ambientdata.claimstore.ClaimStore;
import com.tridion.ambientdata.processing.AbstractClaimProcessor;
import com.tridion.ambientdata.web.WebClaims;

public class myclaimprocessor extends AbstractClaimProcessor {
    @Override
    public void onRequestStart(ClaimStore claims) throws AmbientDataException {
    }
   
    @Override
    public void onRequestEnd(ClaimStore claims) throws AmbientDataException {
    }

    @SuppressWarnings("rawtypes")
    @Override
    public void onSessionStart(ClaimStore claims) throws AmbientDataException {
        try {
               claims.put(new URI("taf:extensions:claim:myclaim"), "myclaimdata");
            }
        } catch (Exception ex) {
            throw new AmbientDataException(ex);
        }
    }
 }

How a claim is used outside of ADF?

The idea behind ADF is to share and use common state data regardless of the technology we are using (.net or java) so that once we have completed developed our claim processors and configured our cartridges we are ready to use the data in our own applications.

Java usage sample

ClaimStore claims = AmbientDataContext.getCurrentClaimStore();
if (claims.contains(new URI("taf:extensions:claim:myclaim"))) {
    String claimdata = claims.get(new URI("taf:extensions:claim:deviceproperties")).toString();
    // Logic here.
}

.Net usage sample

ClaimStore claims = AmbientDataContext.CurrentClaimStore;
if (claims.contains(new URI("taf:extensions:claim:myclaim"))) {
    string claimdata = claims.Get<string>("taf:extensions:claim:deviceproperties");
    // Logic here.

}