You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2011/10/26 11:20:33 UTC

svn commit: r1189089 - in /jackrabbit/sandbox/microkernel/src: main/java/org/apache/jackrabbit/mk/ main/java/org/apache/jackrabbit/mk/wrapper/ test/java/org/apache/jackrabbit/mk/wrapper/

Author: thomasm
Date: Wed Oct 26 09:20:33 2011
New Revision: 1189089

URL: http://svn.apache.org/viewvc?rev=1189089&view=rev
Log:
Virtual repository implementation.

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/VirtualRepositoryWrapper.java
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/wrapper/TestVirtualRepositoryWrapper.java
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java?rev=1189089&r1=1189088&r2=1189089&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java Wed Oct 26 09:20:33 2011
@@ -27,6 +27,7 @@ import org.apache.jackrabbit.mk.mem.Memo
 import org.apache.jackrabbit.mk.util.ExceptionFactory;
 import org.apache.jackrabbit.mk.wrapper.LogWrapper;
 import org.apache.jackrabbit.mk.wrapper.SecurityWrapper;
+import org.apache.jackrabbit.mk.wrapper.VirtualRepositoryWrapper;
 
 /**
  * A factory to create a MicroKernel instance.
@@ -52,6 +53,8 @@ public class MicroKernelFactory {
             return LogWrapper.get(url);
         } else if (url.startsWith("sec:")) {
             return SecurityWrapper.get(url);
+        } else if (url.startsWith("virtual:")) {
+            return VirtualRepositoryWrapper.get(url);
         } else if (url.startsWith("fs:")) {
             boolean clean = false;
             if (url.endsWith(";clean")) {

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/VirtualRepositoryWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/VirtualRepositoryWrapper.java?rev=1189089&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/VirtualRepositoryWrapper.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/VirtualRepositoryWrapper.java Wed Oct 26 09:20:33 2011
@@ -0,0 +1,328 @@
+/*
+ * 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.mk.wrapper;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+import org.apache.jackrabbit.mk.MicroKernelFactory;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.api.MicroKernelException;
+import org.apache.jackrabbit.mk.json.JsopBuilder;
+import org.apache.jackrabbit.mk.json.JsopTokenizer;
+import org.apache.jackrabbit.mk.mem.NodeImpl;
+import org.apache.jackrabbit.mk.mem.NodeMap;
+import org.apache.jackrabbit.mk.util.ExceptionFactory;
+import org.apache.jackrabbit.mk.util.PathUtils;
+
+/**
+ * A microkernel prototype implementation that distributes nodes based on the path,
+ * using a fixed configuration.
+ * All mounted repositories contains the configuration as follows:
+ * /:mount/rep1 { url: "mk:...", paths: "/a,/b" }.
+ */
+public class VirtualRepositoryWrapper implements MicroKernel {
+
+    private static final String PREFIX = "virtual:";
+    private static final String MOUNT = "/:mount";
+
+    /**
+     * The 'main' (wrapped) implementation.
+     */
+    private final MicroKernel mk;
+
+    /**
+     * Path map.
+     * Key: path, value: mount name
+     */
+    private final TreeMap<String, String> dir = new TreeMap<String, String>();
+
+    /**
+     * Pending commit map.
+     * Key: mount name, value: the builder that contains the pending commit.
+     */
+    private final HashMap<String, JsopBuilder> builders = new HashMap<String, JsopBuilder>();
+
+    /**
+     * Mount map.
+     * Key: mount name, value: microkernel implementation.
+     */
+    private final HashMap<String, MicroKernel> mounts = new HashMap<String, MicroKernel>();
+
+    /**
+     * Head revision map.
+     * Key: mount name, value: the head revision for this mount.
+     */
+    private final TreeMap<String, String> revisions = new TreeMap<String, String>();
+
+    private final NodeMap map = new NodeMap();
+
+    private VirtualRepositoryWrapper(MicroKernel mk) {
+        this.mk = mk;
+    }
+
+    public static synchronized VirtualRepositoryWrapper get(String url) {
+        String urlMeta = url.substring(PREFIX.length());
+        MicroKernel mk = MicroKernelFactory.getInstance(urlMeta);
+        try {
+            String head = mk.getHeadRevision();
+            VirtualRepositoryWrapper vm = new VirtualRepositoryWrapper(mk);
+            if (mk.nodeExists(MOUNT, head)) {
+                String mounts = mk.getNodes(MOUNT, head);
+                NodeMap map = new NodeMap();
+                JsopTokenizer t = new JsopTokenizer(mounts);
+                t.read('{');
+                NodeImpl n = NodeImpl.parse(map, t, 0);
+                for (long pos = 0;; pos++) {
+                    String childName = n.getChildNodeName(pos);
+                    if (childName == null) {
+                        break;
+                    }
+                    NodeImpl mount = n.getNode(childName);
+                    String mountUrl = JsopTokenizer.decodeQuoted(mount.getProperty("url"));
+                    String[] paths = JsopTokenizer.decodeQuoted(mount.getProperty("paths")).split(",");
+                    vm.addMount(childName, mountUrl, paths);
+                    vm.getHeadRevision();
+                }
+            }
+            return vm;
+        } catch (MicroKernelException e) {
+            mk.dispose();
+            throw e;
+        }
+    }
+
+    private void addMount(String mount, String url, String[] paths) {
+        MicroKernel mk = MicroKernelFactory.getInstance(url);
+        mounts.put(mount, mk);
+        for (String p : paths) {
+            dir.put(p, mount);
+        }
+    }
+
+    public String commit(String rootPath, String jsonDiff, String revisionId, String message) {
+        JsopTokenizer t = new JsopTokenizer(jsonDiff);
+        while (true) {
+            int r = t.read();
+            if (r == JsopTokenizer.END) {
+                break;
+            }
+            String path = PathUtils.relativize("/", PathUtils.concat(rootPath, t.readString()));
+            switch (r) {
+            case '+':
+                t.read(':');
+                if (t.matches('{')) {
+                    NodeImpl n = NodeImpl.parse(map, t, 0);
+                    JsopBuilder diff = new JsopBuilder();
+                    diff.appendTag("+ ").key(path);
+                    n.append(diff, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, false);
+                    buffer(path, diff);
+                } else {
+                    String value = t.readRawValue().trim();
+                    JsopBuilder diff = new JsopBuilder();
+                    diff.appendTag("+ ").key(path);
+                    diff.encodedValue(value);
+                    buffer(path, diff);
+                }
+                break;
+            case '-': {
+                JsopBuilder diff = new JsopBuilder();
+                diff.appendTag("- ").value(path);
+                buffer(path, diff);
+                break;
+            }
+            case '^':
+                t.read(':');
+                String value;
+                if (t.matches(JsopTokenizer.NULL)) {
+                    JsopBuilder diff = new JsopBuilder();
+                    diff.appendTag("^ ").key(path).value(null);
+                    buffer(path, diff);
+                } else {
+                    value = t.readRawValue().trim();
+                    JsopBuilder diff = new JsopBuilder();
+                    diff.appendTag("^ ").key(path).encodedValue(value);
+                    buffer(path, diff);
+                }
+                break;
+            case '>': {
+                t.read(':');
+                JsopBuilder diff = new JsopBuilder();
+                if (t.matches('{')) {
+                    String position = t.readString();
+                    t.read(':');
+                    String to = t.readString();
+                    t.read('}');
+                    if (!PathUtils.isAbsolute(to)) {
+                        to = PathUtils.concat(rootPath, to);
+                    }
+                    diff.appendTag("> ").key(path);
+                    diff.object().key(position);
+                    diff.value(to).endObject();
+                } else {
+                    String to = t.readString();
+                    if (!PathUtils.isAbsolute(to)) {
+                        to = PathUtils.concat(rootPath, to);
+                    }
+                    diff.appendTag("> ").key(path);
+                    diff.value(to);
+                }
+                buffer(path, diff);
+                break;
+            }
+            default:
+                throw ExceptionFactory.get("token: " + (char) t.getTokenType());
+            }
+        }
+        String revision = null;
+        for (Entry<String, JsopBuilder> e : builders.entrySet()) {
+            String mount = e.getKey();
+            MicroKernel m = mounts.get(mount);
+            String jsop = e.getValue().toString();
+            revision = m.commit("/", jsop, revisionId, message);
+            revisions.put(mount, revision);
+        }
+        builders.clear();
+        return getHeadRevision();
+    }
+
+    private String getMount(String path) {
+        while (true) {
+            String mount = dir.get(path);
+            if (mount != null) {
+                return mount;
+            }
+            // check parent
+            if (PathUtils.denotesRoot(path)) {
+                break;
+            }
+            path = PathUtils.getParentPath(path);
+        }
+        return null;
+    }
+
+    private void buffer(String path, JsopBuilder diff) {
+        String mount = getMount("/" + path);
+        if (mount == null) {
+            for (String m : mounts.keySet()) {
+                getBuilder(m).appendTag(diff.toString()).newline();
+            }
+        } else {
+            getBuilder(mount).appendTag(diff.toString()).newline();
+        }
+    }
+
+    private JsopBuilder getBuilder(String mount) {
+        JsopBuilder builder = builders.get(mount);
+        if (builder == null) {
+            builder = new JsopBuilder();
+            builders.put(mount, builder);
+        }
+        return builder;
+    }
+
+    public void dispose() {
+        for (MicroKernel m : mounts.values()) {
+            m.dispose();
+        }
+    }
+
+    public String getHeadRevision() {
+        StringBuilder buff = new StringBuilder();
+        if (revisions.size() != mounts.size()) {
+            revisions.clear();
+            for (Entry<String, MicroKernel> e : mounts.entrySet()) {
+                String m = e.getKey();
+                String r = e.getValue().getHeadRevision();
+                revisions.put(m, r);
+            }
+        }
+        for (Entry<String, String> e : revisions.entrySet()) {
+            if (buff.length() > 0) {
+                buff.append(',');
+            }
+            buff.append(e.getKey());
+            buff.append(':');
+            buff.append(e.getValue());
+        }
+        return buff.toString();
+    }
+
+    public String getJournal(String fromRevisionId, String toRevisionId) {
+        return mk.getJournal(fromRevisionId, toRevisionId);
+    }
+
+    public String diff(String fromRevisionId, String toRevisionId, String path) throws MicroKernelException {
+        return mk.diff(fromRevisionId, toRevisionId, path);
+    }
+
+    public long getLength(String blobId) {
+        return mk.getLength(blobId);
+    }
+
+    public String getNodes(String path, String revisionId) {
+        return getNodes(path, revisionId, 1, 0, -1);
+    }
+
+    public String getNodes(String path, String revisionId, int depth, long offset, int count) {
+        String mount = getMount(path);
+        if (mount == null) {
+            throw ExceptionFactory.get("Not mapped: " + path);
+        }
+        String rev = getRevision(mount, revisionId);
+        MicroKernel mk = mounts.get(mount);
+        return mk.getNodes(path, rev, depth, offset, count);
+    }
+
+    private String getRevision(String mount, String revisionId) {
+        for (String rev : revisionId.split(",")) {
+            if (rev.startsWith(mount + ":")) {
+                return rev.substring(mount.length() + 1);
+            }
+        }
+        throw ExceptionFactory.get("Unknown revision: " + revisionId + " mount: " + mount);
+    }
+
+    public String getRevisions(long since, int maxEntries) {
+        return mk.getRevisions(since, maxEntries);
+    }
+
+    public boolean nodeExists(String path, String revisionId) {
+        String mount = getMount(path);
+        if (mount == null) {
+            throw ExceptionFactory.get("Not mapped: " + path);
+        }
+        String rev = getRevision(mount, revisionId);
+        MicroKernel mk = mounts.get(mount);
+        return mk.nodeExists(path, rev);
+    }
+
+    public int read(String blobId, long pos, byte[] buff, int off, int length) {
+        return mk.read(blobId, pos, buff, off, length);
+    }
+
+    public String waitForCommit(String oldHeadRevision, long maxWaitMillis) throws InterruptedException {
+        return mk.waitForCommit(oldHeadRevision, maxWaitMillis);
+    }
+
+    public String write(InputStream in) {
+        return mk.write(in);
+    }
+
+}
+

