Index: oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java (revision 1844749) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java (working copy) @@ -61,5 +61,6 @@ .put(DataStoreCommand.NAME, new DataStoreCommand()) .put("segment-copy", new SegmentCopyCommand()) .put("search-nodes", new SearchNodesCommand()) + .put("export-nodes", new ExportNodesCommand()) .build()); } Index: oak-run/src/main/java/org/apache/jackrabbit/oak/run/ExportNodesCommand.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/run/ExportNodesCommand.java (nonexistent) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/ExportNodesCommand.java (working copy) @@ -0,0 +1,75 @@ +/* + * 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.run; + +import static java.util.Arrays.asList; + +import java.io.File; +import java.io.InputStreamReader; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.apache.commons.io.LineIterator; +import org.apache.jackrabbit.oak.run.commons.Command; +import org.apache.jackrabbit.oak.segment.tool.ExportNodes; + +class ExportNodesCommand implements Command { + + @Override + public void execute(String... args) throws Exception { + OptionParser options = new OptionParser(); + OptionSpec help = options.acceptsAll(asList("h", "help"), "Prints help and exits"); + OptionSpec dir = options.nonOptions() + .describedAs("path") + .ofType(File.class); + OptionSet parsed = options.parse(args); + + if (parsed.has(help)) { + options.printHelpOn(System.out); + System.exit(0); + } + + if (parsed.valuesOf(dir).size() == 0) { + System.err.println("Segment Store path not specified"); + System.exit(1); + } + + if (parsed.valuesOf(dir).size() > 1) { + System.err.println("Too many Segment Store paths specified"); + System.exit(1); + } + + ExportNodes exportNodes = ExportNodes.builder() + .withPath(parsed.valueOf(dir)) + .withNodeRecordIds(newIterableInputStream()) + .withOut(System.out) + .withErr(System.err) + .build(); + + System.exit(exportNodes.run()); + } + + private static Iterable newIterableInputStream() { + return () -> new LineIterator(new InputStreamReader(System.in)); + } + +} + Property changes on: oak-run/src/main/java/org/apache/jackrabbit/oak/run/ExportNodesCommand.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/ExportNodes.java =================================================================== --- oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/ExportNodes.java (nonexistent) +++ oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/ExportNodes.java (working copy) @@ -0,0 +1,270 @@ +/* + * 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.segment.tool; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.HashSet; +import java.util.Set; + +import com.google.common.escape.CharEscaperBuilder; +import com.google.common.escape.Escaper; +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.segment.RecordId; +import org.apache.jackrabbit.oak.segment.SegmentBlob; +import org.apache.jackrabbit.oak.segment.SegmentNotFoundException; +import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder; +import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.util.Base64; + +public class ExportNodes { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private File path; + + private Iterable nodeRecordIds; + + private PrintStream out = System.out; + + private PrintStream err = System.err; + + private Builder() { + // Prevent external instantiation + } + + public Builder withPath(File path) { + this.path = checkNotNull(path, "path"); + return this; + } + + public Builder withNodeRecordIds(Iterable nodeRecordIds) { + this.nodeRecordIds = checkNotNull(nodeRecordIds); + return this; + } + + public Builder withOut(PrintStream out) { + this.out = checkNotNull(out); + return this; + } + + public Builder withErr(PrintStream err) { + this.err = checkNotNull(err); + return this; + } + + public ExportNodes build() { + checkState(path != null, "path not provided"); + checkState(nodeRecordIds != null, "node record IDs not provided"); + return new ExportNodes(this); + } + + } + + private final File path; + + private final Iterable nodeRecordIds; + + private final PrintStream out; + + private final PrintStream err; + + private final Set notFoundSegments = new HashSet<>(); + + private ExportNodes(Builder builder) { + this.path = builder.path; + this.nodeRecordIds = builder.nodeRecordIds; + this.out = builder.out; + this.err = builder.err; + } + + public int run() { + try (ReadOnlyFileStore store = newReadOnlyFileStore()) { + return run(store); + } catch (Exception e) { + e.printStackTrace(err); + return 1; + } + } + + private ReadOnlyFileStore newReadOnlyFileStore() throws Exception { + return FileStoreBuilder.fileStoreBuilder(path).buildReadOnly(); + } + + private int run(ReadOnlyFileStore store) { + for (String nodeRecordId : nodeRecordIds) { + String trimmed = nodeRecordId.trim(); + + if (trimmed.isEmpty()) { + continue; + } + + RecordId id; + + try { + id = RecordId.fromString(store.getSegmentIdProvider(), trimmed); + } catch (IllegalArgumentException e) { + err.printf("Unable to parse record ID: %s\n", nodeRecordId); + continue; + } + + try { + exportRoot(nodeRecordId, store.getReader().readNode(id)); + } catch (SegmentNotFoundException e) { + handle(e); + } + } + + return 0; + } + + private void exportRoot(String id, NodeState node) { + out.printf("# %s\n", id); + out.printf("e\n"); + exportNode(node); + out.printf("^\n"); + } + + private void exportNode(NodeState node) { + exportProperties(node); + exportChildren(node); + } + + private void exportChildren(NodeState node) { + try { + for (String name : node.getChildNodeNames()) { + exportChild(node, name); + } + } catch (SegmentNotFoundException e) { + handle(e); + } + } + + private void exportChild(NodeState parent, String name) { + try { + NodeState child = parent.getChildNode(name); + out.printf("c %s\n", name); + exportNode(child); + out.printf("^\n"); + } catch (SegmentNotFoundException e) { + handle(e); + } + } + + private void exportProperties(NodeState node) { + try { + for (PropertyState property : node.getProperties()) { + exportProperty(property); + } + } catch (SegmentNotFoundException e) { + handle(e); + } + } + + private void exportProperty(PropertyState property) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bos); + + try { + out.printf("p %s %s\n", property.getType(), property.getName()); + + if (property.isArray()) { + exportArrayProperty(out, property); + } else { + exportScalarProperty(out, property); + } + + out.printf("^\n"); + + this.out.write(bos.toByteArray()); + } catch (SegmentNotFoundException e) { + handle(e); + } catch (IOException e) { + e.printStackTrace(err); + } + } + + private void exportScalarProperty(PrintStream out, PropertyState property) throws IOException { + if (property.getType().equals(Type.BINARY)) { + exportBinaryValue(out, property.getValue(Type.BINARY)); + } else { + exportStringValue(out, property.getValue(Type.STRING)); + } + } + + private void exportArrayProperty(PrintStream out, PropertyState property) throws IOException { + if (property.getType().equals(Type.BINARIES)) { + for (int i = 0; i < property.count(); i++) { + exportBinaryValue(out, property.getValue(Type.BINARY, i)); + } + } else { + for (int i = 0; i < property.count(); i++) { + exportStringValue(out, property.getValue(Type.STRING, i)); + } + } + } + + private void exportBinaryValue(PrintStream out, Blob blob) throws IOException { + if (blob instanceof SegmentBlob) { + exportBinaryValue(out, (SegmentBlob) blob); + } else { + throw new IllegalStateException("Unable to access blob data"); + } + } + + private static final Escaper ESCAPER = new CharEscaperBuilder() + .addEscape('\n', "\\n") + .addEscape('\\', "\\\\") + .toEscaper(); + + private void exportBinaryValue(PrintStream out, SegmentBlob blob) throws IOException { + if (blob.isExternal()) { + out.printf("x %s\n", ESCAPER.escape(blob.getBlobId())); + } else { + out.printf("v "); + Base64.encode(blob.getNewStream(), out); + out.printf("\n"); + } + } + + private void exportStringValue(PrintStream out, String value) { + out.printf("v %s\n", ESCAPER.escape(value)); + } + + private void handle(SegmentNotFoundException e) { + if (notFoundSegments.add(e.getSegmentId())) { + e.printStackTrace(err); + } + } + +} + Property changes on: oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/ExportNodes.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/util/NodeExportParser.java =================================================================== --- oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/util/NodeExportParser.java (nonexistent) +++ oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/util/NodeExportParser.java (working copy) @@ -0,0 +1,307 @@ +/* + * 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.segment.util; + +import java.io.InputStream; + +public class NodeExportParser { + + private enum State { + START, + ERROR, + EXPORT, + CHILD, + CHILD_NAME, + PROPERTY, + PROPERTY_TYPE, + PROPERTY_NAME, + VALUE, + VALUE_DATA, + VALUE_DATA_SLASH, + EXTERNAL, + EXTERNAL_DATA, + EXTERNAL_DATA_SLASH, + COMMENT, + UP, + } + + protected void onError() throws Exception { + // Do nothing. + } + + protected void onExport() throws Exception { + // Do nothing. + } + + protected void onChild(CharSequence name) throws Exception { + // Do nothing. + } + + protected void onProperty(CharSequence type, CharSequence name) throws Exception { + // Do nothing. + } + + protected void onValue(CharSequence value) throws Exception { + // Do nothing. + } + + protected void onExternal(CharSequence blobId) throws Exception { + // Do nothing. + } + + protected void onUp() throws Exception { + // Do nothing. + } + + public final void parse(InputStream stream) throws Exception { + State state = State.START; + + StringBuilder childName = null; + StringBuilder propertyType = null; + StringBuilder propertyName = null; + StringBuilder propertyData = null; + StringBuilder externalData = null; + + while (true) { + if (state == State.ERROR) { + onError(); + return; + } + + int x = stream.read(); + + if (x < 0) { + return; + } + + char c = (char) x; + + switch (state) { + case START: + switch (c) { + case 'e': + state = State.EXPORT; + break; + case 'c': + state = State.CHILD; + break; + case 'p': + state = State.PROPERTY; + break; + case 'v': + state = State.VALUE; + break; + case 'x': + state = State.EXTERNAL; + break; + case '^': + state = State.UP; + break; + case '#': + state = State.COMMENT; + break; + default: + state = State.ERROR; + break; + } + break; + case COMMENT: + switch (c) { + case '\n': + state = State.START; + break; + default: + state = State.COMMENT; + break; + } + break; + case EXPORT: + switch (c) { + case '\n': + onExport(); + state = State.START; + break; + default: + state = State.ERROR; + break; + } + break; + case CHILD: + switch (c) { + case ' ': + childName = new StringBuilder(); + state = State.CHILD_NAME; + break; + default: + state = State.ERROR; + break; + } + break; + case CHILD_NAME: + switch (c) { + case '\n': + onChild(childName); + childName = null; + state = State.START; + break; + default: + childName.append(c); + state = State.CHILD_NAME; + break; + } + break; + case PROPERTY: + switch (c) { + case ' ': + propertyType = new StringBuilder(); + state = State.PROPERTY_TYPE; + break; + default: + state = State.ERROR; + break; + } + break; + case PROPERTY_TYPE: + switch (c) { + case ' ': + propertyName = new StringBuilder(); + state = State.PROPERTY_NAME; + break; + default: + propertyType.append(c); + state = State.PROPERTY_TYPE; + break; + } + break; + case PROPERTY_NAME: + switch (c) { + case '\n': + onProperty(propertyType, propertyName); + propertyType = null; + propertyName = null; + state = State.START; + break; + default: + propertyName.append(c); + state = State.PROPERTY_NAME; + break; + } + break; + case VALUE: + switch (c) { + case ' ': + propertyData = new StringBuilder(); + state = State.VALUE_DATA; + break; + default: + state = State.ERROR; + break; + } + break; + case VALUE_DATA: + switch (c) { + case '\\': + state = State.VALUE_DATA_SLASH; + break; + case '\n': + onValue(propertyData); + propertyData = null; + state = State.START; + break; + default: + propertyData.append(c); + state = State.VALUE_DATA; + break; + } + break; + case VALUE_DATA_SLASH: + switch (c) { + case '\\': + propertyData.append('\\'); + state = State.VALUE_DATA; + break; + case 'n': + propertyData.append('\n'); + state = State.VALUE_DATA; + break; + default: + state = State.ERROR; + break; + } + break; + case EXTERNAL: + switch (c) { + case ' ': + externalData = new StringBuilder(); + state = State.EXTERNAL_DATA; + break; + default: + state = State.ERROR; + break; + } + break; + case EXTERNAL_DATA: + switch (c) { + case '\\': + state = State.EXTERNAL_DATA_SLASH; + break; + case '\n': + onExternal(externalData); + externalData = null; + state = State.START; + break; + default: + externalData.append(c); + state = State.EXTERNAL_DATA; + break; + } + break; + case EXTERNAL_DATA_SLASH: + switch (c) { + case '\\': + externalData.append('\\'); + state = State.EXTERNAL_DATA; + break; + case 'n': + externalData.append('\n'); + state = State.EXTERNAL_DATA; + break; + default: + state = State.ERROR; + break; + } + break; + case UP: + switch (c) { + case '\n': + onUp(); + state = State.START; + break; + default: + state = State.ERROR; + break; + } + break; + } + + } + } + +} Property changes on: oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/util/NodeExportParser.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property