Author: jukka
Date: Sat Oct 19 00:01:36 2013
New Revision: 1533666
URL: http://svn.apache.org/r1533666
Log:
OAK-987: Implement the MicroKernel API
Track revision history in NodeStoreKernel
Add filter support to getNodes()
Better handling of property values in JSON/P
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonSerializer.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/NodeStoreKernel.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/JsopDiffTest.java
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java?rev=1533666&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java
Sat Oct 19 00:01:36 2013
@@ -0,0 +1,130 @@
+/*
+ * 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.oak.kernel;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.jackrabbit.mk.json.JsopTokenizer;
+
+/**
+ * Utility class for deciding which nodes and properties to serialize.
+ */
+class JsonFilter {
+
+ private static final Pattern EVERYTHING = Pattern.compile(".*");
+
+ private final List<Pattern> nodeIncludes = newArrayList(EVERYTHING);
+
+ private final List<Pattern> nodeExcludes = newArrayList();
+
+ private final List<Pattern> propertyIncludes = newArrayList(EVERYTHING);
+
+ private final List<Pattern> propertyExcludes = newArrayList();
+
+ JsonFilter(String filter) {
+ JsopTokenizer tokenizer = new JsopTokenizer(filter);
+ tokenizer.read('{');
+ for (boolean first = true; !tokenizer.matches('}'); first = false) {
+ if (!first) {
+ tokenizer.read(',');
+ }
+ String key = tokenizer.readString();
+ tokenizer.read(':');
+
+ List<Pattern> includes = newArrayList();
+ List<Pattern> excludes = newArrayList();
+ readPatterns(tokenizer, includes, excludes);
+
+ if (key.equals("nodes")) {
+ nodeIncludes.clear();
+ nodeIncludes.addAll(includes);
+ nodeExcludes.clear();
+ nodeExcludes.addAll(excludes);
+ } else if (key.equals("properties")) {
+ propertyIncludes.clear();
+ propertyIncludes.addAll(includes);
+ propertyExcludes.clear();
+ propertyExcludes.addAll(excludes);
+ } else {
+ throw new IllegalStateException(key);
+ }
+ }
+ }
+
+ private void readPatterns(JsopTokenizer tokenizer, List<Pattern> includes,
+ List<Pattern> excludes) {
+ tokenizer.read('[');
+ for (boolean first = true; !tokenizer.matches(']'); first = false) {
+ if (!first) {
+ tokenizer.read(',');
+ }
+ String pattern = tokenizer.readString();
+ if (pattern.startsWith("-")) {
+ excludes.add(glob(pattern.substring(1)));
+ } else if (pattern.startsWith("\\-")) {
+ includes.add(glob(pattern.substring(1)));
+ } else {
+ includes.add(glob(pattern));
+ }
+ }
+ }
+
+ private static Pattern glob(String pattern) {
+ StringBuilder builder = new StringBuilder();
+ int star = pattern.indexOf('*');
+ while (star != -1) {
+ if (star > 0 && pattern.charAt(star - 1) == '\\') {
+ builder.append(Pattern.quote(pattern.substring(0, star - 1)));
+ builder.append(Pattern.quote("*"));
+ } else {
+ builder.append(Pattern.quote(pattern.substring(0, star)));
+ builder.append(".*");
+ }
+ pattern = pattern.substring(star + 1);
+ star = pattern.indexOf('*');
+ }
+ builder.append(Pattern.quote(pattern));
+ return Pattern.compile(builder.toString());
+ }
+
+ boolean includeNode(String name) {
+ return include(name, nodeIncludes, nodeExcludes);
+ }
+
+ boolean includeProperty(String name) {
+ return include(name, propertyIncludes, propertyExcludes);
+ }
+
+ private boolean include(
+ String name, List<Pattern> includes, List<Pattern> excludes) {
+ for (Pattern include : includes) {
+ if (include.matcher(name).matches()) {
+ for (Pattern exclude : excludes) {
+ if (exclude.matcher(name).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
Propchange:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonFilter.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonSerializer.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonSerializer.java?rev=1533666&r1=1533665&r2=1533666&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonSerializer.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsonSerializer.java
Sat Oct 19 00:01:36 2013
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.kernel
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.api.Type.BINARY;
import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
+import static org.apache.jackrabbit.oak.api.Type.DOUBLE;
import static org.apache.jackrabbit.oak.api.Type.LONG;
import static org.apache.jackrabbit.oak.api.Type.STRING;
@@ -28,7 +29,6 @@ import org.apache.jackrabbit.mk.json.Jso
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.memory.AbstractBlob;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -45,45 +45,51 @@ class JsonSerializer {
private final int maxChildNodes;
- private final boolean includeChildNodeCount;
+ private final JsonFilter filter;
private final BlobSerializer blobs;
private JsonSerializer(
JsopBuilder json, int depth, long offset, int maxChildNodes,
- boolean includeChildNodeCount, BlobSerializer blobs) {
+ JsonFilter filter, BlobSerializer blobs) {
this.json = checkNotNull(json);
this.depth = depth;
this.offset = offset;
this.maxChildNodes = maxChildNodes;
- this.includeChildNodeCount = includeChildNodeCount;
+ this.filter = checkNotNull(filter);
this.blobs = checkNotNull(blobs);
}
JsonSerializer(
- int depth, long offset, int maxChildNodes, BlobSerializer blobs) {
- this(new JsopBuilder(), depth, offset, maxChildNodes, true, blobs);
+ int depth, long offset, int maxChildNodes,
+ String filter, BlobSerializer blobs) {
+ this(new JsopBuilder(), depth, offset, maxChildNodes,
+ new JsonFilter(filter), blobs);
}
JsonSerializer(JsopBuilder json, BlobSerializer blobs) {
- this(json, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, false, blobs);
+ this(json, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
+ new JsonFilter("{\"properties\":[\"*\",
\"-:childNodeCount\"]}"),
+ blobs);
}
protected JsonSerializer getChildSerializer() {
return new JsonSerializer(
- json, depth - 1, 0, maxChildNodes,
- includeChildNodeCount, blobs);
+ json, depth - 1, 0, maxChildNodes, filter, blobs);
}
void serialize(NodeState node) {
json.object();
for (PropertyState property : node.getProperties()) {
- json.key(property.getName());
- serialize(property);
+ String name = property.getName();
+ if (filter.includeProperty(name)) {
+ json.key(name);
+ serialize(property);
+ }
}
- if (includeChildNodeCount) {
+ if (filter.includeProperty(":childNodeCount")) {
json.key(":childNodeCount");
json.value(node.getChildNodeCount(Integer.MAX_VALUE));
}
@@ -91,12 +97,13 @@ class JsonSerializer {
int index = 0;
int count = 0;
for (ChildNodeEntry child : node.getChildNodeEntries()) {
- if (index++ >= offset) {
+ String name = child.getName();
+ if (filter.includeNode(name) && index++ >= offset) {
if (count++ >= maxChildNodes) {
break;
}
- json.key(child.getName());
+ json.key(name);
if (depth > 0) {
getChildSerializer().serialize(child.getNodeState());
} else {
@@ -135,6 +142,13 @@ class JsonSerializer {
json.value(property.getValue(BOOLEAN, index).booleanValue());
} else if (type == LONG) {
json.value(property.getValue(LONG, index).longValue());
+ } else if (type == DOUBLE) {
+ Double value = property.getValue(DOUBLE, index);
+ if (value.isNaN() || value.isInfinite()) {
+ json.value(TypeCodes.encode(type.tag(), value.toString()));
+ } else {
+ json.encodedValue(value.toString());
+ }
} else if (type == BINARY) {
Blob blob = property.getValue(BINARY, index);
json.value(TypeCodes.encode(type.tag(), blobs.serialize(blob)));
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java?rev=1533666&r1=1533665&r2=1533666&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
Sat Oct 19 00:01:36 2013
@@ -44,8 +44,12 @@ public class JsopDiff implements NodeSta
}
+ JsopDiff(BlobSerializer blobs, String path) {
+ this(new JsopBuilder(), blobs, path, Integer.MAX_VALUE);
+ }
+
JsopDiff(BlobSerializer blobs) {
- this(new JsopBuilder(), blobs, "/", Integer.MAX_VALUE);
+ this(blobs, "/");
}
JsopDiff(String path, int depth) {
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java?rev=1533666&r1=1533665&r2=1533666&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
Sat Oct 19 00:01:36 2013
@@ -42,6 +42,7 @@ import com.google.common.cache.CacheLoad
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.mk.json.JsopReader;
@@ -51,7 +52,9 @@ import org.apache.jackrabbit.oak.api.Typ
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState;
import org.apache.jackrabbit.oak.plugins.memory.BooleanPropertyState;
+import org.apache.jackrabbit.oak.plugins.memory.DoublePropertyState;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.plugins.memory.LongPropertyState;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState;
@@ -751,7 +754,13 @@ public final class KernelNodeState exten
private PropertyState readProperty(String name, JsopReader reader) {
if (reader.matches(JsopReader.NUMBER)) {
String number = reader.getToken();
- return createProperty(name, number, PropertyType.LONG);
+ try {
+ return new LongPropertyState(
+ name, Long.parseLong(number), Type.LONG);
+ } catch (NumberFormatException e) {
+ return new DoublePropertyState(
+ name, Double.parseDouble(number));
+ }
} else if (reader.matches(JsopReader.TRUE)) {
return BooleanPropertyState.booleanProperty(name, true);
} else if (reader.matches(JsopReader.FALSE)) {
@@ -795,8 +804,13 @@ public final class KernelNodeState exten
while (!reader.matches(']')) {
if (reader.matches(JsopReader.NUMBER)) {
String number = reader.getToken();
- type = PropertyType.LONG;
- values.add(Conversions.convert(number).toLong());
+ try {
+ type = PropertyType.LONG;
+ values.add(Long.parseLong(number));
+ } catch (NumberFormatException e) {
+ type = PropertyType.DOUBLE;
+ values.add(Double.parseDouble(number));
+ }
} else if (reader.matches(JsopReader.TRUE)) {
type = PropertyType.BOOLEAN;
values.add(true);
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/NodeStoreKernel.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/NodeStoreKernel.java?rev=1533666&r1=1533665&r2=1533666&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/NodeStoreKernel.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/NodeStoreKernel.java
Sat Oct 19 00:01:36 2013
@@ -58,7 +58,7 @@ public class NodeStoreKernel implements
private final NodeStore store;
- private final Map<String, NodeState> revisions = newLinkedHashMap();
+ private final Map<String, Revision> revisions = newLinkedHashMap();
private final Map<String, NodeBuilder> branches = newLinkedHashMap();
@@ -78,7 +78,7 @@ public class NodeStoreKernel implements
public NodeStoreKernel(NodeStore store) {
this.store = store;
this.head = UUID.randomUUID().toString();
- revisions.put(head, store.getRoot());
+ revisions.put(head, new Revision(store.getRoot()));
}
private synchronized NodeState getRoot(String revision)
@@ -87,9 +87,9 @@ public class NodeStoreKernel implements
revision = head;
}
- NodeState root = revisions.get(revision);
- if (root != null) {
- return root;
+ Revision r = revisions.get(revision);
+ if (r != null) {
+ return r.root;
}
NodeBuilder builder = branches.get(revision);
@@ -256,9 +256,9 @@ public class NodeStoreKernel implements
@Override
public synchronized String getHeadRevision() {
NodeState root = store.getRoot();
- if (!root.equals(revisions.get(head))) {
+ if (!root.equals(revisions.get(head).root)) {
head = UUID.randomUUID().toString();
- revisions.put(head, root);
+ revisions.put(head, new Revision(root, "external"));
notifyAll();
}
return head;
@@ -273,21 +273,49 @@ public class NodeStoreKernel implements
public synchronized String getRevisionHistory(
long since, int maxEntries, String path)
throws MicroKernelException {
+ if (maxEntries < 0) {
+ maxEntries = Integer.MAX_VALUE;
+ }
+
JsopBuilder json = new JsopBuilder();
json.array();
int count = 0;
- for (String revision : revisions.keySet()) {
- if (count++ > maxEntries) {
- break;
+ NodeState before = null;
+ for (Map.Entry<String, Revision> entry : revisions.entrySet()) {
+ Revision rev = entry.getValue();
+ if (before != null && rev.timestamp >= since
+ && !getPathChanges(before, rev.root, path).isEmpty()) {
+ if (count++ > maxEntries) {
+ break;
+ }
+ json.object();
+ json.key("id").value(entry.getKey());
+ json.key("ts").value(rev.timestamp);
+ json.key("msg").value(rev.message);
+ json.endObject();
}
- json.object();
- json.key("id").value(revision);
- json.endObject();
+ before = rev.root;
}
json.endArray();
return json.toString();
}
+ private String getPathChanges(
+ NodeState before, NodeState after, String path) {
+ if (path != null) {
+ for (String element : PathUtils.elements(path)) {
+ before = before.getChildNode(element);
+ after = after.getChildNode(element);
+ }
+ } else {
+ path = "/";
+ }
+
+ JsopDiff diff = new JsopDiff(blobSerializer, path);
+ after.compareAgainstBaseState(before, diff);
+ return diff.toString();
+ }
+
@Override
public synchronized String waitForCommit(
String oldHeadRevisionId, long timeout)
@@ -301,10 +329,41 @@ public class NodeStoreKernel implements
}
@Override
- public String getJournal(
+ public synchronized String getJournal(
String fromRevisionId, String toRevisionId, String path)
throws MicroKernelException {
- throw new UnsupportedOperationException();
+ if (!revisions.containsKey(fromRevisionId)) {
+ throw new MicroKernelException(
+ "Revision not found: " + fromRevisionId);
+ }
+
+ JsopBuilder json = new JsopBuilder();
+ json.array();
+ NodeState before = null;
+ boolean active = false;
+ for (Map.Entry<String, Revision> entry : revisions.entrySet()) {
+ String id = entry.getKey();
+ Revision rev = entry.getValue();
+ active = active || id.equals(fromRevisionId);
+ if (before != null && active) {
+ String jsop = getPathChanges(before, rev.root, path);
+ if (!jsop.isEmpty()) {
+ json.object();
+ json.key("id").value(id);
+ json.key("ts").value(rev.timestamp);
+ json.key("msg").value(rev.message);
+ json.key("changes").value(jsop);
+ json.endObject();
+ }
+ }
+ if (id.equals(toRevisionId)) {
+ break;
+ }
+ before = rev.root;
+ }
+
+ json.endArray();
+ return json.toString();
}
@Override
@@ -364,8 +423,11 @@ public class NodeStoreKernel implements
if (maxChildNodes < 0) {
maxChildNodes = Integer.MAX_VALUE;
}
+ if (filter == null) {
+ filter = "{}";
+ }
JsonSerializer json = new JsonSerializer(
- depth, offset, maxChildNodes, blobSerializer);
+ depth, offset, maxChildNodes, filter, blobSerializer);
json.serialize(node);
return json.toString();
} else {
@@ -381,19 +443,20 @@ public class NodeStoreKernel implements
revisionId = head;
}
- NodeState root = revisions.get(revisionId);
- if (root != null) {
+ Revision r = revisions.get(revisionId);
+ if (r != null) {
try {
- NodeBuilder builder = root.builder();
+ NodeBuilder builder = r.root.builder();
applyJsop(builder, path, jsonDiff);
NodeState newRoot = store.merge(
builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
- NodeState oldRoot = revisions.get(head);
- if (!newRoot.equals(oldRoot)) {
+ Revision old = revisions.get(head);
+ if (!newRoot.equals(old.root)) {
String uuid = UUID.randomUUID().toString();
- revisions.put(uuid, newRoot);
+ revisions.put(uuid, new Revision(newRoot, message));
head = uuid;
+ notifyAll();
}
return head;
@@ -430,11 +493,12 @@ public class NodeStoreKernel implements
NodeState newRoot = store.merge(
builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
- NodeState oldRoot = revisions.get(head);
- if (!newRoot.equals(oldRoot)) {
+ Revision old = revisions.get(head);
+ if (!newRoot.equals(old.root)) {
String uuid = UUID.randomUUID().toString();
- revisions.put(uuid, newRoot);
+ revisions.put(uuid, new Revision(newRoot, message));
head = uuid;
+ notifyAll();
}
return head;
@@ -532,4 +596,26 @@ public class NodeStoreKernel implements
}
}
+ private static class Revision {
+
+ private final NodeState root;
+
+ private final String message;
+
+ private final long timestamp;
+
+ Revision(NodeState root, String message) {
+ this.root = root;
+ this.message = message;
+ this.timestamp = System.currentTimeMillis();
+ }
+
+ Revision(NodeState root) {
+ this.root = root;
+ this.message = "origin";
+ this.timestamp = -1;
+ }
+
+ }
+
}
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/JsopDiffTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/JsopDiffTest.java?rev=1533666&r1=1533665&r2=1533666&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/JsopDiffTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/JsopDiffTest.java
Sat Oct 19 00:01:36 2013
@@ -45,7 +45,7 @@ public class JsopDiffTest {
diff = new JsopDiff();
diff.propertyChanged(before, DoublePropertyState.doubleProperty("foo",
1.23));
- assertEquals("^\"/foo\":\"dou:1.23\"", diff.toString()); // TODO: 1.23?
+ assertEquals("^\"/foo\":1.23", diff.toString());
diff = new JsopDiff();
diff.propertyChanged(before,
BooleanPropertyState.booleanProperty("foo", true));