Java tutorial
/* * Copyright 2015-2017 Hippo B.V. (http://www.onehippo.com) * * 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 org.onehippo.forge.channelmanager.pagesupport.document.management.impl; import java.rmi.RemoteException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Workspace; import org.apache.commons.lang.StringUtils; import org.hippoecm.repository.HippoStdNodeType; import org.hippoecm.repository.api.HippoNode; import org.hippoecm.repository.api.HippoNodeType; import org.hippoecm.repository.api.HippoWorkspace; import org.hippoecm.repository.api.StringCodec; import org.hippoecm.repository.api.StringCodecFactory; import org.hippoecm.repository.api.Workflow; import org.hippoecm.repository.api.WorkflowException; import org.hippoecm.repository.api.WorkflowManager; import org.hippoecm.repository.standardworkflow.DefaultWorkflow; import org.hippoecm.repository.standardworkflow.FolderWorkflow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Internal utility to invoke Hippo Workflow APIs. */ class HippoWorkflowUtils { private static Logger log = LoggerFactory.getLogger(HippoWorkflowUtils.class); /** * Hippo Repository specific predefined folder node type name */ private static final String DEFAULT_HIPPO_FOLDER_NODE_TYPE = "hippostd:folder"; /** * The workflow category name to get a folder workflow. We use threepane as this is the same as the CMS uses */ private static final String DEFAULT_HIPPO_FOLDER_WORKFLOW_CATEGORY = "threepane"; /** * The workflow category name to add a new document. */ private static final String DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY = "new-document"; /** * The workflow category name to add a new folder. */ private static final String DEFAULT_NEW_FOLDER_WORKFLOW_CATEGORY = "new-folder"; /** * The workflow category name to localize the new document */ private static final String DEFAULT_WORKFLOW_CATEGORY = "core"; /** * The codec which is used for the node names */ private static final StringCodec DEFAULT_URI_ENCODING = new StringCodecFactory.UriEncoding(); private HippoWorkflowUtils() { } /** * Returns {@link Workflow} instance by the {@code category} for the {@code node}. * @param session JCR session * @param category workflow category * @param node folder or document node * @return {@link Workflow} instance for the {@code node} and the {@code category} * @throws RepositoryException if any repository/workflow exception occurs */ public static Workflow getHippoWorkflow(final Session session, final String category, final Node node) throws RepositoryException { Workspace workspace = session.getWorkspace(); ClassLoader workspaceClassloader = workspace.getClass().getClassLoader(); ClassLoader currentClassloader = Thread.currentThread().getContextClassLoader(); try { if (workspaceClassloader != currentClassloader) { Thread.currentThread().setContextClassLoader(workspaceClassloader); } WorkflowManager wfm = ((HippoWorkspace) workspace).getWorkflowManager(); return wfm.getWorkflow(category, node); } finally { if (workspaceClassloader != currentClassloader) { Thread.currentThread().setContextClassLoader(currentClassloader); } } } /** * Returns a map of variant nodes, keyed by variant states such as {@link HippoStdNodeType.PUBLISHED} or {@link HippoStdNodeType.UNPUBLISHED}. * @param handle document handle node * @return a map of variant nodes, keyed by variant states such as {@link HippoStdNodeType.PUBLISHED} or {@link HippoStdNodeType.UNPUBLISHED} * @throws RepositoryException if any repository/workflow exception occurs */ public static Map<String, Node> getDocumentVariantsMap(final Node handle) throws RepositoryException { Map<String, Node> variantsMap = new HashMap<>(); Node variantNode = null; String hippoState; for (NodeIterator nodeIt = handle.getNodes(handle.getName()); nodeIt.hasNext();) { variantNode = nodeIt.nextNode(); if (variantNode.hasProperty(HippoStdNodeType.HIPPOSTD_STATE)) { hippoState = variantNode.getProperty(HippoStdNodeType.HIPPOSTD_STATE).getString(); variantsMap.put(hippoState, variantNode); } } return variantsMap; } /** * Checks if all the folders exist in the given {@code absPath} and creates folders if not existing. * @param session JCR session * @param absPath absolute folder node path * @return the final folder node if successful * @throws RepositoryException if any repository exception occurs * @throws WorkflowException if any workflow exception occurs */ public static Node createMissingHippoFolders(final Session session, String absPath) throws RepositoryException, WorkflowException { String[] folderNames = StringUtils.split(absPath, "/"); Node rootNode = session.getRootNode(); Node curNode = rootNode; String folderNodePath; for (String folderName : folderNames) { String folderNodeName = DEFAULT_URI_ENCODING.encode(folderName); if (curNode == rootNode) { folderNodePath = "/" + folderNodeName; } else { folderNodePath = curNode.getPath() + "/" + folderNodeName; } Node existingFolderNode = getExistingHippoFolderNode(session, folderNodePath); if (existingFolderNode == null) { curNode = session.getNode(createHippoFolderNodeByWorkflow(session, curNode, DEFAULT_HIPPO_FOLDER_NODE_TYPE, folderName)); } else { curNode = existingFolderNode; } curNode = getHippoCanonicalNode(curNode); if (isHippoMirrorNode(curNode)) { curNode = getRereferencedNodeByHippoMirror(curNode); } } return curNode; } /** * Returns {@code node} if it is a document handle node or its parent if it is a document variant node. * Otherwise returns null. * @param node JCR node * @return {@code node} if it is a document handle node or its parent if it is a document variant node. Otherwise returns null. * @throws RepositoryException if repository exception occurs */ public static Node getHippoDocumentHandle(Node node) throws RepositoryException { if (node.isNodeType("hippo:handle")) { return node; } else if (node.isNodeType("hippo:document")) { if (!node.getSession().getRootNode().isSame(node)) { Node parentNode = node.getParent(); if (parentNode.isNodeType("hippo:handle")) { return parentNode; } } } return null; } private static Node getHippoCanonicalNode(Node node) { if (node instanceof HippoNode) { HippoNode hnode = (HippoNode) node; try { Node canonical = hnode.getCanonicalNode(); if (canonical == null) { log.debug( "Cannot get canonical node for '{}'. This means there is no phyiscal equivalence of the " + "virtual node. Return null", node.getPath()); } return canonical; } catch (RepositoryException e) { log.error("Repository exception while fetching canonical node. Return null", e); throw new RuntimeException(e); } } return node; } private static boolean isHippoMirrorNode(Node node) throws RepositoryException { if (node.isNodeType(HippoNodeType.NT_FACETSELECT) || node.isNodeType(HippoNodeType.NT_MIRROR)) { return true; } return false; } private static Node getRereferencedNodeByHippoMirror(Node mirrorNode) { String docBaseUUID = null; try { if (!isHippoMirrorNode(mirrorNode)) { log.info("Cannot deref a node that is not of (sub)type '{}' or '{}'. Return null", HippoNodeType.NT_FACETSELECT, HippoNodeType.NT_MIRROR); return null; } // HippoNodeType.HIPPO_DOCBASE is a mandatory property so no need to test if exists docBaseUUID = mirrorNode.getProperty(HippoNodeType.HIPPO_DOCBASE).getString(); try { return mirrorNode.getSession().getNodeByIdentifier(docBaseUUID); } catch (IllegalArgumentException e) { log.warn("Docbase cannot be parsed to a valid uuid. Return null"); return null; } } catch (ItemNotFoundException e) { String path = null; try { path = mirrorNode.getPath(); } catch (RepositoryException e1) { log.error("RepositoryException, cannot return deferenced node: {}", e1); } log.info( "ItemNotFoundException, cannot return deferenced node because docbase uuid '{}' cannot be found. The docbase property is at '{}/hippo:docbase'. Return null", docBaseUUID, path); } catch (RepositoryException e) { log.error("RepositoryException, cannot return deferenced node: {}", e); } return null; } private static Node getExistingHippoFolderNode(final Session session, final String absPath) throws RepositoryException { if (!session.nodeExists(absPath)) { return null; } Node node = session.getNode(absPath); Node candidateNode = null; if (session.getRootNode().isSame(node)) { return session.getRootNode(); } else { Node parentNode = node.getParent(); for (NodeIterator nodeIt = parentNode.getNodes(node.getName()); nodeIt.hasNext();) { Node siblingNode = nodeIt.nextNode(); if (!isHippoDocumentHandleOrVariant(siblingNode)) { candidateNode = siblingNode; break; } } } if (candidateNode == null) { return null; } Node canonicalFolderNode = getHippoCanonicalNode(candidateNode); if (isHippoMirrorNode(canonicalFolderNode)) { canonicalFolderNode = getRereferencedNodeByHippoMirror(canonicalFolderNode); } if (canonicalFolderNode == null) { return null; } if (isHippoDocumentHandleOrVariant(canonicalFolderNode)) { return null; } return canonicalFolderNode; } private static boolean isHippoDocumentHandleOrVariant(Node node) throws RepositoryException { if (node.isNodeType("hippo:handle")) { return true; } else if (node.isNodeType("hippo:document")) { if (!node.getSession().getRootNode().isSame(node)) { Node parentNode = node.getParent(); if (parentNode.isNodeType("hippo:handle")) { return true; } } } return false; } private static String createHippoFolderNodeByWorkflow(final Session session, Node folderNode, String nodeTypeName, String name) throws RepositoryException, WorkflowException { try { folderNode = getHippoCanonicalNode(folderNode); Workflow wf = getHippoWorkflow(session, DEFAULT_HIPPO_FOLDER_WORKFLOW_CATEGORY, folderNode); if (wf instanceof FolderWorkflow) { FolderWorkflow fwf = (FolderWorkflow) wf; String category = DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY; if (nodeTypeName.equals(DEFAULT_HIPPO_FOLDER_NODE_TYPE)) { category = DEFAULT_NEW_FOLDER_WORKFLOW_CATEGORY; // now check if there is some more specific workflow for hippostd:folder if (fwf.hints() != null && fwf.hints().get("prototypes") != null) { Object protypesMap = fwf.hints().get("prototypes"); if (protypesMap instanceof Map) { for (Object o : ((Map) protypesMap).entrySet()) { Entry entry = (Entry) o; if (entry.getKey() instanceof String && entry.getValue() instanceof Set) { if (((Set) entry.getValue()).contains(DEFAULT_HIPPO_FOLDER_NODE_TYPE)) { // we found possibly a more specific workflow for folderNodeTypeName. Use the key as category category = (String) entry.getKey(); break; } } } } } } String nodeName = DEFAULT_URI_ENCODING.encode(name); String added = fwf.add(category, nodeTypeName, nodeName); if (added == null) { throw new WorkflowException("Failed to add document/folder for type '" + nodeTypeName + "'. Make sure there is a prototype."); } Node addedNode = folderNode.getSession().getNode(added); if (!nodeName.equals(name)) { DefaultWorkflow defaultWorkflow = (DefaultWorkflow) getHippoWorkflow(session, DEFAULT_WORKFLOW_CATEGORY, addedNode); defaultWorkflow.setDisplayName(name); } if (DEFAULT_NEW_DOCUMENT_WORKFLOW_CATEGORY.equals(category)) { // added new document : because the document must be in 'preview' availability, we now set this explicitly if (addedNode.isNodeType("hippostd:publishable")) { log.info("Added document '{}' is pusblishable so set status to preview.", addedNode.getPath()); addedNode.setProperty("hippostd:state", "unpublished"); addedNode.setProperty(HippoNodeType.HIPPO_AVAILABILITY, new String[] { "preview" }); } else { log.info("Added document '{}' is not publishable so set status to live & preview directly.", addedNode.getPath()); addedNode.setProperty(HippoNodeType.HIPPO_AVAILABILITY, new String[] { "live", "preview" }); } if (addedNode.isNodeType("hippostd:publishableSummary")) { addedNode.setProperty("hippostd:stateSummary", "new"); } addedNode.getSession().save(); } return added; } else { throw new WorkflowException("Can't create folder " + name + " [" + nodeTypeName + "] in the folder " + folderNode.getPath() + ", because there is no FolderWorkflow possible on the folder node: " + wf); } } catch (RemoteException e) { throw new WorkflowException(e.toString(), e); } } }