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?
CartridgeA 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 .netcd_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.
}
Good stuff, Eric!
ReplyDeleteA couple of comments:
1) The order in which cartridges are executed is actually based on their dependencies. That is why you configure input and output claims: so that other cartridges could depend on your output claims (by declaring them as input claims). The dependencies are resolved and the cartridges are then executed in the right order.
2) Claim Processors do not have a scope themselves; they are always executed on every request and start of the session. The scope is only defined for claim values and is meant to define the lifetime of the claim. So in the future, a claim set to a "request" scope could be removed automatically once the request is done. At the moment, though, this is not implemented and the scope is mostly informational; but you may as well define the proper scope now so that your cartridges are ready when the scope is enforced.
I hope that makes sense - and thanks again for sharing!
Hi Peter,
ReplyDeleteThank you for your comments!!!, yes they make sense.
Very good stuff Eric!
ReplyDeleteYou mention: "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." However, I don't experience an exception thrown if no dependant input claim was present. The dependency order simply can get messed up, but no error is thrown by the framework.
Excellent stuff!!
ReplyDeleteI have a scenario where in one page the custom claims are being added in claimstore and the page is redirected two another page i.e Response.End() is called. In this case the second page doesn't get any custom claim keys/values that means custom claims are not persisted.
This works when custom claims are saved and fetched in two different requests in two subsequent pages without using Response.Redirect().
Also I wanted to know in Tridion 2013 SP1 there is a third parameter introduced in Put() - Tridion.ContentDelivery.AmbientData.Runtime.ClaimValueScope which holds two enum values i.e Session and Request. Do we still need to use cartrdge implementing Session scope?