StaticContentBuilder.java 15.5 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.text.GlobPattern
 *  javax.jcr.Credentials
 *  javax.jcr.Item
 *  javax.jcr.Node
 *  javax.jcr.Repository
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  javax.jcr.SimpleCredentials
 *  javax.jcr.Workspace
 *  javax.jcr.security.AccessControlException
 *  org.apache.commons.httpclient.HttpClient
 *  org.apache.commons.httpclient.HttpConnectionManager
 *  org.apache.commons.httpclient.HttpException
 *  org.apache.commons.httpclient.HttpMethod
 *  org.apache.commons.httpclient.MultiThreadedHttpConnectionManager
 *  org.apache.commons.httpclient.methods.GetMethod
 *  org.apache.commons.io.IOUtils
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Deactivate
 *  org.apache.felix.scr.annotations.Modified
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.jackrabbit.util.Text
 *  org.apache.sling.api.resource.ValueMap
 *  org.apache.sling.commons.osgi.PropertiesUtil
 */
package com.day.cq.replication.impl.content;

import com.day.cq.replication.AgentConfig;
import com.day.cq.replication.ContentBuilder;
import com.day.cq.replication.ReplicationAction;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.ReplicationContent;
import com.day.cq.replication.ReplicationContentFactory;
import com.day.cq.replication.ReplicationException;
import com.day.cq.replication.ReplicationLog;
import com.day.text.GlobPattern;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.jcr.Credentials;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Workspace;
import javax.jcr.security.AccessControlException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
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.util.Text;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.osgi.PropertiesUtil;

