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 1844544) +++ 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,74 @@ +/* + * 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,260 @@ +/* + * 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 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; + + 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() { + 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("b\n"); + exportNode(node); + out.printf("e\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("u\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); + } + + 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("i "); + 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