diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/PnCommand.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/PnCommand.groovy index ef7b4a7723..987b6df5dd 100644 --- a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/PnCommand.groovy +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/PnCommand.groovy @@ -20,7 +20,7 @@ package org.apache.jackrabbit.oak.console.commands import groovy.transform.CompileStatic import org.apache.jackrabbit.oak.console.ConsoleSession -import org.apache.jackrabbit.oak.spi.state.AbstractNodeState +import org.apache.jackrabbit.oak.nodestate.NodeStateHelper import org.codehaus.groovy.tools.shell.CommandSupport import org.codehaus.groovy.tools.shell.Groovysh @@ -28,18 +28,19 @@ import org.codehaus.groovy.tools.shell.Groovysh class PnCommand extends CommandSupport{ public static final String COMMAND_NAME = 'print-node' - public PnCommand(Groovysh shell) { + PnCommand(Groovysh shell) { super(shell, COMMAND_NAME, 'pn') } @Override Object execute(List args) { assertNoArguments(args) - io.out.println(AbstractNodeState.toString(getSession().getWorkingNode())) + io.out.println(NodeStateHelper.nodeStateToString(getSession().getWorkingNode())) return null } ConsoleSession getSession(){ return (ConsoleSession)variables.session } + } diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/exporter/NodeStateSerializer.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/exporter/NodeStateSerializer.java index 8a164107c2..577904335e 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/exporter/NodeStateSerializer.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/exporter/NodeStateSerializer.java @@ -101,9 +101,9 @@ public class NodeStateSerializer { } private void serialize(JsopWriter writer, BlobSerializer blobSerializer) throws IOException { - JsonSerializer serializer = new JsonSerializer(writer, depth, 0, maxChildNodes, getFilter(), blobSerializer); + JsonSerializer serializer = new JsonSerializer(writer, depth, 0, maxChildNodes, getFilter(), blobSerializer, true); NodeState state = NodeStateUtils.getNode(nodeState, path); - serializer.serialize(state); + serializer.serialize(state, path); } private BlobSerializer createBlobSerializer(File dir) { diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/nodestate/NodeStateHelper.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/nodestate/NodeStateHelper.java new file mode 100644 index 0000000000..da488e580e --- /dev/null +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/nodestate/NodeStateHelper.java @@ -0,0 +1,79 @@ +/************************************************************************** + * 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.nodestate; + +import static java.lang.Integer.getInteger; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +public abstract class NodeStateHelper { + + private static final int CHILDREN_CAP = getInteger("oak.children.cap", 100); + + public static String nodeStateToString(NodeState state) { + if (!state.exists()) { + return "{N/A}"; + } + StringBuilder builder = new StringBuilder("{"); + String separator = " "; + for (PropertyState property : state.getProperties()) { + builder.append(separator); + separator = ", "; + try { + builder.append(property); + } catch (Throwable t) { + builder.append(property.getName()); + builder.append(" = { ERROR on property: "); + builder.append(t.getMessage()); + builder.append(" }"); + } + } + int count = CHILDREN_CAP; + for (ChildNodeEntry entry : state.getChildNodeEntries()) { + if (count-- == 0) { + builder.append("..."); + break; + } + builder.append(separator); + separator = ", "; + try { + builder.append(childNodeEntryToString(entry)); + } catch (Throwable t) { + builder.append(entry.getName()); + builder.append(" = { ERROR on node: "); + builder.append(t.getMessage()); + builder.append(" }"); + } + } + builder.append(" }"); + return builder.toString(); + } + + public static String childNodeEntryToString(ChildNodeEntry entry) { + String name = entry.getName(); + NodeState state = entry.getNodeState(); + if (state.getChildNodeCount(1) == 0) { + return name + " : " + nodeStateToString(state); + } else { + return name + " = { ... }"; + } + } +} diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java index db772ef5d9..a443a150c9 100644 --- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java +++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java @@ -25,9 +25,12 @@ import static org.apache.jackrabbit.oak.api.Type.LONG; import static org.apache.jackrabbit.oak.api.Type.NAMES; import static org.apache.jackrabbit.oak.api.Type.STRING; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import javax.annotation.Nonnull; import javax.jcr.PropertyType; import com.google.common.collect.ImmutableList; @@ -38,19 +41,31 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.commons.json.JsopWriter; +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; +import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState; +import org.apache.jackrabbit.oak.spi.state.AbstractNodeState; import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Utility class for serializing node and property states to JSON. */ public class JsonSerializer { + private static final Logger log = LoggerFactory.getLogger(JsonSerializer.class); + public static final String DEFAULT_FILTER_EXPRESSION = "{\"properties\":[\"*\", \"-:childNodeCount\"]}"; private static final JsonFilter DEFAULT_FILTER = new JsonFilter(DEFAULT_FILTER_EXPRESSION); + private static final String ERROR_JSON_KEY = "_error"; + private static final String ERROR_JSON_VALUE_PREFIX = "ERROR: "; + private final JsopWriter json; private final int depth; @@ -63,86 +78,167 @@ public class JsonSerializer { private final BlobSerializer blobs; + private final boolean catchExceptions; + private JsonSerializer( JsopWriter json, int depth, long offset, int maxChildNodes, - JsonFilter filter, BlobSerializer blobs) { + JsonFilter filter, BlobSerializer blobs, boolean catchExceptions) { this.json = checkNotNull(json); this.depth = depth; this.offset = offset; this.maxChildNodes = maxChildNodes; this.filter = checkNotNull(filter); this.blobs = checkNotNull(blobs); + this.catchExceptions = catchExceptions; } public JsonSerializer( int depth, long offset, int maxChildNodes, String filter, BlobSerializer blobs) { this(new JsopBuilder(), depth, offset, maxChildNodes, - new JsonFilter(filter), blobs); + new JsonFilter(filter), blobs, false); } public JsonSerializer(JsopWriter json, int depth, long offset, int maxChildNodes, String filter, BlobSerializer blobs) { this(json, depth, offset, maxChildNodes, - new JsonFilter(filter), blobs); + new JsonFilter(filter), blobs, false); + } + + public JsonSerializer(JsopWriter json, + int depth, long offset, int maxChildNodes, + String filter, BlobSerializer blobs, boolean catchExceptions) { + this(json, depth, offset, maxChildNodes, + new JsonFilter(filter), blobs, catchExceptions); } public JsonSerializer(JsopWriter json, BlobSerializer blobs) { this(json, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, - DEFAULT_FILTER, blobs); + DEFAULT_FILTER, blobs, false); } public JsonSerializer(JsopWriter json, String filter, BlobSerializer blobs) { this(json, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, - new JsonFilter(filter), blobs); + new JsonFilter(filter), blobs, false); } protected JsonSerializer getChildSerializer() { return new JsonSerializer( - json, depth - 1, 0, maxChildNodes, filter, blobs); + json, depth - 1, 0, maxChildNodes, filter, blobs, catchExceptions); } public void serialize(NodeState node) { + serialize(node, ""); + } + + public void serialize(NodeState node, String basePath) { json.object(); - for (PropertyState property : node.getProperties()) { - String name = property.getName(); - if (filter.includeProperty(name)) { - json.key(name); - serialize(property); + try { + for (PropertyState property : node.getProperties()) { + String name = property.getName(); + if (filter.includeProperty(name)) { + json.key(name); + try { + serialize(property); + } catch (Throwable t) { + if (catchExceptions) { + String message = "Cannot read property value " + basePath + "/" + name + " : " + t.getMessage(); + log.error(message); + json.value(ERROR_JSON_VALUE_PREFIX + message); + } else { + throw t; + } + } + } } - } - int index = 0; - int count = 0; - for (ChildNodeEntry child : getChildNodeEntries(node)) { - String name = child.getName(); - if (filter.includeNode(name) && index++ >= offset) { - if (count++ >= maxChildNodes) { - break; - } + int index = 0; + int count = 0; + for (ChildNodeEntry child : getChildNodeEntries(node, basePath)) { + String name = child.getName(); + if (filter.includeNode(name) && index++ >= offset) { + if (count++ >= maxChildNodes) { + break; + } - json.key(name); - if (depth > 0) { - getChildSerializer().serialize(child.getNodeState()); - } else { - json.object(); - json.endObject(); + json.key(name); + if (depth > 0) { + getChildSerializer().serialize(child.getNodeState(), basePath + "/" + name); + } else { + json.object(); + json.endObject(); + } } } + } catch (Throwable t) { + if (catchExceptions) { + String message = "Cannot read node " + basePath + " : " + t.getMessage(); + log.error(message); + json.key(ERROR_JSON_KEY); + json.value(ERROR_JSON_VALUE_PREFIX + message); + } else { + throw t; + } } json.endObject(); } - private Iterable getChildNodeEntries(NodeState node) { + private Iterable getChildNodeEntries(NodeState node, String basePath) { PropertyState order = node.getProperty(":childOrder"); if (order != null) { List names = ImmutableList.copyOf(order.getValue(NAMES)); List entries = Lists.newArrayListWithCapacity(names.size()); for (String name : names) { - entries.add(new MemoryChildNodeEntry(name, node.getChildNode(name))); + try { + entries.add(new MemoryChildNodeEntry(name, node.getChildNode(name))); + } catch (Throwable t) { + if (catchExceptions) { + String message = "Cannot read node " + basePath + "/" + name + " : " + t.getMessage(); + log.error(message); + + // return a placeholder child node entry for tracking the error into the JSON + entries.add(new MemoryChildNodeEntry(name, new AbstractNodeState() { + @Override + public boolean exists() { + return true; + } + + @Nonnull + @Override + public Iterable getProperties() { + return Collections.singleton(new StringPropertyState(ERROR_JSON_KEY, ERROR_JSON_VALUE_PREFIX + message)); + } + + @Override + public boolean hasChildNode(@Nonnull String name) { + return false; + } + + @Nonnull + @Override + public NodeState getChildNode(@Nonnull String name) throws IllegalArgumentException { + return EmptyNodeState.MISSING_NODE; + } + + @Nonnull + @Override + public Iterable getChildNodeEntries() { + return Collections.EMPTY_LIST; + } + + @Nonnull + @Override + public NodeBuilder builder() { + return new ReadOnlyBuilder(this); + } + })); + } else { + throw t; + } + } } return entries; }