@Component(name="com.day.cq.replication.content.StaticContentBuilder", metatype=1, label="%static.builder.name", description="%static.builder.description")
@Service(value={ContentBuilder.class})
public class StaticContentBuilder
implements ContentBuilder {
    @Property(name="name", propertyPrivate=1)
    private static final String NAME = "static";
    private static final String DEFAULT_HOST = "localhost";
    @Property(value={"localhost"})
    private static final String PROP_HOST = "host";
    private static final int DEFAULT_PORT = 4502;
    @Property(intValue={4502})
    private static final String PROP_PORT = "port";
    private static final String TITLE = "Static Content Builder";
    private static final String MT_ZIP = "application/zip";
    private static final String REPO_DESC_ID = "crx.repository.systemid";
    private static final String REPO_DESC_CLUSTER_ID = "crx.cluster.id";
    private HttpClient httpClient;
    private String serverHost;
    private int serverPort;
    @Reference
    private Repository repository;
    private String repositoryId;
    private Map<String, String> tokenMap = new HashMap<String, String>();
    private String name;

    @Activate
    private void activate(Map<String, Object> configuration) {
        this.name = PropertiesUtil.toString((Object)configuration.get("name"), (String)"static");
        MultiThreadedHttpConnectionManager conMgr = new MultiThreadedHttpConnectionManager();
        this.httpClient = new HttpClient((HttpConnectionManager)conMgr);
        this.serverHost = PropertiesUtil.toString((Object)configuration.get("host"), (String)"localhost");
        this.serverPort = PropertiesUtil.toInteger((Object)configuration.get("port"), (int)4502);
        this.repositoryId = this.repository.getDescriptor("crx.cluster.id");
        if (this.repositoryId == null) {
            this.repositoryId = this.repository.getDescriptor("crx.repository.systemid");
        }
    }

    @Modified
    private void modified(Map<String, Object> configuration) {
        this.deactivate(configuration);
        this.activate(configuration);
    }

    @Deactivate
    private void deactivate(Map<String, Object> configuration) {
        if (this.httpClient != null) {
            ((MultiThreadedHttpConnectionManager)this.httpClient.getHttpConnectionManager()).shutdown();
            this.httpClient = null;
        }
        this.tokenMap.clear();
    }

    private Rule[] parseRules(String s, ReplicationLog log) {
        String[] lines;
        ArrayList<Rule> rules = new ArrayList<Rule>();
        for (String line : lines = Text.explode((String)s, (int)10)) {
            String[] rule = Text.explode((String)line, (int)32);
            if (rule.length != 2) {
                log.warn(String.format("Rule invalid: %s, should consist of two elements separated by spaces.", line));
                continue;
            }
            rules.add(new Rule(rule[0], rule[1]));
        }
        Rule[] result = new Rule[rules.size()];
        rules.toArray(result);
        return result;
    }

    private String createTokenCookie(Session session) throws ReplicationException {
        if (this.repositoryId == null) {
            throw new ReplicationException("Unable to obtain repository id.");
        }
        Session s1 = null;
        try {
            SimpleCredentials c = new SimpleCredentials(session.getUserID(), new char[0]);
            c.setAttribute(".token", (Object)"");
            s1 = session.impersonate((Credentials)c);
            String value = Text.escape((String)String.format("%s:%s:%s", this.repositoryId, c.getAttribute(".token"), session.getWorkspace().getName()));
            String string = String.format("login-token=%s", value);
            return string;
        }
        catch (RepositoryException e) {
            throw new ReplicationException("Unable to impersonate same user", (Exception)e);
        }
        finally {
            if (s1 != null) {
                s1.logout();
            }
        }
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private String getOrCreateTokenCookie(Session session, boolean create) throws ReplicationException {
        String tokenCookie;
        Map<String, String> map;
        if (!create) {
            map = this.tokenMap;
            synchronized (map) {
                tokenCookie = this.tokenMap.get(session.getUserID());
                if (tokenCookie != null) {
                    return tokenCookie;
                }
            }
        }
        tokenCookie = this.createTokenCookie(session);
        map = this.tokenMap;
        synchronized (map) {
            this.tokenMap.put(session.getUserID(), tokenCookie);
        }
        return tokenCookie;
    }

    @Override
    public ReplicationContent create(Session session, ReplicationAction action, ReplicationContentFactory factory) throws ReplicationException {
        if (action.getType() != ReplicationActionType.ACTIVATE) {
            return ReplicationContent.VOID;
        }
        AgentConfig config = action.getConfig();
        if (config == null) {
            throw new ReplicationException("No agent configuration found.");
        }
        ReplicationLog log = action.getLog();
        if (log == null) {
            throw new ReplicationException("No replication log found.");
        }
        String s = (String)config.getProperties().get("definition", String.class);
        if (s == null || s.length() == 0) {
            log.info(String.format("No rules definition found in agent %s", config.getName()));
            return ReplicationContent.VOID;
        }
        Rule[] rules = this.parseRules(s, log);
        if (rules.length == 0) {
            log.info(String.format("Rules definition in agent %s did not contain a valid rule", config.getName()));
            return ReplicationContent.VOID;
        }
        try {
            Node node = (Node)session.getItem(action.getPath());
            return this.create(rules, factory, node, log);
        }
        catch (AccessControlException e) {
            log.error(String.format("Agent cannot access %s: %s", action.getPath(), e.getMessage()));
            throw new ReplicationException("Agent is unable to access " + action.getPath(), (Exception)e);
        }
        catch (RepositoryException e) {
            log.error(String.format("Repository exception occurred during serialization of content %s.", action.getPath()));
            throw new ReplicationException("RepositoryException during serialization", (Exception)e);
        }
        catch (IOException e) {
            log.error(String.format("I/O error occurred during serialization of content %s", action.getPath()));
            throw new ReplicationException("I/O error during serialization", e);
        }
    }

    @Override
    public ReplicationContent create(Session session, ReplicationAction action, ReplicationContentFactory factory, Map<String, Object> parameters) throws ReplicationException {
        return this.create(session, action, factory);
    }

    private ReplicationContent create(Rule[] rules, ReplicationContentFactory factory, Node node, ReplicationLog log) throws ReplicationException, RepositoryException, IOException {
        HashMap<String, byte[]> binaries = new HashMap<String, byte[]>();
        String path = node.getPath();
        for (Rule rule : rules) {
            byte[] data;
            log.debug(String.format("Checking rule (%s) against node path %s", rule, path));
            String urlPath = rule.replace(path);
            if (urlPath == null) {
                log.debug(String.format("Rule (%s) did not match.", rule));
                continue;
            }
            try {
                data = this.getContent(urlPath, node.getSession(), log);
            }
            catch (ReplicationException e) {
                log.warn(String.format("Unable to retrieve content: %s", e.getMessage()));
                continue;
            }
            int query = urlPath.indexOf(63);
            if (query != -1) {
                urlPath = urlPath.substring(0, query);
            }
            binaries.put(urlPath, data);
        }
        if (binaries.size() == 0) {
            log.debug(String.format("No rule did match for node path %s.", path));
            return ReplicationContent.VOID;
        }
        File file = this.createZip(binaries);
        try {
            return factory.create("application/zip", file, true);
        }
        catch (IOException e) {
            boolean deleted = file.delete();
            log.debug("file {} deleted : {}", file.getAbsolutePath(), deleted);
            throw new ReplicationException(e);
        }
    }

    private byte[] getContent(String path, Session session, ReplicationLog log) throws RepositoryException, ReplicationException {
        int query;
        StringBuilder sb = new StringBuilder("http://");
        sb.append(this.serverHost);
        if (this.serverPort != 80) {
            sb.append(':').append(this.serverPort);
        }
        if ((query = path.indexOf(63)) != -1) {
            sb.append(Text.escapePath((String)path.substring(0, query)));
            sb.append('?');
            sb.append(path.substring(query + 1));
        } else {
            sb.append(Text.escapePath((String)path));
        }
        String url = sb.toString();
        log.debug(String.format("Requesting %s", url));
        boolean create = false;
        do {
            GetMethod getReq = new GetMethod(url);
            getReq.setFollowRedirects(true);
            getReq.setRequestHeader("Cookie", this.getOrCreateTokenCookie(session, create));
            try {
                int status = this.httpClient.executeMethod((HttpMethod)getReq);
                if (status == 200) {
                    byte[] arrby = getReq.getResponseBody();
                    return arrby;
                }
                if (!(status != 404 && status != 401 || create)) {
                    create = true;
                    continue;
                }
                try {
                    throw new ReplicationException("Unable to fetch content from " + url + " : " + status);
                }
                catch (HttpException e) {
                    throw new ReplicationException((Exception)e);
                }
                catch (IOException e) {
                    throw new ReplicationException(e);
                }
            }
            finally {
                getReq.releaseConnection();
                continue;
            }
            break;
        } while (true);
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private File createZip(Map<String, byte[]> files) throws IOException {
        File tmpFile = File.createTempFile("cq5", ".zip");
        ZipOutputStream out = null;
        boolean successful = false;
        try {
            out = new ZipOutputStream(new FileOutputStream(tmpFile));
            for (Map.Entry<String, byte[]> entry : files.entrySet()) {
                out.putNextEntry(new ZipEntry(entry.getKey()));
                out.write(entry.getValue());
            }
            successful = true;
            File file = tmpFile;
            return file;
        }
        finally {
            IOUtils.closeQuietly((OutputStream)out);
            if (!successful && !tmpFile.delete()) {
                throw new IOException("tmp zip file could not be deleted");
            }
        }
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getTitle() {
        return "Static Content Builder";
    }

    protected void bindRepository(Repository repository) {
        this.repository = repository;
    }

    protected void unbindRepository(Repository repository) {
        if (this.repository == repository) {
            this.repository = null;
        }
    }

    private static class Rule {
        private final GlobPattern pattern;
        private final String replacement;

        public Rule(String pattern, String replacement) {
            this.pattern = new GlobPattern(pattern);
            this.replacement = replacement;
        }

        public String replace(String input) {
            if (this.pattern.matches(input)) {
                Properties p = new Properties();
                p.setProperty("path", input);
                return Text.replaceVariables((Properties)p, (String)this.replacement, (boolean)false);
            }
            return null;
        }

        public String toString() {
            return this.pattern.toString() + " -> " + this.replacement;
        }
    }

}