CodeUpgraderImpl.java 13.9 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.text.Text
 *  javax.jcr.Item
 *  javax.jcr.Node
 *  javax.jcr.NodeIterator
 *  javax.jcr.Property
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  javax.jcr.version.Version
 *  org.apache.commons.io.IOUtils
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.compat.codeupgrade.impl;

import com.day.cq.compat.codeupgrade.internal.api.CodeUpgrader;
import com.day.cq.compat.codeupgrade.internal.api.NodeBackup;
import com.day.text.Text;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.version.Version;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class CodeUpgraderImpl
implements CodeUpgrader {
    private final Logger log;
    private int nodeCount;
    private int maxFileSize;
    public static final int SAVE_EVERY_N = 1000;
    private final String configPath;
    private final Map<String, String> resourceTypeMap;
    private final Map<String, String> templatesMap;
    private boolean stop;
    private String currentPath;
    private int tooLargeFilesCount;
    private Pattern fileAcceptRegexp;
    public static final int DEFAULT_MAX_FILE_SIZE = 102400;
    public static final String DEFAULT_FILE_ACCEPT_REGEXP = ".*";
    public static final String JCR_MIME_TYPE_PATH = "jcr:content/jcr:mimeType";
    public static final String JCR_CONTENT = "jcr:content";
    public static final String JCR_DATA = "jcr:data";
    public static final String JCR_LASTMODIFIED = "jcr:lastModified";
    public static final String JCR_DATA_PATH = "jcr:content/jcr:data";
    public static final String RESOURCE_TYPES_FILENAME = "resourcetypes.txt";
    public static final String TEMPLATES_FILENAME = "templates.txt";
    public static final String FILE_ENCODING = "UTF-8";
    public static final String[] IGNORE_MIME_TYPE_PREFIXES = new String[]{"image/"};

    public CodeUpgraderImpl(Session session, String configPath) throws IOException, RepositoryException {
        this.log = LoggerFactory.getLogger(this.getClass());
        this.maxFileSize = 102400;
        this.fileAcceptRegexp = Pattern.compile(".*");
        this.configPath = configPath;
        this.resourceTypeMap = this.loadMappings(session, configPath + "/" + "resourcetypes.txt");
        this.templatesMap = this.loadMappings(session, configPath + "/" + "templates.txt");
    }

    @Override
    public void upgrade(Session session, String path, NodeBackup b, Writer logOutput) throws Exception {
        if (session == null) {
            throw new IllegalArgumentException("Session is null");
        }
        if (!session.itemExists(path)) {
            this.info(logOutput, "Path does not exist, ignored (" + path + ")");
            return;
        }
        this.info(logOutput, "Upgrading path " + path + ", config=" + this.configPath);
        Node n = (Node)session.getItem(path);
        this.upgradeNode(n, b, logOutput);
        if (session.hasPendingChanges()) {
            this.info(logOutput, "Upgrade ends, saving changes");
            session.save();
        }
    }

    public String toString() {
        return this.getClass().getSimpleName() + ", config path=" + this.configPath;
    }

    private void upgradeNode(Node n, NodeBackup b, Writer logOutput) throws RepositoryException {
        if (this.stop) {
            return;
        }
        if (n.isNodeType("nt:file")) {
            try {
                this.processFileNode(n, b, logOutput);
            }
            catch (FileTooLargeException tle) {
                this.log.info("File ignored, over maximum size (" + this.maxFileSize + "): " + n.getPath());
                ++this.tooLargeFilesCount;
            }
            catch (Exception e) {
                this.log.info("Upgrade of file node failed: " + n.getPath(), (Throwable)e);
            }
        } else {
            try {
                this.processNonFileNode(n, b, logOutput);
            }
            catch (Exception e) {
                this.log.info("Upgrade of non-file node failed: " + n.getPath(), (Throwable)e);
                ++this.tooLargeFilesCount;
            }
            NodeIterator it = n.getNodes();
            while (it.hasNext()) {
                this.upgradeNode(it.nextNode(), b, logOutput);
            }
        }
        ++this.nodeCount;
        if (this.nodeCount % 1000 == 0 && n.getSession().hasPendingChanges()) {
            this.info(logOutput, "" + this.nodeCount + " nodes processed, saving JCR session");
            n.getSession().save();
        }
        this.currentPath = n.getPath();
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private void processFileNode(Node n, NodeBackup b, Writer logOutput) throws IOException, RepositoryException {
        String mimeType = null;
        boolean ignore = false;
        if (!this.fileAcceptRegexp.matcher(n.getName()).matches()) {
            this.log.debug("File node {} ignored, name does not match {}", (Object)n.getPath(), (Object)this.getFileAcceptRegexp());
            return;
        }
        if (n.hasProperty("jcr:content/jcr:mimeType")) {
            mimeType = n.getProperty("jcr:content/jcr:mimeType").getString();
        }
        if (mimeType != null) {
            for (String prefix : IGNORE_MIME_TYPE_PREFIXES) {
                if (!mimeType.startsWith(prefix)) continue;
                this.log.debug("File node {} ignored due to {} mime-type", (Object)n.getPath(), (Object)mimeType);
                ignore = true;
                break;
            }
        }
        if (!ignore && !n.hasProperty("jcr:content/jcr:data")) {
            ignore = true;
            this.log.debug("File node {} ignored, no {} property", (Object)n.getPath(), (Object)"jcr:content/jcr:data");
        }
        if (!ignore) {
            this.log.debug("Processing file node {}", (Object)n.getPath());
            InputStream in = n.getProperty("jcr:content/jcr:data").getStream();
            String text = null;
            try {
                text = this.loadContent(in, n.getPath());
            }
            finally {
                IOUtils.closeQuietly((InputStream)in);
            }
            int changes = 0;
            for (String rt : this.resourceTypeMap.keySet()) {
                int index = 0;
                StringBuilder sb = new StringBuilder();
                String rest = text;
                while ((index = rest.indexOf(rt)) > 0) {
                    char c = rest.charAt(index - 1);
                    if (c == '\'' || c == '\"') {
                        String replacement = this.resourceTypeMap.get(rt);
                        this.info(logOutput, "Replacing resource type " + rt + " with " + replacement + " in " + n.getPath());
                        sb.append(rest.substring(0, index));
                        sb.append(replacement);
                        rest = rest.substring(index + rt.length());
                        ++changes;
                        continue;
                    }
                    sb.append(rest.substring(0, index + rt.length()));
                    rest = rest.substring(index + rt.length());
                }
                sb.append(rest);
                text = sb.toString();
            }
            if (changes > 0) {
                this.info(logOutput, "Saving " + changes + " resource path changes modified in " + n.getPath());
                Node content = n.getNode("jcr:content");
                CheckoutHandler ch = new CheckoutHandler(content);
                try {
                    ch.checkoutIfNeeded();
                    b.backup(content.getProperty("jcr:data"));
                    content.setProperty("jcr:data", (InputStream)new ByteArrayInputStream(text.getBytes("UTF-8")));
                    content.setProperty("jcr:lastModified", Calendar.getInstance());
                }
                finally {
                    ch.cleanup();
                }
            }
            this.log.debug("No changes in file node {}", (Object)n.getPath());
        }
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private void processNonFileNode(Node n, NodeBackup b, Writer logOutput) throws IOException, RepositoryException {
        this.log.debug("Processing non-file node {}", (Object)n.getPath());
        int changes = 0;
        CheckoutHandler ch = new CheckoutHandler(n);
        try {
            changes += this.upgradeNode(this.resourceTypeMap, n, "sling:resourceType", b, ch, logOutput);
            changes += this.upgradeNode(this.resourceTypeMap, n, "sling:resourceSuperType", b, ch, logOutput);
        }
        finally {
            ch.cleanup();
        }
        if ((changes += this.upgradeNode(this.templatesMap, n, "cq:template", b, ch, logOutput)) > 0) {
            this.info(logOutput, "" + changes + " property values changed on node " + n.getPath());
        }
    }

    private int upgradeNode(Map<String, String> mapping, Node n, String propertyName, NodeBackup b, CheckoutHandler ch, Writer logOutput) throws RepositoryException, IOException {
        if (!n.hasProperty(propertyName)) {
            return 0;
        }
        int result = 0;
        Property p = n.getProperty(propertyName);
        String oldValue = p.getString();
        String newValue = mapping.get(oldValue);
        if (newValue != null && !newValue.equals(oldValue)) {
            ch.checkoutIfNeeded();
            b.backup(p);
            n.setProperty(propertyName, newValue);
            this.info(logOutput, "Changing property " + p.getPath() + " from " + oldValue + " to " + newValue);
            ++result;
        }
        return result;
    }

    private void info(Writer logOutput, String msg) {
        this.log.info(msg);
        if (logOutput != null) {
            try {
                logOutput.write(msg);
                logOutput.write(10);
            }
            catch (IOException ignore) {
                // empty catch block
            }
        }
    }

    private Map<String, String> loadMappings(Session session, String path) throws IOException, RepositoryException {
        while (path.startsWith("/")) {
            path = path.substring(1);
        }
        String propPath = path + "/" + "jcr:content/jcr:data";
        if (!session.itemExists("/" + propPath)) {
            throw new IOException("Mappings path not found:" + propPath);
        }
        String libsPrefix = "/libs/";
        InputStream in = session.getRootNode().getProperty(propPath).getStream();
        HashMap<String, String> mapping = new HashMap<String, String>();
        for (Object o : IOUtils.readLines((InputStream)in)) {
            String[] elems;
            String line = o.toString().trim();
            if (line.startsWith("#") || (elems = Text.explode((String)line, (int)124)).length <= 1) continue;
            String from = elems[0].trim();
            String to = elems[1].trim();
            if (to.indexOf(63) >= 0) continue;
            this.log.debug("Adding resource type mapping from {} to {}", (Object)from, (Object)to);
            mapping.put(from, to);
            from = "/libs/" + from;
            to = "/libs/" + to;
            this.log.debug("Adding absolute resource type mapping from {} to {}", (Object)from, (Object)to);
            mapping.put(from, to);
        }
        IOUtils.closeQuietly((InputStream)in);
        return mapping;
    }

    @Override
    public void setMaxFileSize(int len) {
        this.maxFileSize = len;
    }

    @Override
    public int getMaxFileSize() {
        return this.maxFileSize;
    }

    private String loadContent(InputStream s, String path) throws IOException {
        StringWriter sw = new StringWriter();
        InputStreamReader in = new InputStreamReader(s, "UTF-8");
        char[] buffer = new char[32768];
        long count = 0;
        int n = 0;
        while (-1 != (n = in.read(buffer))) {
            sw.write(buffer, 0, n);
            if ((count += (long)n) <= (long)this.maxFileSize) continue;
            throw new FileTooLargeException("File is larger than " + this.maxFileSize + " bytes (" + path + ")");
        }
        return sw.toString();
    }

    @Override
    public String getProgressInfo() {
        return this.currentPath;
    }

    @Override
    public void stop() {
        this.stop = true;
    }

    @Override
    public int getTooLargeFilesCount() {
        return this.tooLargeFilesCount;
    }

    @Override
    public void setFileAcceptRegexp(String regexp) {
        this.fileAcceptRegexp = Pattern.compile(regexp);
    }

    @Override
    public String getFileAcceptRegexp() {
        return this.fileAcceptRegexp.pattern();
    }

    private class CheckoutHandler {
        private final Node n;
        private boolean needToCheckin;

        CheckoutHandler(Node n) {
            this.n = n;
        }

        void checkoutIfNeeded() throws RepositoryException {
            if (!this.n.isCheckedOut()) {
                CodeUpgraderImpl.this.log.info("Checking out node {}", (Object)this.n.getPath());
                this.n.checkout();
                this.needToCheckin = true;
            }
        }

        void cleanup() throws RepositoryException {
            if (this.needToCheckin) {
                CodeUpgraderImpl.this.log.info("Node has been checked out, saving and checking back in {}", (Object)this.n.getPath());
                this.n.save();
                this.n.checkin();
            }
        }
    }

    static class FileTooLargeException
    extends IOException {
        FileTooLargeException(String reason) {
            super(reason);
        }
    }

}