jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r1512568 [23/39] - in /jackrabbit/commons/filevault/trunk: ./ parent/ vault-cli/ vault-cli/src/ vault-cli/src/main/ vault-cli/src/main/appassembler/ vault-cli/src/main/assembly/ vault-cli/src/main/java/ vault-cli/src/main/java/org/ vault-cl...
Date Sat, 10 Aug 2013 05:53:54 GMT
Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryCopier.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryCopier.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryCopier.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryCopier.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,588 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * Repository Copier that copies content from a source to a destination repository.
+ */
+public class RepositoryCopier {
+
+    /**
+     * default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(RepositoryCopier.class);
+
+    protected ProgressTrackerListener tracker;
+
+    private int numNodes = 0;
+
+    private int totalNodes = 0;
+
+    private long totalSize = 0;
+
+    private long currentSize = 0;
+
+    private int batchSize = 1024;
+
+    private long throttle = 0;
+
+    private long start = 0;
+
+    private String lastKnownGood;
+
+    private String currentPath;
+
+    private String resumeFrom;
+
+    private WorkspaceFilter srcFilter;
+
+    private Map<String, String> prefixMapping = new HashMap<String, String>();
+
+    private boolean onlyNewer;
+
+    private boolean update;
+
+    private boolean noOrdering;
+
+    private Session srcSession;
+
+    private Session dstSession;
+
+    private String  cqLastModified;
+
+    private volatile boolean abort;
+
+    public void setTracker(ProgressTrackerListener tracker) {
+        this.tracker = tracker;
+    }
+
+    public int getBatchSize() {
+        return batchSize;
+    }
+
+    public void setBatchSize(int batchSize) {
+        this.batchSize = batchSize;
+    }
+
+    public long getThrottle() {
+        return throttle;
+    }
+
+    public void setThrottle(long throttle) {
+        this.throttle = throttle;
+    }
+
+    public void setSourceFilter(WorkspaceFilter srcFilter) {
+        this.srcFilter = srcFilter;
+    }
+
+    public void setOnlyNewer(boolean onlyNewer) {
+        this.onlyNewer = onlyNewer;
+    }
+
+    public void setUpdate(boolean update) {
+        this.update = update;
+    }
+
+    public boolean isNoOrdering() {
+        return noOrdering;
+    }
+
+    public void setNoOrdering(boolean noOrdering) {
+        this.noOrdering = noOrdering;
+    }
+
+    public boolean isOnlyNewer() {
+        return onlyNewer;
+    }
+
+    public boolean isUpdate() {
+        return update;
+    }
+
+    public WorkspaceFilter getSrcFilter() {
+        return srcFilter;
+    }
+
+    public String getResumeFrom() {
+        return resumeFrom;
+    }
+
+    public void setResumeFrom(String resumeFrom) {
+        this.resumeFrom = resumeFrom;
+    }
+
+    public String getLastKnownGood() {
+        return lastKnownGood;
+    }
+
+    public String getCurrentPath() {
+        return currentPath;
+    }
+
+    public int getCurrentNumNodes() {
+        return numNodes;
+    }
+
+    public int getTotalNodes() {
+        return totalNodes;
+    }
+
+    public long getTotalSize() {
+        return totalSize;
+    }
+
+    public long getCurrentSize() {
+        return currentSize;
+    }
+
+    public void abort() {
+        abort = true;
+    }
+
+    public void copy(RepositoryAddress src, RepositoryAddress dst, boolean recursive) {
+        track("", "Copy %s to %s (%srecursive)", src, dst, recursive ? "" : "non-");
+
+        Session srcSession = null;
+        Session dstSession = null;
+        try {
+            RepositoryProvider repProvider = new RepositoryProvider();
+            Repository srcRepo;
+            try {
+                srcRepo = repProvider.getRepository(src);
+            } catch (RepositoryException e) {
+                log.error("Error while retrieving src repository {}: {}", src, e.toString());
+                return;
+            }
+            Repository dstRepo;
+            try {
+                dstRepo = repProvider.getRepository(dst);
+            } catch (RepositoryException e) {
+                log.error("Error while retrieving dst repository {}: {}", dst, e.toString());
+                return;
+            }
+
+            try {
+                Credentials srcCreds = src.getCredentials();
+                srcSession = srcRepo.login(srcCreds, src.getWorkspace());
+            } catch (RepositoryException e) {
+                log.error("Error while logging in src repository {}: {}", src, e.toString());
+                return;
+            }
+
+            try {
+                Credentials dstCreds = dst.getCredentials();
+                dstSession = dstRepo.login(dstCreds, dst.getWorkspace());
+            } catch (RepositoryException e) {
+                log.error("Error while logging in dst repository {}: {}", dst, e.toString());
+                return;
+            }
+            copy(srcSession, src.getPath(), dstSession, dst.getPath(), recursive);
+        } finally {
+            if (srcSession != null) {
+                srcSession.logout();
+            }
+            if (dstSession != null) {
+                dstSession.logout();
+            }
+        }
+    }
+
+    public void copy(Session srcSession, String srcPath, Session dstSession, String dstPath, boolean recursive) {
+        if (srcSession == null || dstSession == null) {
+            throw new IllegalArgumentException("no src or dst session provided");
+        }
+        this.srcSession = srcSession;
+        this.dstSession = dstSession;
+
+        // get root nodes
+        String dstParent = Text.getRelativeParent(dstPath, 1);
+        String dstName = checkNameSpace(Text.getName(dstPath));
+        Node srcRoot;
+        try {
+            srcRoot = srcSession.getNode(srcPath);
+        } catch (RepositoryException e) {
+            log.error("Error while retrieving src node {}: {}", srcPath, e.toString());
+            return;
+        }
+        Node dstRoot;
+        try {
+            dstRoot = dstSession.getNode(dstParent);
+        } catch (RepositoryException e) {
+            log.error("Error while retrieving dst parent node {}: {}", dstParent, e.toString());
+            return;
+        }
+        // check if the cq namespace exists
+        try {
+            cqLastModified = srcSession.getNamespacePrefix("http://www.day.com/jcr/cq/1.0") + ":lastModified";
+        } catch (RepositoryException e) {
+            // ignore
+        }
+        try {
+            numNodes = 0;
+            totalNodes = 0;
+            currentSize = 0;
+            totalSize = 0;
+            start = System.currentTimeMillis();
+            copy(srcRoot, dstRoot, dstName, recursive);
+            if (numNodes > 0) {
+                track("", "Saving %d nodes...", numNodes);
+                dstSession.save();
+                track("", "Done.");
+            }
+            long end = System.currentTimeMillis();
+            track("", "Copy completed. %d nodes in %dms. %d bytes", totalNodes, end-start, totalSize);
+        } catch (RepositoryException e) {
+            log.error("Error during copy: {}", e.toString());
+        }
+    }
+
+    private void copy(Node src, Node dstParent, String dstName, boolean recursive)
+            throws RepositoryException {
+        if (abort) {
+            return;
+        }
+        String path = src.getPath();
+        currentPath = path;
+        String dstPath = dstParent.getPath() + "/" + dstName;
+        if (srcFilter != null && !srcFilter.contains(path)) {
+            track(path, "------ I");
+            return;
+        }
+
+        boolean skip = false;
+        if (resumeFrom != null) {
+            if (path.equals(resumeFrom)) {
+                // found last node, resuming
+                resumeFrom = null;
+            } else {
+                skip = true;
+            }
+        }
+
+        // check for special node that need sysview import handling
+        boolean useSysView = src.getDefinition().isProtected();
+        Node dst;
+        boolean isNew = false;
+        boolean overwrite = update;
+        if (dstParent.hasNode(dstName)) {
+            dst = dstParent.getNode(dstName);
+            if (skip) {
+                track(path, "------ S");
+            } else if (overwrite) {
+                if (onlyNewer && dstName.equals("jcr:content")) {
+                    if (isNewer(src, dst)) {
+                        track(dstPath, "%06d U", ++totalNodes);
+                    } else {
+                        overwrite = false;
+                        recursive = false;
+                        track(dstPath, "%06d -", ++totalNodes);
+                    }
+                } else {
+                    track(dstPath, "%06d U", ++totalNodes);
+                }
+                if (useSysView) {
+                    dst = sysCopy(src, dstParent, dstName);
+                }
+            } else {
+                track(dstPath, "%06d -", ++totalNodes);
+            }
+        } else {
+            try {
+                if (skip) {
+                    track(path, "------ S");
+                    dst = null;
+                } else if (useSysView) {
+                    dst = sysCopy(src, dstParent, dstName);
+                } else {
+                    dst = dstParent.addNode(dstName, src.getPrimaryNodeType().getName());
+                }
+                track(dstPath, "%06d A", ++totalNodes);
+                isNew = true;
+            } catch (RepositoryException e) {
+                log.warn("Error while adding node {} (ignored): {}", dstPath, e.toString());
+                return;
+            }
+        }
+        if (useSysView) {
+            if (!skip) {
+                // track changes
+                trackTree(dst, isNew);
+            }
+        } else {
+            Set<String> names = new HashSet<String>();
+            if (!skip && (overwrite || isNew)) {
+                if (!isNew) {
+                    for (NodeType nt: dst.getMixinNodeTypes()) {
+                        names.add(nt.getName());
+                    }
+                    // add mixins
+                    for (NodeType nt: src.getMixinNodeTypes()) {
+                        String mixName = checkNameSpace(nt.getName());
+                        if (!names.remove(mixName)) {
+                            dst.addMixin(nt.getName());
+                        }
+                    }
+                    // handle removed mixins
+                    for (String mix: names) {
+                        dst.removeMixin(mix);
+                    }
+                } else {
+                    // add mixins
+                    for (NodeType nt: src.getMixinNodeTypes()) {
+                        dst.addMixin(checkNameSpace(nt.getName()));
+                    }
+                }
+
+                // add properties
+                names.clear();
+                if (!isNew) {
+                    PropertyIterator iter = dst.getProperties();
+                    while (iter.hasNext()) {
+                        names.add(checkNameSpace(iter.nextProperty().getName()));
+                    }
+                }
+                PropertyIterator iter = src.getProperties();
+                while (iter.hasNext()) {
+                    Property p = iter.nextProperty();
+                    String pName = checkNameSpace(p.getName());
+                    names.remove(pName);
+                    // ignore protected
+                    if (p.getDefinition().isProtected()) {
+                        continue;
+                    }
+                    // remove destination property to avoid type clashes
+                    if (dst.hasProperty(pName)) {
+                        dst.getProperty(pName).remove();
+                    }
+                    if (p.getDefinition().isMultiple()) {
+                        Value[] vs = p.getValues();
+                        dst.setProperty(pName, vs);
+                        for (long s: p.getLengths()) {
+                            totalSize+=s;
+                            currentSize+=s;
+                        }
+                    } else {
+                        Value v = p.getValue();
+                        dst.setProperty(pName, v);
+                        long s= p.getLength();
+                        totalSize+=s;
+                        currentSize+=s;
+                    }
+                }
+                // remove obsolete properties
+                for (String pName: names) {
+                    try {
+                        dst.getProperty(pName).remove();
+                    } catch (RepositoryException e) {
+                        // ignore
+                    }
+                }
+            }
+
+            // descend
+            if (recursive && dst != null) {
+                names.clear();
+                if (overwrite && !isNew) {
+                    NodeIterator niter = dst.getNodes();
+                    while (niter.hasNext()) {
+                        names.add(checkNameSpace(niter.nextNode().getName()));
+                    }
+                }
+                NodeIterator niter = src.getNodes();
+                while (niter.hasNext()) {
+                    Node child = niter.nextNode();
+                    String cName = checkNameSpace(child.getName());
+                    names.remove(cName);
+                    copy(child, dst, cName, true);
+                }
+                if (resumeFrom == null) {
+                    // check if we need to order
+                    if (overwrite && !isNew && !noOrdering && src.getPrimaryNodeType().hasOrderableChildNodes()) {
+                        niter = src.getNodes();
+                        while (niter.hasNext()) {
+                            Node child = niter.nextNode();
+                            String name = child.getName();
+                            if (dst.hasNode(name)) {
+                                dst.orderBefore(name, null);
+                            }
+                        }
+                    }
+
+                    // remove obsolete child nodes
+                    for (String name: names) {
+                        try {
+                            Node cNode = dst.getNode(name);
+                            track(cNode.getPath(), "%06d D", ++totalNodes);
+                            cNode.remove();
+                        } catch (RepositoryException e) {
+                            // ignore
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!skip) {
+            numNodes++;
+        }
+
+        // check for save
+        if (numNodes >= batchSize) {
+            try {
+                track("", "Intermediate saving %d nodes (%d kB)...", numNodes, currentSize/1000);
+                long now = System.currentTimeMillis();
+                dstSession.save();
+                long end = System.currentTimeMillis();
+                track("", "Done in %d ms. Total time: %d, total nodes %d, %d kB", end-now, end-start, totalNodes, totalSize/1000);
+                lastKnownGood = currentPath;
+                numNodes = 0;
+                currentSize = 0;
+                if (throttle > 0) {
+                    track("", "Throttling enabled. Waiting %d second%s...", throttle, throttle == 1 ? "" : "s");
+                    try {
+                        Thread.sleep(throttle * 1000);
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                }
+            } catch (RepositoryException e) {
+                log.error("Error during intermediate save ({}); try again later: {}", numNodes, e.toString());
+            }
+        }
+    }
+
+    private Node sysCopy(Node src, Node dstParent, String dstName) throws RepositoryException {
+        try {
+            ContentHandler handler = dstParent.getSession().getImportContentHandler(dstParent.getPath(), ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+            src.getSession().exportSystemView(src.getPath(), handler, true, false);
+            return dstParent.getNode(dstName);
+        } catch (SAXException e) {
+            throw new RepositoryException("Unable to perform sysview copy", e);
+        }
+    }
+
+    private void trackTree(Node node, boolean isNew) throws RepositoryException {
+        NodeIterator iter = node.getNodes();
+        while (iter.hasNext()) {
+            Node child = iter.nextNode();
+            if (isNew) {
+                track(child.getPath(), "%06d A", ++totalNodes);
+            } else {
+                track(child.getPath(), "%06d U", ++totalNodes);
+            }
+            trackTree(child, isNew);
+        }
+    }
+    /**
+     * Checks if <code>src</code> node is newer than <code>dst</code> node.
+     * this only applies if the nodes have either a "jcr:lastModified" or
+     * "cq:lastModified" property.
+     *
+     * @param src source node
+     * @param dst destination node
+     * @return <code>true</code> if src is newer than dst node or if the
+     *         nodes could not be compared
+     */
+    private boolean isNewer(Node src, Node dst) {
+        try {
+            Calendar srcDate = null;
+            Calendar dstDate = null;
+            if (cqLastModified != null && src.hasProperty(cqLastModified) && dst.hasProperty(cqLastModified)) {
+                srcDate = src.getProperty(cqLastModified).getDate();
+                dstDate = dst.getProperty(cqLastModified).getDate();
+            } else if (src.hasProperty(JcrConstants.JCR_LASTMODIFIED) && dst.hasProperty(JcrConstants.JCR_LASTMODIFIED)) {
+                srcDate = src.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate();
+                dstDate = dst.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate();
+            }
+            return srcDate == null || dstDate == null || srcDate.after(dstDate);
+        } catch (RepositoryException e) {
+            log.error("Unable to compare dates: {}", e.toString());
+            return true;
+        }
+    }
+
+    private String checkNameSpace(String name) {
+        try {
+            int idx = name.indexOf(':');
+            if (idx > 0) {
+                String prefix = name.substring(0, idx);
+                String mapped = prefixMapping.get(prefix);
+                if (mapped == null) {
+                    String uri = srcSession.getNamespaceURI(prefix);
+                    try {
+                        mapped = dstSession.getNamespacePrefix(uri);
+                    } catch (NamespaceException e) {
+                        mapped = prefix;
+                        int i=0;
+                        while (i>=0) {
+                            try {
+                                dstSession.getWorkspace().getNamespaceRegistry().registerNamespace(mapped, uri);
+                                i=-1;
+                            } catch (NamespaceException e1) {
+                                mapped = prefix + i++;
+                            }
+                        }
+                    }
+                    prefixMapping.put(prefix, mapped);
+                }
+                if (mapped.equals(prefix)) {
+                    return name;
+                } else {
+                    return mapped + prefix.substring(idx);
+                }
+            }
+        } catch (RepositoryException e) {
+            log.error("Error processing namespace for {}: {}", name, e.toString());
+        }
+        return name;
+    }
+
+    private void track(String path, String fmt, Object ... args) {
+        if (tracker != null) {
+            tracker.onMessage(ProgressTrackerListener.Mode.TEXT, String.format(fmt, args), path);
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryProvider.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryProvider.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/RepositoryProvider.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.imageio.spi.ServiceRegistry;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.api.RepositoryFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>RepositoryProvider</code>...
+ *
+ */
+public class RepositoryProvider {
+
+    protected static Logger log = LoggerFactory.getLogger(RepositoryProvider.class);
+
+    private Map<RepositoryAddress, Repository> repos
+            = new HashMap<RepositoryAddress, Repository>();
+
+    public Repository getRepository(RepositoryAddress address)
+            throws RepositoryException {
+        Repository rep = repos.get(address);
+        if (rep == null) {
+            rep = createRepository(address);
+            repos.put(address, rep);
+        }
+        return rep;
+    }
+
+    private Repository createRepository(RepositoryAddress address)
+            throws RepositoryException {
+        Iterator<RepositoryFactory> iter = ServiceRegistry.lookupProviders(RepositoryFactory.class);
+        Set<String> supported = new HashSet<String>();
+        while (iter.hasNext()) {
+            RepositoryFactory fac = iter.next();
+            supported.addAll(fac.getSupportedSchemes());
+            Repository rep = fac.createRepository(address);
+            if (rep != null) {
+                // wrap JCR logger
+                if (Boolean.getBoolean("jcrlog.sysout") || System.getProperty("jcrlog.file") != null) {
+                    Repository wrapped = wrapLogger(rep, address);
+                    if (wrapped != null) {
+                        log.info("Enabling JCR Logger.");
+                        rep = wrapped;
+                    }
+                }
+                return rep;
+            }
+        }
+        StringBuffer msg = new StringBuffer("URL scheme ");
+        msg.append(address.getURI().getScheme());
+        msg.append(" not supported. only");
+        for (String s: supported) {
+            msg.append(" ").append(s);
+        }
+        throw new RepositoryException(msg.toString());
+    }
+
+    private Repository wrapLogger(Repository base,RepositoryAddress address) {
+        try {
+            Class clazz = getClass().getClassLoader().loadClass("org.apache.jackrabbit.jcrlog.RepositoryLogger");
+            // just map all properties
+            Properties props = new Properties();
+            for (Object o: System.getProperties().keySet()) {
+                String name = o.toString();
+                if (name.startsWith("jcrlog.")) {
+                    props.put(name.substring("jcrlog.".length()), System.getProperty(name));
+                }
+            }
+            Constructor c = clazz.getConstructor(Repository.class, Properties.class, String.class);
+            return (Repository) c.newInstance(base, props, address.toString());
+        } catch (Exception e) {
+            log.error("Unable to initialize JCR logger: {}", e.toString());
+            return null;
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/SHA1.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/SHA1.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/SHA1.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/SHA1.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * SHA1 abstraction
+ */
+public class SHA1 {
+
+    public final static SHA1 NULL = new SHA1(0,0,0,0,0);
+
+    private final int w0;
+
+    private final int w1;
+
+    private final int w2;
+
+    private final int w3;
+
+    private final int w4;
+
+    public SHA1(int w0, int w1, int w2, int w3, int w4) {
+        this.w0 = w0;
+        this.w1 = w1;
+        this.w2 = w2;
+        this.w3 = w3;
+        this.w4 = w4;
+    }
+
+    public SHA1(String str) {
+        if (str.length() != 40) {
+            throw new IllegalArgumentException("invalid string length " + str.length());
+        }
+        w0 = (int) Long.parseLong(str.substring(0x00, 0x08), 16);
+        w1 = (int) Long.parseLong(str.substring(0x08, 0x10), 16);
+        w2 = (int) Long.parseLong(str.substring(0x10, 0x18), 16);
+        w3 = (int) Long.parseLong(str.substring(0x18, 0x20), 16);
+        w4 = (int) Long.parseLong(str.substring(0x20, 0x28), 16);
+    }
+
+    public SHA1(byte[] bytes) {
+        if (bytes.length != 20) {
+            throw new IllegalArgumentException("invalid bytes length " + bytes.length);
+        }
+        w0 = getInt(bytes, 0);
+        w1 = getInt(bytes, 4);
+        w2 = getInt(bytes, 8);
+        w3 = getInt(bytes, 12);
+        w4 = getInt(bytes, 16);
+    }
+
+    public int[] getInts() {
+        return new int[]{w0, w1, w2, w3, w4};
+    }
+
+    public byte[] getBytes() {
+        byte[] buf = new byte[20];
+        setInt(buf, 0, w0);
+        setInt(buf, 4, w1);
+        setInt(buf, 8, w2);
+        setInt(buf, 12, w3);
+        setInt(buf, 16, w4);
+        return buf;
+    }
+
+    public static SHA1 digest(InputStream in) throws IOException {
+        try {
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance("SHA-1");
+            } catch (NoSuchAlgorithmException e) {
+                throw new IllegalArgumentException(e.toString());
+            }
+            byte[] buffer = new byte[8192];
+            int read;
+            while ((read = in.read(buffer)) > 0) {
+                md.update(buffer, 0, read);
+            }
+            return new SHA1(md.digest());
+        } finally {
+            in.close();
+        }
+    }
+
+    public static SHA1 digest(File file) throws IOException {
+        return digest(FileUtils.openInputStream(file));
+    }
+
+    public String toString() {
+        return String.format("%08x%08x%08x%08x%08x", w0, w1, w2, w3, w4);
+    }
+
+    public int hashCode() {
+        return w2;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SHA1 sha1 = (SHA1) o;
+        return w0 == sha1.w0 && w1 == sha1.w1 && w2 == sha1.w2 && w3 == sha1.w3 && w4 == sha1.w4;
+    }
+
+    private static int getInt(byte[] b, int offs) {
+        return ((b[    offs] & 0xFF) << 24) +
+               ((b[1 + offs] & 0xFF) << 16) +
+               ((b[2 + offs] & 0xFF) << 8) +
+               ((b[3 + offs] & 0xFF));
+    }
+
+    private static void setInt(byte[] b, int offs, int v) {
+        b[offs  ] = (byte) ((v >>> 24) & 0xFF);
+        b[offs+1] = (byte) ((v >>> 16) & 0xFF);
+        b[offs+2] = (byte) ((v >>>  8) & 0xFF);
+        b[offs+3] = (byte) (v & 0xFF);
+    }
+
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/TempFileInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/TempFileInputStream.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/TempFileInputStream.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/TempFileInputStream.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * <code>TempFileInputStream</code>...
+ */
+public class TempFileInputStream extends FileInputStream {
+
+    private File file;
+
+    public TempFileInputStream(String name) throws FileNotFoundException {
+        this(new File(name));
+    }
+
+    public TempFileInputStream(File file) throws FileNotFoundException {
+        super(file);
+        this.file = file;
+    }
+
+    public TempFileInputStream(FileDescriptor fdObj) {
+        super(fdObj);
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+        if (file != null) {
+            file.delete();
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Text.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Text.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Text.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Text.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,784 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Properties;
+
+/**
+ * This Class provides some text related utilities
+ */
+public class Text {
+
+    /**
+     * Hidden constructor.
+     */
+    private Text() {
+    }
+
+    /**
+     * used for the md5
+     */
+    public static final char[] hexTable = "0123456789abcdef".toCharArray();
+
+    /**
+     * Calculate an MD5 hash of the string given.
+     *
+     * @param data the data to encode
+     * @param enc  the character encoding to use
+     * @return a hex encoded string of the md5 digested input
+     */
+    public static String md5(String data, String enc)
+            throws UnsupportedEncodingException {
+        try {
+            return digest("MD5", data.getBytes(enc));
+        } catch (NoSuchAlgorithmException e) {
+            throw new InternalError("MD5 digest not available???");
+        }
+    }
+
+    /**
+     * Calculate an MD5 hash of the string given using 'utf-8' encoding.
+     *
+     * @param data the data to encode
+     * @return a hex encoded string of the md5 digested input
+     */
+    public static String md5(String data) {
+        try {
+            return md5(data, "utf-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new InternalError("UTF8 digest not available???");
+        }
+    }
+
+    /**
+     * Digest the plain string using the given algorithm.
+     *
+     * @param algorithm The alogrithm for the digest. This algorithm must be
+     *                  supported by the MessageDigest class.
+     * @param data      The plain text String to be digested.
+     * @param enc       The character encoding to use
+     * @return The digested plain text String represented as Hex digits.
+     * @throws java.security.NoSuchAlgorithmException     if the desired algorithm is not supported by
+     *                                      the MessageDigest class.
+     * @throws java.io.UnsupportedEncodingException if the encoding is not supported
+     */
+    public static String digest(String algorithm, String data, String enc)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException {
+
+        return digest(algorithm, data.getBytes(enc));
+    }
+
+    /**
+     * Digest the plain string using the given algorithm.
+     *
+     * @param algorithm The algorithm for the digest. This algorithm must be
+     *                  supported by the MessageDigest class.
+     * @param data      the data to digest with the given algorithm
+     * @return The digested plain text String represented as Hex digits.
+     * @throws java.security.NoSuchAlgorithmException if the desired algorithm is not supported by
+     *                                  the MessageDigest class.
+     */
+    public static String digest(String algorithm, byte[] data)
+            throws NoSuchAlgorithmException {
+
+        MessageDigest md = MessageDigest.getInstance(algorithm);
+        byte[] digest = md.digest(data);
+        StringBuffer res = new StringBuffer(digest.length * 2);
+        for (int i = 0; i < digest.length; i++) {
+            byte b = digest[i];
+            res.append(hexTable[(b >> 4) & 15]);
+            res.append(hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+
+    /**
+     * returns an array of strings decomposed of the original string, split at
+     * every occurrence of 'ch'. if 2 'ch' follow each other with no intermediate
+     * characters, empty "" entries are avoided.
+     *
+     * @param str the string to decompose
+     * @param ch  the character to use a split pattern
+     * @return an array of strings
+     */
+    public static String[] explode(String str, int ch) {
+        return explode(str, ch, false);
+    }
+
+    /**
+     * returns an array of strings decomposed of the original string, split at
+     * every occurance of 'ch'.
+     *
+     * @param str          the string to decompose
+     * @param ch           the character to use a split pattern
+     * @param respectEmpty if <code>true</code>, empty elements are generated
+     * @return an array of strings
+     */
+    public static String[] explode(String str, int ch, boolean respectEmpty) {
+        if (str == null || str.length() == 0) {
+            return new String[0];
+        }
+
+        ArrayList<String> strings = new ArrayList<String>();
+        int pos;
+        int lastpos = 0;
+
+        // add snipples
+        while ((pos = str.indexOf(ch, lastpos)) >= 0) {
+            if (pos - lastpos > 0 || respectEmpty) {
+                strings.add(str.substring(lastpos, pos));
+            }
+            lastpos = pos + 1;
+        }
+        // add rest
+        if (lastpos < str.length()) {
+            strings.add(str.substring(lastpos));
+        } else if (respectEmpty && lastpos == str.length()) {
+            strings.add("");
+        }
+
+        // return string array
+        return strings.toArray(new String[strings.size()]);
+    }
+
+    /**
+     * Concatenates all strings in the string array using the specified delimiter.
+     * @param arr
+     * @param delim
+     * @return the concatenated string
+     */
+    public static String implode(String[] arr, String delim) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < arr.length; i++) {
+            if (i > 0) {
+                buf.append(delim);
+            }
+            buf.append(arr[i]);
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Replaces all occurrences of <code>oldString</code> in <code>text</code>
+     * with <code>newString</code>.
+     *
+     * @param text
+     * @param oldString old substring to be replaced with <code>newString</code>
+     * @param newString new substring to replace occurrences of <code>oldString</code>
+     * @return a string
+     */
+    public static String replace(String text, String oldString, String newString) {
+        if (text == null || oldString == null || newString == null) {
+            throw new IllegalArgumentException("null argument");
+        }
+        int pos = text.indexOf(oldString);
+        if (pos == -1) {
+            return text;
+        }
+        int lastPos = 0;
+        StringBuffer sb = new StringBuffer(text.length());
+        while (pos != -1) {
+            sb.append(text.substring(lastPos, pos));
+            sb.append(newString);
+            lastPos = pos + oldString.length();
+            pos = text.indexOf(oldString, lastPos);
+        }
+        if (lastPos < text.length()) {
+            sb.append(text.substring(lastPos));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Replaces illegal XML characters in the given string by their corresponding
+     * predefined entity references.
+     *
+     * @param text text to be escaped
+     * @return a string
+     */
+    public static String encodeIllegalXMLCharacters(String text) {
+        if (text == null) {
+            throw new IllegalArgumentException("null argument");
+        }
+        StringBuffer buf = null;
+        int length = text.length();
+        int pos = 0;
+        for (int i = 0; i < length; i++) {
+            int ch = text.charAt(i);
+            switch (ch) {
+                case '<':
+                case '>':
+                case '&':
+                case '"':
+                case '\'':
+                    if (buf == null) {
+                        buf = new StringBuffer();
+                    }
+                    if (i > 0) {
+                        buf.append(text.substring(pos, i));
+                    }
+                    pos = i + 1;
+                    break;
+                default:
+                    continue;
+            }
+            if (ch == '<') {
+                buf.append("&lt;");
+            } else if (ch == '>') {
+                buf.append("&gt;");
+            } else if (ch == '&') {
+                buf.append("&amp;");
+            } else if (ch == '"') {
+                buf.append("&quot;");
+            } else if (ch == '\'') {
+                buf.append("&apos;");
+            }
+        }
+        if (buf == null) {
+            return text;
+        } else {
+            if (pos < length) {
+                buf.append(text.substring(pos));
+            }
+            return buf.toString();
+        }
+    }
+
+    /**
+     * The list of characters that are not encoded by the <code>escape()</code>
+     * and <code>unescape()</code> METHODS. They contains the characters as
+     * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
+     * <p/>
+     * <pre>
+     * unreserved  = alphanum | mark
+     * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+     * </pre>
+     */
+    public static BitSet URISave;
+
+    /**
+     * Same as {@link #URISave} but also contains the '/'
+     */
+    public static BitSet URISaveEx;
+
+    static {
+        URISave = new BitSet(256);
+        int i;
+        for (i = 'a'; i <= 'z'; i++) {
+            URISave.set(i);
+        }
+        for (i = 'A'; i <= 'Z'; i++) {
+            URISave.set(i);
+        }
+        for (i = '0'; i <= '9'; i++) {
+            URISave.set(i);
+        }
+        URISave.set('-');
+        URISave.set('_');
+        URISave.set('.');
+        URISave.set('!');
+        URISave.set('~');
+        URISave.set('*');
+        URISave.set('\'');
+        URISave.set('(');
+        URISave.set(')');
+
+        URISaveEx = (BitSet) URISave.clone();
+        URISaveEx.set('/');
+    }
+
+    /**
+     * Does an URL encoding of the <code>string</code> using the
+     * <code>escape</code> character. The characters that don't need encoding
+     * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+     * RFC 2396, but without the escape character.
+     *
+     * @param string the string to encode.
+     * @param escape the escape character.
+     * @return the escaped string
+     * @throws NullPointerException if <code>string</code> is <code>null</code>.
+     */
+    public static String escape(String string, char escape) {
+        return escape(string, escape, false);
+    }
+
+    /**
+     * Does an URL encoding of the <code>string</code> using the
+     * <code>escape</code> character. The characters that don't need encoding
+     * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+     * RFC 2396, but without the escape character. If <code>isPath</code> is
+     * <code>true</code>, additionally the slash '/' is ignored, too.
+     *
+     * @param string the string to encode.
+     * @param escape the escape character.
+     * @param isPath if <code>true</code>, the string is treated as path
+     * @return the escaped string
+     * @throws NullPointerException if <code>string</code> is <code>null</code>.
+     */
+    public static String escape(String string, char escape, boolean isPath) {
+        try {
+            BitSet validChars = isPath ? URISaveEx : URISave;
+            byte[] bytes = string.getBytes("utf-8");
+            StringBuffer out = new StringBuffer(bytes.length);
+            for (byte aByte : bytes) {
+                int c = aByte & 0xff;
+                if (validChars.get(c) && c != escape) {
+                    out.append((char) c);
+                } else {
+                    out.append(escape);
+                    out.append(hexTable[(c >> 4) & 0x0f]);
+                    out.append(hexTable[(c) & 0x0f]);
+                }
+            }
+            return out.toString();
+        } catch (UnsupportedEncodingException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    /**
+     * Does a URL encoding of the <code>string</code>. The characters that
+     * don't need encoding are those defined 'unreserved' in section 2.3 of
+     * the 'URI generic syntax' RFC 2396.
+     *
+     * @param string the string to encode
+     * @return the escaped string
+     * @throws NullPointerException if <code>string</code> is <code>null</code>.
+     */
+    public static String escape(String string) {
+        return escape(string, '%');
+    }
+
+    /**
+     * Does a URL encoding of the <code>path</code>. The characters that
+     * don't need encoding are those defined 'unreserved' in section 2.3 of
+     * the 'URI generic syntax' RFC 2396. In contrast to the
+     * {@link #escape(String)} method, not the entire path string is escaped,
+     * but every individual part (i.e. the slashes are not escaped).
+     *
+     * @param path the path to encode
+     * @return the escaped path
+     * @throws NullPointerException if <code>path</code> is <code>null</code>.
+     */
+    public static String escapePath(String path) {
+        return escape(path, '%', true);
+    }
+
+    /**
+     * Does a URL decoding of the <code>string</code> using the
+     * <code>escape</code> character. Please note that in opposite to the
+     * {@link java.net.URLDecoder} it does not transform the + into spaces.
+     *
+     * @param string the string to decode
+     * @param escape the escape character
+     * @return the decoded string
+     * @throws NullPointerException           if <code>string</code> is <code>null</code>.
+     * @throws IllegalArgumentException       if the 2 characters following the escape
+     *                                        character do not represent a hex-number
+     *                                        or if not enough characters follow an
+     *                                        escape character
+     */
+    public static String unescape(String string, char escape)  {
+        try {
+            byte[] utf8 = string.getBytes("utf-8");
+
+            // Check whether escape occurs at invalid position
+            if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape) ||
+                (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
+                throw new IllegalArgumentException("Premature end of escape sequence at end of input");
+            }
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
+            for (int k = 0; k < utf8.length; k++) {
+                byte b = utf8[k];
+                if (b == escape) {
+                    out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
+                }
+                else {
+                    out.write(b);
+                }
+            }
+
+            return new String(out.toByteArray(), "utf-8");
+        }
+        catch (UnsupportedEncodingException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    /**
+     * Does a URL decoding of the <code>string</code>. Please note that in
+     * opposite to the {@link java.net.URLDecoder} it does not transform the +
+     * into spaces.
+     *
+     * @param string the string to decode
+     * @return the decoded string
+     * @throws NullPointerException           if <code>string</code> is <code>null</code>.
+     * @throws ArrayIndexOutOfBoundsException if not enough character follow an
+     *                                        escape character
+     * @throws IllegalArgumentException       if the 2 characters following the escape
+     *                                        character do not represent a hex-number.
+     */
+    public static String unescape(String string) {
+        return unescape(string, '%');
+    }
+
+    /**
+     * Escapes all illegal JCR name characters of a string.
+     * The encoding is loosely modeled after URI encoding, but only encodes
+     * the characters it absolutely needs to in order to make the resulting
+     * string a valid JCR name.
+     * Use {@link #unescapeIllegalJcrChars(String)} for decoding.
+     * <p/>
+     * QName EBNF:<br>
+     * <xmp>
+     * simplename ::= onecharsimplename | twocharsimplename | threeormorecharname
+     * onecharsimplename ::= (* Any Unicode character except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
+     * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' | onecharsimplename onecharsimplename
+     * threeormorecharname ::= nonspace string nonspace
+     * string ::= char | string char
+     * char ::= nonspace | ' '
+     * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*', '|' or any whitespace character *)
+     * </xmp>
+     *
+     * @param name the name to escape
+     * @return the escaped name
+     */
+    public static String escapeIllegalJcrChars(String name) {
+        StringBuffer buffer = new StringBuffer(name.length() * 2);
+        for (int i = 0; i < name.length(); i++) {
+            char ch = name.charAt(i);
+            if (ch == '%' || ch == '/' || ch == ':' || ch == '[' || ch == ']'
+                || ch == '*' || ch == '|'
+                || (ch == '.' && name.length() < 3)
+                || (ch == ' ' && (i == 0 || i == name.length() - 1))
+                || ch == '\t' || ch == '\r' || ch == '\n') {
+                buffer.append('%');
+                buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
+                buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
+            } else {
+                buffer.append(ch);
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Escapes illegal XPath search characters at the end of a string.
+     * <p>Example:<br>
+     * A search string like 'test?' will run into a ParseException
+     * documented in http://issues.apache.org/jira/browse/JCR-1248
+     *
+     * @param s the string to encode
+     * @return the escaped string
+     */
+    public static String escapeIllegalXpathSearchChars(String s) {
+        StringBuffer sb = new StringBuffer();
+        sb.append(s.substring(0, (s.length() - 1)));
+        char c = s.charAt(s.length() - 1);
+        // NOTE: keep this in sync with _ESCAPED_CHAR below!
+        if (c == '!' || c == '(' || c == ':' || c == '^'
+            || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
+            sb.append('\\');
+        }
+        sb.append(c);
+        return sb.toString();
+    }
+
+    /**
+     * Unescapes previously escaped jcr chars.
+     * <p/>
+     * Please note, that this does not exactly the same as the url related
+     * {@link #unescape(String)}, since it handles the byte-encoding
+     * differently.
+     *
+     * @param name the name to unescape
+     * @return the unescaped name
+     */
+    public static String unescapeIllegalJcrChars(String name) {
+        StringBuffer buffer = new StringBuffer(name.length());
+        int i = name.indexOf('%');
+        while (i > -1 && i + 2 < name.length()) {
+            buffer.append(name.toCharArray(), 0, i);
+            int a = Character.digit(name.charAt(i + 1), 16);
+            int b = Character.digit(name.charAt(i + 2), 16);
+            if (a > -1 && b > -1) {
+                buffer.append((char) (a * 16 + b));
+                name = name.substring(i + 3);
+            } else {
+                buffer.append('%');
+                name = name.substring(i + 1);
+            }
+            i = name.indexOf('%');
+        }
+        buffer.append(name);
+        return buffer.toString();
+    }
+
+    /**
+     * Returns the name part of the path. If the given path is already a name
+     * (i.e. contains no slashes) it is returned.
+     *
+     * @param path the path
+     * @return the name part or <code>null</code> if <code>path</code> is <code>null</code>.
+     */
+    public static String getName(String path) {
+        return getName(path, '/');
+    }
+
+    /**
+     * Returns the name part of the path, delimited by the given <code>delim</code>.
+     * If the given path is already a name (i.e. contains no <code>delim</code>
+     * characters) it is returned.
+     *
+     * @param path the path
+     * @param delim the delimiter
+     * @return the name part or <code>null</code> if <code>path</code> is <code>null</code>.
+     */
+    public static String getName(String path, char delim) {
+        return path == null
+                ? null
+                : path.substring(path.lastIndexOf(delim) + 1);
+    }
+
+    /**
+     * Same as {@link #getName(String)} but adding the possibility
+     * to pass paths that end with a trailing '/'
+     *
+     * @see #getName(String)
+     */
+    public static String getName(String path, boolean ignoreTrailingSlash) {
+        if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
+            path = path.substring(0, path.length()-1);
+        }
+        return getName(path);
+    }
+
+    /**
+     * Returns the namespace prefix of the given <code>qname</code>. If the
+     * prefix is missing, an empty string is returned. Please note, that this
+     * method does not validate the name or prefix.
+     * </p>
+     * the qname has the format: qname := [prefix ':'] local;
+     *
+     * @param qname a qualified name
+     * @return the prefix of the name or "".
+     *
+     * @see #getLocalName(String)
+     *
+     * @throws NullPointerException if <code>qname</code> is <code>null</code>
+     */
+    public static String getNamespacePrefix(String qname) {
+        int pos = qname.indexOf(':');
+        return pos >=0 ? qname.substring(0, pos) : "";
+    }
+
+    /**
+     * Returns the local name of the given <code>qname</code>. Please note, that
+     * this method does not validate the name.
+     * </p>
+     * the qname has the format: qname := [prefix ':'] local;
+     *
+     * @param qname a qualified name
+     * @return the localname
+     *
+     * @see #getNamespacePrefix(String)
+     *
+     * @throws NullPointerException if <code>qname</code> is <code>null</code>
+     */
+    public static String getLocalName(String qname) {
+        int pos = qname.indexOf(':');
+        return pos >=0 ? qname.substring(pos+1) : qname;
+    }
+
+    /**
+     * Determines, if two paths denote hierarchical siblins.
+     *
+     * @param p1 first path
+     * @param p2 second path
+     * @return true if on same level, false otherwise
+     */
+    public static boolean isSibling(String p1, String p2) {
+        int pos1 = p1.lastIndexOf('/');
+        int pos2 = p2.lastIndexOf('/');
+        return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
+    }
+
+    /**
+     * Determines if the <code>descendant</code> path is hierarchical a
+     * descendant of <code>path</code>.
+     *
+     * @param path     the current path
+     * @param descendant the potential descendant
+     * @return <code>true</code> if the <code>descendant</code> is a descendant;
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isDescendant(String path, String descendant) {
+        String pattern = path.endsWith("/") ? path : path + "/";
+        return !pattern.equals(descendant) &&
+                descendant.startsWith(pattern);
+    }
+
+    /**
+     * Determines if the <code>descendant</code> path is hierarchical a
+     * descendant of <code>path</code> or equal to it.
+     *
+     * @param path       the path to check
+     * @param descendant the potential descendant
+     * @return <code>true</code> if the <code>descendant</code> is a descendant
+     *         or equal; <code>false</code> otherwise.
+     */
+    public static boolean isDescendantOrEqual(String path, String descendant) {
+        if (path.equals(descendant)) {
+            return true;
+        } else {
+            String pattern = path.endsWith("/") ? path : path + "/";
+            return descendant.startsWith(pattern);
+        }
+    }
+
+    /**
+     * Returns the n<sup>th</sup> relative parent of the path, where n=level.
+     * <p>Example:<br>
+     * <code>
+     * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
+     * </code>
+     *
+     * @param path the path of the page
+     * @param level  the level of the parent
+     */
+    public static String getRelativeParent(String path, int level) {
+        int idx = path.length();
+        while (level > 0) {
+            idx = path.lastIndexOf('/', idx - 1);
+            if (idx < 0) {
+                return "";
+            }
+            level--;
+        }
+        return (idx == 0) ? "/" : path.substring(0, idx);
+    }
+
+    /**
+     * Same as {@link #getRelativeParent(String, int)} but adding the possibility
+     * to pass paths that end with a trailing '/'
+     *
+     * @see #getRelativeParent(String, int)
+     */
+    public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
+        if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
+            path = path.substring(0, path.length()-1);
+        }
+        return getRelativeParent(path, level);
+    }
+
+    /**
+     * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
+     * <p>Example:<br>
+     * <code>
+     * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
+     * </code>
+     *
+     * @param path the path of the page
+     * @param level  the level of the parent
+     */
+    public static String getAbsoluteParent(String path, int level) {
+        int idx = 0;
+        int len = path.length();
+        while (level >= 0 && idx < len) {
+            idx = path.indexOf('/', idx + 1);
+            if (idx < 0) {
+                idx = len;
+            }
+            level--;
+        }
+        return level >= 0 ? "" : path.substring(0, idx);
+    }
+
+    /**
+     * Performs variable replacement on the given string value.
+     * Each <code>${...}</code> sequence within the given value is replaced
+     * with the value of the named parser variable. If a variable is not found
+     * in the properties an IllegalArgumentException is thrown unless
+     * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
+     * missing variable is replaced by the empty string.
+     *
+     * @param value         the original value
+     * @param ignoreMissing if <code>true</code>, missing variables are replaced
+     *                      by the empty string.
+     * @return value after variable replacements
+     * @throws IllegalArgumentException if the replacement of a referenced
+     *                                  variable is not found
+     */
+    public static String replaceVariables(Properties variables, String value,
+                                          boolean ignoreMissing)
+            throws IllegalArgumentException {
+        StringBuffer result = new StringBuffer();
+
+        // Value:
+        // +--+-+--------+-+-----------------+
+        // |  |p|-->     |q|-->              |
+        // +--+-+--------+-+-----------------+
+        int p = 0, q = value.indexOf("${");                // Find first ${
+        while (q != -1) {
+            result.append(value.substring(p, q));          // Text before ${
+            p = q;
+            q = value.indexOf("}", q + 2);                 // Find }
+            if (q != -1) {
+                String variable = value.substring(p + 2, q);
+                String replacement = variables.getProperty(variable);
+                if (replacement == null) {
+                    if (ignoreMissing) {
+                        replacement = "";
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Replacement not found for ${" + variable + "}.");
+                    }
+                }
+                result.append(replacement);
+                p = q + 1;
+                q = value.indexOf("${", p);                // Find next ${
+            }
+        }
+        result.append(value.substring(p, value.length())); // Trailing text
+
+        return result.toString();
+    }
+
+    private static byte decodeDigit(byte b) {
+        if (b >= 0x30 && b <= 0x39) {
+            return (byte) (b - 0x30);
+        }
+        else if (b >= 0x41 && b <= 0x46) {
+            return (byte) (b - 0x37);
+        }
+        else if (b >= 0x61 && b <= 0x66) {
+            return (byte) (b - 0x57);
+        }
+        else {
+            throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char)b);
+        }
+    }
+
+}

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Tree.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Tree.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Tree.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/Tree.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * <code>Tree</code>...
+ */
+public class Tree<E> {
+
+    private final char separator;
+
+    private Node<E> root = new Node<E>(null, "");
+
+    public Tree() {
+        this('/');
+    }
+
+    public Tree(char separator) {
+        this.separator = separator;
+    }
+
+    public void clear() {
+        root.elem = null;
+        root.children.clear();
+    }
+
+    public E put(String path, E elem) {
+        E previous;
+        Node<E> n = get(path, true);
+        previous = n.elem;
+        n.elem = elem;
+        return previous;
+    }
+
+    public E get(String path) {
+        Node<E> n = get(path, false);
+        return n == null ? null : n.elem;
+    }
+
+    public Node<E> getNode(String path) {
+        return get(path, false);
+    }
+
+    public E remove(String path) {
+        Node<E> n = get(path, false);
+        if (n == null) {
+            return null;
+        }
+        E previous = n.elem;
+        n.elem = null;
+        n.prune();
+        return previous;
+    }
+
+    private Node<E> get(String path, boolean create) {
+        String[] segs = Text.explode(path, separator);
+        Node<E> n = root;
+        for (String name: segs) {
+            Node<E> c = n.get(name, create);
+            if (c == null) {
+                return null;
+            }
+            n = c;
+        }
+        return n;
+    }
+
+    public void removeChildren(String path) {
+        Node<E> n = get(path, false);
+        if (n != null) {
+            n.removeChildren();
+        }
+    }
+
+    public Map<String, E> map() {
+        Map<String, E> map = new LinkedHashMap<String, E>();
+        fill(map, root, "");
+        return map;
+    }
+
+    public String getRootPath() {
+        Node<E> n = root;
+        StringBuffer path = new StringBuffer();
+        while (n.elem == null && n.children.size() == 1) {
+            n = n.children.values().iterator().next();
+            path.append(separator).append(n.name);
+        }
+        if (path.length() == 0) {
+            path.append(separator);
+        }
+        return path.toString();
+    }
+
+    public Node<E> getRootNode() {
+        Node<E> n = root;
+        while (n.elem == null && n.children.size() == 1) {
+            n = n.children.values().iterator().next();
+        }
+        return n;
+    }
+
+    private void fill(Map<String, E> map, Node<E> node, String parentPath) {
+        String path;
+        if (parentPath.length() != 1) {
+            // stupid check for root path
+            path = parentPath + separator + node.name;
+        } else {
+            path = parentPath + node.name;
+        }
+        if (node.elem != null) {
+            map.put(path, node.elem);
+        }
+        for (Node<E> child: node.children.values()) {
+            fill(map, child, path);
+        }
+    }
+
+    public static class Node<E> {
+
+        private String name;
+
+        private E elem;
+
+        private final Node parent;
+
+        private final Map<String, Node<E>> children = new LinkedHashMap<String, Node<E>>();
+
+        private Node(Node parent, String name) {
+            this.parent = parent;
+            this.name = name;
+        }
+
+        private Node<E> get(String name, boolean create) {
+            Node<E> child = children.get(name);
+            if (child == null && create) {
+                child = new Node<E>(this, name);
+                children.put(name, child);
+            }
+            return child;
+        }
+
+        private Node<E> remove(String name) {
+            return children.remove(name);
+        }
+
+        private void prune() {
+            if (children.isEmpty() && elem == null && parent != null) {
+                parent.remove(this.name);
+                parent.prune();
+            }
+        }
+
+        private void removeChildren() {
+            children.clear();
+            prune();
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public E getElem() {
+            return elem;
+        }
+
+        public Node getParent() {
+            return parent;
+        }
+
+        public Map<String, Node<E>> getChildren() {
+            return children;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("Node");
+            sb.append("{name='").append(name).append('\'');
+            sb.append(", elem=").append(elem);
+            sb.append(", children=").append(children.keySet());
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ValueComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ValueComparator.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ValueComparator.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ValueComparator.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.vault.util;
+
+import java.util.Comparator;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/**
+ * Comparator for values
+ */
+public class ValueComparator implements Comparator<Value> {
+
+    private static final ValueComparator INSTANCE = new ValueComparator();
+
+    private ValueComparator() {
+    }
+
+    public static ValueComparator getInstance() {
+        return INSTANCE;
+    }
+
+    public int compare(Value o1, Value o2) {
+        try {
+            // assume types are equal
+            switch (o1.getType()) {
+                case PropertyType.BINARY:
+                    throw new IllegalArgumentException("sorting of binary values not supported.");
+                case PropertyType.DATE:
+                    return o1.getDate().compareTo(o2.getDate());
+                case PropertyType.DECIMAL:
+                    return o1.getDecimal().compareTo(o2.getDecimal());
+                case PropertyType.DOUBLE:
+                    return ((Double) o1.getDouble()).compareTo(o2.getDouble());
+                case PropertyType.LONG:
+                    return ((Long) o1.getLong()).compareTo(o2.getLong());
+                default:
+                    return o1.getString().compareTo(o2.getString());
+            }
+        } catch (RepositoryException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
\ No newline at end of file



Mime
View raw message