BinaryValueManagerImpl.java 17.1 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.cq.commons.jcr.JcrUtil
 *  javax.jcr.AccessDeniedException
 *  javax.jcr.Node
 *  javax.jcr.NodeIterator
 *  javax.jcr.PathNotFoundException
 *  javax.jcr.Property
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  javax.jcr.Value
 *  javax.jcr.nodetype.NodeType
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.resource.LoginException
 *  org.apache.sling.api.resource.NonExistingResource
 *  org.apache.sling.api.resource.Resource
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.api.resource.ResourceResolverFactory
 *  org.apache.sling.api.resource.ResourceUtil
 *  org.apache.sling.api.resource.ValueMap
 *  org.apache.sling.jcr.resource.JcrResourceUtil
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.wcm.undo.impl;

import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.wcm.undo.BinaryHandlingException;
import com.day.cq.wcm.undo.BinaryValueManager;
import com.day.cq.wcm.undo.UndoConfigService;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeType;
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.sling.api.resource.LoginException;
import org.apache.sling.api.resource.NonExistingResource;
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.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.resource.JcrResourceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(label="Day CQ WCM Undo Binary Value Manager", immediate=1, metatype=0, description="Manages binary values for CQ's undo facilities.")
@Service(value={BinaryValueManager.class, Runnable.class})
@Properties(value={@Property(name="scheduler.period", longValue={3600}), @Property(name="scheduler.concurrent", boolValue={0}, propertyPrivate=1)})
public class BinaryValueManagerImpl
implements BinaryValueManager,
Runnable {
    @Reference
    protected UndoConfigService config;
    @Reference
    private ResourceResolverFactory rrf;
    private static final Logger log = LoggerFactory.getLogger(BinaryValueManagerImpl.class);
    private static final String UNDO_SERVICE = "undo-service";

    private String createBasePath() {
        String undoPath = this.config.getConfig("path", String.class);
        StringBuilder basePath = new StringBuilder(undoPath.length() + 32);
        basePath.append(undoPath).append('/');
        long nowSec = System.currentTimeMillis() / 1000;
        long firstLevel = nowSec / 3600;
        basePath.append(firstLevel).append('/');
        long secondLevel = nowSec / 60 % 60;
        basePath.append(secondLevel);
        return basePath.toString();
    }

    private Node findNtResource(Node baseNode) throws RepositoryException {
        if (baseNode.getPrimaryNodeType().isNodeType("nt:resource")) {
            return baseNode;
        }
        NodeIterator children = baseNode.getNodes();
        while (children.hasNext()) {
            Node child = children.nextNode();
            Node blobNode = this.findNtResource(child);
            if (blobNode == null) continue;
            return blobNode;
        }
        return null;
    }

    private void removeResource(Resource resourceToRemove) throws BinaryHandlingException {
        if (resourceToRemove == null) {
            return;
        }
        ResourceResolver resolver = resourceToRemove.getResourceResolver();
        Node targetNode = (Node)resourceToRemove.adaptTo(Node.class);
        Session session = (Session)resolver.adaptTo(Session.class);
        try {
            session.refresh(false);
            targetNode.remove();
            session.save();
        }
        catch (RepositoryException re) {
            throw new BinaryHandlingException("Could not delete binary value", (Throwable)re);
        }
    }

    @Override
    public boolean isChanged(Resource par, String subPath, String originalRef) throws BinaryHandlingException {
        if (originalRef == null) {
            return true;
        }
        ResourceResolver resolver = par.getResourceResolver();
        Resource originalBinary = resolver.getResource(originalRef + "/binary");
        if (originalBinary instanceof NonExistingResource) {
            originalBinary = null;
        }
        Resource currentBinary = null;
        try {
            Node blobNode;
            Node subNode;
            Node parNode = (Node)par.adaptTo(Node.class);
            if (parNode.hasNode(subPath) && (blobNode = this.findNtResource(subNode = parNode.getNode(subPath))) != null) {
                currentBinary = resolver.getResource(blobNode.getPath());
            }
        }
        catch (RepositoryException re) {
            throw new BinaryHandlingException("Could not detect if BLOB has changed.", (Throwable)re);
        }
        if (currentBinary == null) {
            return originalBinary != null;
        }
        if (originalBinary == null) {
            return true;
        }
        ValueMap currentProps = ResourceUtil.getValueMap((Resource)currentBinary);
        Calendar currentLastModified = (Calendar)currentProps.get("jcr:lastModified", Calendar.class);
        ValueMap originalProps = ResourceUtil.getValueMap((Resource)originalBinary);
        Calendar originalLastModified = (Calendar)originalProps.get("jcr:lastModified", Calendar.class);
        log.debug("Current last modified: {}; original last modified: {}", (Object)(currentLastModified != null ? Long.valueOf(currentLastModified.getTimeInMillis()) : "-"), (Object)(originalLastModified != null ? Long.valueOf(originalLastModified.getTimeInMillis()) : "-"));
        if (currentLastModified == null) {
            return originalLastModified != null;
        }
        if (originalLastModified == null) {
            return true;
        }
        return !originalLastModified.equals(currentLastModified);
    }

    @Override
    public String save(Resource par, String subPath) throws BinaryHandlingException {
        String path = this.createBasePath();
        String uuid = UUID.randomUUID().toString();
        String dataPath = path + "/" + uuid;
        if (par == null) {
            throw new BinaryHandlingException("No paragraph specified.");
        }
        ResourceResolver resolver = par.getResourceResolver();
        if (resolver == null) {
            throw new BinaryHandlingException("No resource resolver available.");
        }
        Session session = (Session)par.getResourceResolver().adaptTo(Session.class);
        try {
            Node dataNode;
            session.refresh(false);
            Node parNode = (Node)par.adaptTo(Node.class);
            if (!parNode.hasNode(subPath)) {
                String string = null;
                return string;
            }
            Node subNode = parNode.getNode(subPath);
            Node blobNode = this.findNtResource(subNode);
            if (blobNode == null) {
                throw new BinaryHandlingException("No binary node found.");
            }
            StringBuilder rscSubPath = new StringBuilder(64);
            Node processingNode = blobNode;
            while (!processingNode.getPath().equals(subNode.getPath())) {
                rscSubPath.insert(0, processingNode.getName());
                rscSubPath.insert(0, '/');
                processingNode = processingNode.getParent();
            }
            rscSubPath.insert(0, subPath);
            String[] pathParts = rscSubPath.toString().split("/");
            String[] partTypes = new String[pathParts.length];
            processingNode = parNode;
            int p = 0;
            for (String part : pathParts) {
                processingNode = processingNode.getNode(part);
                partTypes[p++] = processingNode.getPrimaryNodeType().getName();
            }
            log.debug("Saving BLOB from '{}' to '{}'.", (Object)par.getPath(), (Object)dataPath);
            try {
                dataNode = JcrResourceUtil.createPath((String)dataPath, (String)"sling:Folder", (String)"nt:unstructured", (Session)session, (boolean)false);
            }
            catch (PathNotFoundException pnfe) {
                throw new AccessDeniedException("Possible permission problem while creating path to '" + dataPath + "'.");
            }
            dataNode.setProperty("pathParts", pathParts);
            dataNode.setProperty("partTypes", partTypes);
            JcrUtil.copy((Node)blobNode, (Node)dataNode, (String)"binary");
            session.save();
            log.debug("BLOB '{}' saved successfully.", (Object)dataPath);
        }
        catch (RepositoryException re) {
            throw new BinaryHandlingException("Could not save binary value", (Throwable)re);
        }
        finally {
            try {
                session.refresh(false);
            }
            catch (RepositoryException re) {}
        }
        return dataPath;
    }

    @Override
    public void restore(Resource undoData, Resource par, String subPath) throws BinaryHandlingException {
        String undoPath = this.config.getConfig("path", String.class);
        Session session = (Session)undoData.getResourceResolver().adaptTo(Session.class);
        try {
            Node parNode;
            session.refresh(false);
            String undoDataPath = undoData.getPath();
            if (!undoDataPath.startsWith(undoPath + "/")) {
                throw new BinaryHandlingException("Cannot restore from paths other than '" + undoPath + "'");
            }
            Node undoNode = (Node)undoData.adaptTo(Node.class);
            Node dataNode = undoNode.getNode("binary");
            Node processingNode = parNode = (Node)par.adaptTo(Node.class);
            Value[] pathParts = undoNode.getProperty("pathParts").getValues();
            Value[] partTypes = undoNode.getProperty("partTypes").getValues();
            int p = 0;
            for (Value part : pathParts) {
                String partName = part.getString();
                if (!processingNode.hasNode(partName)) {
                    String nodeType = partTypes[p].getString();
                    processingNode = processingNode.addNode(partName, nodeType);
                } else {
                    processingNode = processingNode.getNode(partName);
                }
                ++p;
            }
            String targetName = processingNode.getName();
            String targetPath = processingNode.getPath();
            Node copiedNode = JcrUtil.copy((Node)dataNode, (Node)processingNode.getParent(), (String)targetName);
            if (parNode.isNodeType("cq:Page")) {
                parNode = parNode.getNode("jcr:content");
            }
            Calendar now = Calendar.getInstance();
            String userId = parNode.getSession().getUserID();
            copiedNode.setProperty("jcr:lastModified", now);
            copiedNode.setProperty("jcr:lastModifiedBy", userId);
            parNode.setProperty("jcr:lastModified", now);
            parNode.setProperty("jcr:lastModifiedBy", userId);
            session.save();
            log.debug("BLOB restored successfully from '{}' to '{}'", (Object)undoData.getPath(), (Object)targetPath);
        }
        catch (RepositoryException re) {
            throw new BinaryHandlingException("Could not restore binary value", (Throwable)re);
        }
        finally {
            try {
                session.refresh(false);
            }
            catch (RepositoryException re) {}
        }
    }

    @Override
    public void delete(Resource par, String subPath, boolean deletePar) throws BinaryHandlingException {
        Resource resourceToRemove;
        ResourceResolver resolver = par.getResourceResolver();
        if (deletePar) {
            Resource resourceToCheck = resolver.getResource(par, subPath);
            Node nodeToDelete = null;
            try {
                for (Node nodeToCheck = (Node)resourceToCheck.adaptTo(Node.class); nodeToCheck != null; nodeToCheck = nodeToCheck.getParent()) {
                    if (nodeToCheck.isNodeType("nt:resource")) {
                        if (nodeToDelete != null) break;
                        nodeToDelete = nodeToCheck;
                        continue;
                    }
                    if (!nodeToCheck.isNodeType("nt:file")) continue;
                    nodeToDelete = nodeToCheck;
                    break;
                }
                if (nodeToDelete == null) {
                    throw new BinaryHandlingException("Could not determine a valid binary node of type nt:resource or nt:file.");
                }
                String pathToDelete = nodeToDelete.getPath();
                log.debug("Removing binary from actual path '{}'", (Object)pathToDelete);
                resourceToRemove = resolver.getResource(pathToDelete);
            }
            catch (RepositoryException re) {
                throw new BinaryHandlingException("Could not determine binary node (nt:resource or nt:file) to remove due to an underlying repository problem.", (Throwable)re);
            }
        } else {
            resourceToRemove = resolver.getResource(par, subPath);
        }
        this.removeResource(resourceToRemove);
    }

    @Override
    public void delete(ResourceResolver resolver, String undoPath) throws BinaryHandlingException {
        String basePath = this.config.getConfig("path", String.class);
        if (!undoPath.startsWith(basePath + "/")) {
            throw new BinaryHandlingException("Cannot delete in paths that don't start with '" + basePath + "'");
        }
        this.removeResource(resolver.getResource(undoPath));
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    @Override
    public void run() {
        log.debug("Janitor job executing.");
        long undoDataValidity = this.config.getConfig("dataValidity", Integer.class).longValue();
        String undoPath = this.config.getConfig("path", String.class);
        long nowHour = System.currentTimeMillis() / 3600000;
        long thresholdHour = nowHour - undoDataValidity;
        log.debug("Cleaning for threshold hour {}", (Object)thresholdHour);
        ResourceResolver resolver = null;
        Session session = null;
        try {
            resolver = this.rrf.getServiceResourceResolver(Collections.singletonMap("sling.service.subservice", "undo-service"));
            session = (Session)resolver.adaptTo(Session.class);
            if (undoPath != null && session.nodeExists(undoPath)) {
                Node dataRoot = session.getNode(undoPath);
                NodeIterator dataIt = dataRoot.getNodes();
                ArrayList<Node> toRemove = new ArrayList<Node>();
                while (dataIt.hasNext()) {
                    Node nodeToCheck = dataIt.nextNode();
                    log.debug("Checking undo folder '{}' for removal.", (Object)nodeToCheck.getPath());
                    try {
                        long nodeHour = Long.parseLong(nodeToCheck.getName());
                        if (nodeHour >= thresholdHour) continue;
                        toRemove.add(nodeToCheck);
                    }
                    catch (NumberFormatException nfe) {
                        log.debug("Invalid node name: {}", (Object)nodeToCheck.getName());
                    }
                }
                for (Node nodeToRemove : toRemove) {
                    log.debug("Removing undo data folder '{}'", (Object)nodeToRemove.getPath());
                    nodeToRemove.remove();
                }
                session.save();
            }
        }
        catch (LoginException e) {
            log.error("Unable to obtain a resource resolver for cleaning up the undo area.", (Throwable)e);
        }
        catch (RepositoryException re) {
            log.warn("Could not cleanup undo data.", (Throwable)re);
        }
        finally {
            if (session != null) {
                session.logout();
            }
        }
    }

    protected void bindConfig(UndoConfigService undoConfigService) {
        this.config = undoConfigService;
    }

    protected void unbindConfig(UndoConfigService undoConfigService) {
        if (this.config == undoConfigService) {
            this.config = null;
        }
    }

    protected void bindRrf(ResourceResolverFactory resourceResolverFactory) {
        this.rrf = resourceResolverFactory;
    }

    protected void unbindRrf(ResourceResolverFactory resourceResolverFactory) {
        if (this.rrf == resourceResolverFactory) {
            this.rrf = null;
        }
    }
}