Added: jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/wrapper/TestVirtualRepositoryWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/wrapper/TestVirtualRepositoryWrapper.java?rev=1189089&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/wrapper/TestVirtualRepositoryWrapper.java (added)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/wrapper/TestVirtualRepositoryWrapper.java Wed Oct 26 09:20:33 2011
@@ -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.mk.wrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import java.io.ByteArrayInputStream;
+import java.util.Random;
+import org.apache.jackrabbit.mk.MicroKernelFactory;
+import org.apache.jackrabbit.mk.MultiMkTestBase;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.util.IOUtilsTest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test the virtual repository wrapper.
+ */
+@RunWith(Parameterized.class)
+public class TestVirtualRepositoryWrapper extends MultiMkTestBase {
+
+    private String head;
+    private MicroKernel mkRep1;
+    private MicroKernel mkRep2;
+    private MicroKernel mkVirtual;
+
+    public TestVirtualRepositoryWrapper(String url) {
+        super(url);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        String url1 = url + "1", url2 = url + "2";
+        mkRep1 = MicroKernelFactory.getInstance(url1 + ";clean");
+        mkRep2 = MicroKernelFactory.getInstance(url2 + ";clean");
+        String init = "+ \":mount\": { " +
+            "\"r1\": { \"url\": \"" + url1 + "\", \"paths\": \"/data/a\" }" +
+            "," +
+            "\"r2\": { \"url\": \"" + url2 + "\", \"paths\": \"/data/b\" }" +
+            "}";
+        mkRep1.commit("/", init, mkRep1.getHeadRevision(), "");
+        mkRep2.commit("/", init, mkRep2.getHeadRevision(), "");
+        mkVirtual = MicroKernelFactory.getInstance("virtual:" + url1);
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        try {
+            mkRep1.commit("/", "- \":mount\"", mkRep1.getHeadRevision(), "");
+            mkRep2.commit("/", "- \":mount\"", mkRep2.getHeadRevision(), "");
+            if (mkVirtual != null) {
+                mkVirtual.dispose();
+            }
+            if (mkRep1 != null) {
+                mkRep1.dispose();
+            }
+            if (mkRep2 != null) {
+                mkRep2.dispose();
+            }
+            super.tearDown();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void commit() {
+        if (url.startsWith("fs:")) {
+            return;
+        }
+
+        // add node
+        head = mkVirtual.commit("/", "+ \"data\": {} ", head, "");
+        head = mkVirtual.commit("/", "+ \"data/a\": { \"data\": \"Hello\" }", head, "");
+        head = mkVirtual.commit("/", "+ \"data/b\": { \"data\": \"World\" }", head, "");
+        String m1 = mkRep1.getNodes("/data", mkRep1.getHeadRevision());
+        assertEquals("{\":childNodeCount\":1,\"a\":{\"data\":\"Hello\",\":childNodeCount\":0}}", m1);
+        String m2 = mkRep2.getNodes("/data", mkRep2.getHeadRevision());
+        assertEquals("{\":childNodeCount\":1,\"b\":{\"data\":\"World\",\":childNodeCount\":0}}", m2);
+        String m = mkVirtual.getNodes("/data/a", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Hello\",\":childNodeCount\":0}", m);
+        m = mkVirtual.getNodes("/data/b", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"World\",\":childNodeCount\":0}", m);
+
+        // set property
+        head = mkVirtual.commit("/", "^ \"data/a/data\": \"Hallo\"", head, "");
+        head = mkVirtual.commit("/", "^ \"data/b/data\": \"Welt\"", head, "");
+        m = mkVirtual.getNodes("/data/a", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Hallo\",\":childNodeCount\":0}", m);
+        m = mkVirtual.getNodes("/data/b", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Welt\",\":childNodeCount\":0}", m);
+
+        // add property
+        head = mkVirtual.commit("/", "+ \"data/a/lang\": \"de\"", head, "");
+        m = mkVirtual.getNodes("/data/a", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Hallo\",\"lang\":\"de\",\":childNodeCount\":0}", m);
+        head = mkVirtual.commit("/", "^ \"data/a/lang\": null", head, "");
+        m = mkVirtual.getNodes("/data/a", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Hallo\",\":childNodeCount\":0}", m);
+
+        // move
+        head = mkVirtual.commit("/", "+ \"data/a/sub\": {}", head, "");
+        head = mkVirtual.commit("/", "> \"data/a/sub\": \"data/a/sub2\"", head, "");
+        m = mkVirtual.getNodes("/data/a", mkVirtual.getHeadRevision());
+        assertEquals("{\"data\":\"Hallo\",\":childNodeCount\":1,\"sub2\":{\":childNodeCount\":0}}", m);
+        head = mkVirtual.commit("/", "- \"data/a/sub2\"", head, "");
+
+        // remove node
+        head = mkVirtual.commit("/", "- \"data/b\"", head, "");
+        assertTrue(mkVirtual.nodeExists("/data/a", head));
+        assertFalse(mkVirtual.nodeExists("/data/b", head));
+
+    }
+
+    @Test
+    public void binary() {
+        int len = 1000;
+        byte[] data = new byte[len];
+        new Random().nextBytes(data);
+        String id = mkVirtual.write(new ByteArrayInputStream(data));
+        byte[] test = new byte[len];
+        mkVirtual.read(id, 0, test, 0, len);
+        IOUtilsTest.assertEquals(data, test);
+        assertEquals(len, mkVirtual.getLength(id));
+    }
+
+}