com.day.cq.analytics.testandtarget.util.MboxHelper Maven / Gradle / Ivy
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2011 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.day.cq.analytics.testandtarget.util;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.analytics.sitecatalyst.Framework;
import com.day.cq.analytics.testandtarget.MboxConstants;
import com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap;
import com.day.cq.commons.inherit.InheritanceValueMap;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.personalization.impl.util.PersonalizationConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.WCMMode;
import com.day.cq.wcm.webservicesupport.Configuration;
import com.day.cq.wcm.webservicesupport.ConfigurationManager;
public class MboxHelper {
private static final Logger log = LoggerFactory.getLogger(MboxHelper.class);
//no instance
private MboxHelper() {
}
/**
* Returns a name for the {@link Resource}. As name the jcr:title with
* removed whitespaces is used if available. If no jcr:title is specified,
* a name is generated by {@link MboxHelper#getMboxId(Resource)}.
*
* @param rsrc {@link Resource}
* @return a name for the {@link Resource}
*/
public static String getMboxName(Resource rsrc) {
String mboxName = "";
rsrc = getStartResource(rsrc);
ValueMap resourceConfig = rsrc.adaptTo(ValueMap.class);
if(resourceConfig != null) {
mboxName = resourceConfig.get("jcr:title","");
if ("".equals(mboxName)) {
mboxName = getMboxId(rsrc);
}
mboxName = mboxName.replaceAll("\\s", "");
}
return mboxName;
}
/**
* Generates the location name used by this location in Adobe Target.
* The location name uses the following pattern:
* {location-name-in-aem}-{the non-master ambit names joined by # and hashed using MD5}--{wcm mode}. If the {@link WCMMode} is {@link WCMMode#DISABLED} then it's omitted. If not, the "--author" suffix is used.
*
* @param currentResource the {@link Resource} representing the target component
* @param wcmMode the {@link WCMMode}
* @param ambitMappings Array of ambits
* @return the location name generated using the pattern described above
*/
public static String generateLocationName(Resource currentResource, WCMMode wcmMode, String... ambitMappings) {
String mboxBaseName = getMboxName(currentResource);
String mboxName;
if (ambitMappings != null && ambitMappings.length > 0) {
SortedSet mappings = new TreeSet();
// check that we don't have the master ambit mapped to the parent page
for (String mapping: ambitMappings) {
if (!mapping.endsWith("master")) {
mappings.add(mapping);
}
}
String hashedInfo = mappings.size() > 0 ?
"-" + hashData(StringUtils.join(mappings, "#")) :
"";
mboxName = MboxHelper.qualifyMboxNameOrId(mboxBaseName + hashedInfo, wcmMode);
} else {
mboxName = MboxHelper.qualifyMboxNameOrId(mboxBaseName, wcmMode);
}
return mboxName;
}
/**
* Generates an mbox ID. The ID is generated by replacing slashes and
* removing jcr:content from the resource path of the start element.
* If the corresponding start element can not be found the provided
* element is used.
*
* @param rsrc Resource of start/end element
* @return an MboxId for the {@link Resource}
*/
public static String getMboxId(Resource rsrc) {
String mboxId = "";
rsrc = getStartResource(rsrc);
if(rsrc != null) {
ValueMap vm = rsrc.adaptTo(ValueMap.class);
String loc = vm.get(MboxConstants.PROP_MBOX_LOCATION, rsrc.getPath());
mboxId = getMboxId(loc);
}
return mboxId;
}
/**
* Adds the WCM mode qualifier to the mbox name, if necessary
* @param mboxNameOrId the mbox name
* @param wcmMode the {@link WCMMode}
* @return the mbox name containing the WCM mode, if necessary. It the WCM mode is {@link WCMMode#DISABLED} then the return value is the one passed in the mboxNameOrId parameter
*/
public static String qualifyMboxNameOrId(String mboxNameOrId, WCMMode wcmMode) {
if (wcmMode == WCMMode.DISABLED)
return mboxNameOrId;
// the double dash is guaranteed to not come from a valid JCR path since we
// translate slashes to dashes and a double slash is not a valid JCR path
return mboxNameOrId + "--author";
}
/**
* Adds the necessary qualifiers (WCM Mode and the name of the ambit) to the mbox name
* @param mboxNameOrId the actual mbox name
* @param wcmMode the {@link WCMMode}
* @param ambitName the name of the ambit, if this mbox belongs to a site using MSM
* @return the location name containing the ambit name and the wcm mode, if necessary. If the WCM mode is {@link WCMMode#DISABLED} and the ambit name is "master" then the return value is the one passed on the mboxNameOrId parameter
*/
public static String qualifyMboxNameOrId(String mboxNameOrId, WCMMode wcmMode, String ambitName) {
if (StringUtils.isEmpty(ambitName) || PersonalizationConstants.AMBIT_DEFAULT_NAME.equals(ambitName)) {
return qualifyMboxNameOrId(mboxNameOrId, wcmMode);
}
return qualifyMboxNameOrId(StringUtils.join(new String[] { mboxNameOrId, ambitName }, "-"), wcmMode);
}
/**
* Generates an mbox ID from an mbox location.
* Slashes are replaced with dashes. If this location is a repository path then jcr:content
is stripped from it.
*
* @param location the mbox location
* @return the mbox id
*
* @see #getMboxId(Resource)
*/
public static String getMboxId(String location) {
if (location.startsWith("/")) {
// strip the leading / from paths
location = location.substring(1);
}
return location.replaceAll("/", "-").replaceAll("-jcr:content", "");
}
/**
* Search the start element for the current element type.
* @param resource {@link Resource}
* @return start element for the current element type
*/
public static Resource searchStartElement(final Resource resource) {
if ( ResourceUtil.getName(resource).equals(JcrConstants.JCR_CONTENT) ) {
return null;
}
if ( resource.getPath().lastIndexOf("/") == 0 ) {
return null;
}
if ( ResourceUtil.isA(resource, MboxConstants.RT_MBOX_BEGIN) ) {
return resource;
}
// first, we have to collect all predecessors
final Resource parent = ResourceUtil.getParent(resource);
final List predecessor = new ArrayList();
final Iterator i = ResourceUtil.listChildren(parent);
while ( i.hasNext() ) {
final Resource current = i.next();
if ( current.getPath().equals(resource.getPath()) ) {
break;
}
predecessor.add(current);
}
// reverse the order, to get the immediate predecessors first
Collections.reverse(predecessor);
// iterate, as soon as we find a form begin, we're done
// as soon as we find a form end, the form begin is missing for this form
final Iterator rsrcIter = predecessor.iterator();
while ( rsrcIter.hasNext() ) {
final Resource current = rsrcIter.next();
if ( ResourceUtil.isA(current, MboxConstants.RT_MBOX_BEGIN) ) {
return current;
}
if ( ResourceUtil.isA(current, MboxConstants.RT_MBOX_END) ) {
return null;
}
}
return searchStartElement(parent);
}
/**
* Checks {@link Resource} for end element and returns start element if
* possible. If the {@link Resource} is a start element it is returned
* directly.
*
* @param rsrc {@link Resource}
*/
private static Resource getStartResource(Resource rsrc) {
if(ResourceUtil.isA(rsrc, MboxConstants.RT_MBOX_END)) {
Resource startElement = searchStartElement(rsrc);
if(startElement !=null) {
return startElement;
}
}
return rsrc;
}
/**
* Returns the repository path to a custom mbox.js file for the current specified resource and currentPage
*
* @param resource {@link Resource}
* @param currentPage current {@link Page}
* @param cfgMgr {@link ConfigurationManager}
* @return the path to the custom mbox.js file or null if the default one should be used
* @throws RepositoryException {@link RepositoryException}
*/
public static String getCustomMboxJsPath(Resource resource, Page currentPage, ConfigurationManager cfgMgr) throws RepositoryException {
Configuration configuration = null;
HierarchyNodeInheritanceValueMap mboxProperties = new HierarchyNodeInheritanceValueMap(resource);
String[] services = mboxProperties.getInherited( "cq:cloudserviceconfigs", new String[] {});
if (services.length == 0) {
mboxProperties = new HierarchyNodeInheritanceValueMap( currentPage.getContentResource());
services = mboxProperties.getInherited("cq:cloudserviceconfigs", new String[] {});
}
if (cfgMgr != null)
configuration = cfgMgr.getConfiguration("testandtarget", services);
boolean isValidConfig = configuration != null && (configuration.getInherited("clientcode", null) != null);
if (isValidConfig) {
Node configNode = null;
if (configuration != null)
configNode = configuration.getResource().adaptTo(Node.class);
if (configNode.hasNode("./jcr:content/public/mbox.js")) {
final Node scriptNode = configNode.getNode("./jcr:content/public/mbox.js");
return scriptNode.getPath();
}
}
return null;
}
/**
* Returns true if the mbox represented by the resource has accurateTargeting enabled.
*
* @param resource {@link Resource}
* @return true if the mbox represented by the resource has accurateTargeting enabled.
* @throws RepositoryException {@link RepositoryException}
*/
public static boolean isAccurateRendering(Resource resource) throws RepositoryException {
return resource.adaptTo(ValueMap.class).get("accurateTargeting", false);
}
/**
* Returns the names of the ClientContext parameters which should be sent as part of mbox calls
*
*
* This method merges the directly defined parameter names with the parameters names inherited from a Adobe Target
* framework.
*
*
* @param resource the target resource
* @param pageProperties {@link InheritanceValueMap}
* @param configurationManager {@link ConfigurationManager}
* @return a list of parameter names, never null
* @throws RepositoryException {@link RepositoryException}
*/
public static List getClientContextParameterNames(Resource resource, InheritanceValueMap pageProperties,
ConfigurationManager configurationManager) throws RepositoryException {
return new ArrayList(getMappedClientContextParameterNames(resource, pageProperties,
configurationManager).values());
}
/**
* Returns the names and mapped values of the ClientContext parameters which should be sent as part of mbox calls
*
*
* This method merges the directly defined parameter names with the parameters names inherited from a Adobe Target
* framework.
*
*
*
* The mapped values are usually defined by the {@link Framework}. In case they are defined statically on the
* component the property name is transformed by transforming all slashes ('/') to dots ('.').
*
*
* @param resource the target resource
* @param pageProperties {@link InheritanceValueMap}
* @param configurationManager {@link ConfigurationManager}
* @return parameter mappings never null
* @throws RepositoryException {@link RepositoryException}
*/
public static Map getMappedClientContextParameterNames(Resource resource,
InheritanceValueMap pageProperties, ConfigurationManager configurationManager) throws RepositoryException {
Map mappedProperties = new LinkedHashMap();
List directMappings = Arrays.asList(resource.adaptTo(ValueMap.class).get("cq:mappings", new String[0]));
for (String directMapping : directMappings)
mappedProperties.put(directMapping, directMapping.replace('/', '.')); // same as cbmappings.jsp
Framework framework = getFramework(pageProperties, configurationManager);
if (framework == null)
return mappedProperties;
for (String scVar : framework.scVars())
mappedProperties.put(framework.getMapping(scVar), scVar);
return mappedProperties;
}
/**
* Returns the static parameters configured for this target component. These parameters are used when performing mbox calls.
* @param resource the {@link Resource} representing the target component
* @return a {@link Map} containing the mapped parameters
*/
public static Map getStaticParameters(Resource resource) {
String[] staticParameters = resource.adaptTo(ValueMap.class).get("staticParams", new String[0]);
Map toReturn = new HashMap();
try {
for (String keyAndValue : staticParameters) {
JSONArray array = new JSONArray(keyAndValue);
toReturn.put(array.getString(0), array.getString(1));
}
} catch (JSONException e) {
log.error(e.getMessage(),e);
}
return toReturn;
}
private static Framework getFramework(InheritanceValueMap pageProperties, ConfigurationManager configurationManager) {
// check for a framework defined on the page or above
String[] services = pageProperties.getInherited(Constants.PN_CQ_CLOUD_SERVICE_CONFIGS, new String[] {});
log.debug("Found has {} service configs", services.length);
if (services.length == 0)
return null;
Configuration configuration = configurationManager.getConfiguration("testandtarget", services);
if (configuration == null)
return null;
log.debug("Resource configured to use configuration at {}", configuration.getPath());
return configuration.getContentResource().adaptTo(Framework.class);
}
private static String hashData(String data) {
MessageDigest md = null;
String mappings = "";
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return data;
}
md.update(data.getBytes());
byte[] hashedBytes = md.digest();
// convert to hex
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashedBytes.length; i++) {
sb.append(Integer.toString((hashedBytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}