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));
+ }
+
+}