/*
* Copyright 2012 david gonzalez.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.activecq.samples.workflow.impl;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* @author david
*/
@Component(
label = "Samples - Tag Explosion Workflow",
description = "Sample Workflow Process implementation",
metatype = false,
immediate = true
)
@Properties({
@Property(
name = Constants.SERVICE_DESCRIPTION,
value = "Explodes tags.",
propertyPrivate = true
),
@Property(
label = "Vendor",
name = Constants.SERVICE_VENDOR,
value = "ActiveCQ",
propertyPrivate = true
),
@Property(
label = "Workflow Label",
name = "process.label",
value = "Tag Explosion",
description = "Explodes tags down the tree."
)
})
@Service
public class TagExplosionProcessWorkflow implements WorkflowProcess {
public static final String FROM_PROPERTY = "inputTags";
public static final String TO_PROPERTY = "cq:tags";
public static final String TYPE_CQ_PAGE_CONTENT = "cq:PageContent";
public static final String TYPE_CQ_PAGE = "cq:Page";
public static final String TYPE_DAM_ASSET = "dam:Asset";
public static final String TYPE_DAM_ASSET_METADATA = "metatdata";
public static final String REL_PATH_DAM_ASSET_METADATA = "jcr:content/metatdata";
/**
* OSGi Service References *
*/
@Reference
private ResourceResolverFactory resourceResolverFactory;
/**
* Fields *
*/
private static final Logger log = LoggerFactory.getLogger(TagExplosionProcessWorkflow.class);
/**
* Work flow execute method *
*/
@Override
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap args) throws WorkflowException {
log.debug("TAG EXPLODE");
final WorkflowData workflowData = workItem.getWorkflowData();
final String type = workflowData.getPayloadType();
// Check if the payload is a path in the JCR
if (!StringUtils.equals(type, "JCR_PATH")) {
return;
}
Session session = workflowSession.getSession();
// Get the path to the JCR resource from the payload
String path = workflowData.getPayload().toString();
// Get a ResourceResolver using the same permission set as the Workflow's executing Session
ResourceResolver resolver = null;
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
// Initialize some variables
final HashSet<String> newExplodedTags = new HashSet<String>();
try {
// Get the Workflow Sessions' resource resolver using the authInfo created above
resolver = resourceResolverFactory.getResourceResolver(authInfo);
// Get the Resource representing the WF payload
final Resource resource = resolver.getResource(path);
// Get the TagManager (using the same permission level as the Workflow's Session)
final TagManager tagManager = resolver.adaptTo(TagManager.class);
// Use custom implementation to find the resource to look for cq:tags and write the
// custom property "tag-titles" to
final Resource contentResource = getContentResource(resource);
if (contentResource == null) {
log.error("Could not find a valid content resource node for payload: {}", resource.getPath());
return;
}
// Gain access to the content resource's properties
final ValueMap properties = contentResource.adaptTo(ValueMap.class);
// Get the full tag paths (namespace:path/to/tag) from the content resource
// This only works on the cq:tags property
final List<Tag> tags = getStringPropertyToTag(contentResource, FROM_PROPERTY, tagManager);
// Get any previously applied Localized Tag Titles.
// This is used to determine if changes if any updates are needed to this node.
final String[] previousExplodedTags = properties.get(TO_PROPERTY, new String[]{});
if (!tags.isEmpty()) {
newExplodedTags.addAll(getExplodedTags(tags, newExplodedTags));
}
try {
// Get the node in the JCR the payload points to
final Node node = session.getNode(contentResource.getPath());
// If the currently applied Tag Titles are the same as the derived Tag titles then skip!
if (!isSame(newExplodedTags.toArray(new String[]{}), previousExplodedTags)) {
// If changes have been made to the Tag Names, then apply to the tag-titles property
// on the content resource.
node.setProperty(TO_PROPERTY, newExplodedTags.toArray(new String[]{}));
} else {
log.debug("No change in Tags. Do not update this content resource.");
}
} catch (PathNotFoundException ex) {
log.error(ex.getMessage());
} catch (RepositoryException ex) {
log.error(ex.getMessage());
}
} catch (LoginException ex) {
log.error(ex.getMessage());
} finally {
// Clean up after yourself please!!!
if (resolver != null) {
resolver.close();
resolver = null;
}
}
}
/** Helper methods **/
/**
* Checks if two String arrays are the same (same values and same order)
*
* @param a
* @param b
* @return
*/
private boolean isSame(String[] a, String[] b) {
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (!StringUtils.equals(a[i], b[i])) {
return false;
}
}
return true;
}
/**
* Returns localized Tag Titles for all the ancestor tags to the tags supplied in "tagPaths"
* <p/>
* Tags in
*
* @param tags
* @param explodedTags
* @return
*/
private HashSet<String> getExplodedTags(final List<Tag> tags, HashSet<String> explodedTags) {
for (final Tag tag : tags) {
explodedTags.add(tag.getTagID());
if (tag.listChildren() != null && tag.listChildren().hasNext()) {
final List<Tag> children = IteratorUtils.toList(tag.listChildren());
explodedTags.addAll(this.getExplodedTags(children, explodedTags));
} else {
log.debug("Add tag: {}", tag.getTagID());
explodedTags.add(tag.getTagID());
}
}
return explodedTags;
}
private List<Tag> getStringPropertyToTag(final Resource resource, final String property, final TagManager tagManager) {
final ValueMap properties = resource.adaptTo(ValueMap.class);
final String[] tagIds = properties.get(property, new String[]{});
final List<Tag> tags = new ArrayList<Tag>();
for (final String tagId : tagIds) {
final Tag tmp = tagManager.resolve(tagId);
if (tmp != null) {
tags.add(tmp);
}
}
return tags;
}
/**
* Finds the proper "content" resource to read cq:tags from and write tag-titles to, based on
* payload resource type.
* <p/>
* cq:Page
* cq:PageContent
* nt:unstructured acting as cq:PageContent
* <p/>
* dam:Asset
* dam:Asset metadata
*
* @param payloadResource
* @return
*/
private Resource getContentResource(final Resource payloadResource) {
if (isPrimaryType(payloadResource, TYPE_CQ_PAGE)) {
/** cq:Page **/
return payloadResource.getChild(JcrConstants.JCR_CONTENT);
} else if (StringUtils.equals(payloadResource.getName(), JcrConstants.JCR_CONTENT) &&
isPrimaryType(payloadResource, TYPE_CQ_PAGE_CONTENT)) {
/** cq:PageContent **/
return payloadResource;
} else if (isPrimaryType(payloadResource, JcrConstants.NT_UNSTRUCTURED)) {
/** nt:unstructured **/
final Resource parent = payloadResource.getParent();
if (parent != null &&
isPrimaryType(parent, TYPE_CQ_PAGE) &&
StringUtils.equals(payloadResource.getName(), JcrConstants.JCR_CONTENT)) {
/** cq:Page / jcr:content(nt:unstructured) **/
return payloadResource;
} else if (StringUtils.equals(payloadResource.getName(), TYPE_DAM_ASSET_METADATA)) {
if (parent != null && StringUtils.equals(parent.getName(), JcrConstants.JCR_CONTENT)) {
Resource grandParent = null;
if (parent != null) {
grandParent = parent.getParent();
}
if (grandParent != null && isPrimaryType(grandParent, TYPE_DAM_ASSET)) {
/** dam:Asset / jcr:content / metadata **/
return payloadResource;
}
}
}
} else if (isPrimaryType(payloadResource, TYPE_DAM_ASSET)) {
/** dam:Asset **/
return payloadResource.getChild(REL_PATH_DAM_ASSET_METADATA);
}
/** Use the payload resource; Ex. a component resource that uses cq:tags **/
return payloadResource;
}
/**
* Checks if the jcr:PrimaryType of a resource matches the type param
*
* @param resource
* @param type
* @return
*/
private boolean isPrimaryType(final Resource resource, final String type) {
ValueMap properties = resource.adaptTo(ValueMap.class);
String primaryType = properties.get(JcrConstants.JCR_PRIMARYTYPE, "__unknown__");
return StringUtils.equals(type, primaryType);
}
}