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 1843888) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java (working copy) @@ -60,5 +60,6 @@ .put("server", new ServerCommand()) .put(DataStoreCommand.NAME, new DataStoreCommand()) .put("segment-copy", new SegmentCopyCommand()) + .put("search", new SearchCommand()) .build()); } Index: oak-run/src/main/java/org/apache/jackrabbit/oak/run/SearchCommand.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/run/SearchCommand.java (nonexistent) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/SearchCommand.java (working copy) @@ -0,0 +1,90 @@ +/* + * 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 joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.apache.jackrabbit.oak.run.commons.Command; +import org.apache.jackrabbit.oak.segment.tool.SearchNodes; +import org.apache.jackrabbit.oak.segment.tool.SearchNodes.Builder; + +class SearchCommand implements Command { + + @Override + public void execute(String... args) throws Exception { + OptionParser options = new OptionParser(); + OptionSpec property = options.acceptsAll(asList("p", "property"), "Matches a property name") + .withRequiredArg() + .describedAs("name"); + OptionSpec childName = options.acceptsAll(asList("c", "child"), "Matches a child node name") + .withRequiredArg() + .describedAs("name"); + OptionSpec value = options.acceptsAll(asList("v", "value"), "Matches a property value") + .withRequiredArg() + .describedAs("name=value"); + OptionSpec help = options.acceptsAll(asList("h", "help"), "Prints help and exits"); + OptionSpec dir = options.nonOptions() + .describedAs("path") + .ofType(String.class); + OptionSet parsed = options.parse(args); + + if (parsed.has(help)) { + options.printHelpOn(System.out); + System.exit(0); + } + + if (parsed.valuesOf(dir).size() != 1) { + options.printHelpOn(System.err); + System.exit(1); + } + + Builder builder = SearchNodes.builder() + .withPath(new File(parsed.valueOf(dir))) + .withOut(System.out) + .withErr(System.err); + + for (String v : parsed.valuesOf(property)) { + builder.withProperty(v); + } + + for (String v : parsed.valuesOf(childName)) { + builder.withChild(v); + } + + for (String v : parsed.valuesOf(value)) { + String[] parts = v.split("="); + + if (parts.length != 2) { + System.err.println("Invalid property value specified: " + v); + System.exit(1); + } + + builder.withValue(parts[0], parts[1]); + } + + System.exit(builder.build().run()); + } + +} Property changes on: oak-run/src/main/java/org/apache/jackrabbit/oak/run/SearchCommand.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/SearchNodes.java =================================================================== --- oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/SearchNodes.java (nonexistent) +++ oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/SearchNodes.java (working copy) @@ -0,0 +1,235 @@ +/* + * 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.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.json.JsonObject; +import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; +import org.apache.jackrabbit.oak.segment.RecordId; +import org.apache.jackrabbit.oak.segment.RecordType; +import org.apache.jackrabbit.oak.segment.SegmentId; +import org.apache.jackrabbit.oak.segment.SegmentNodeState; +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; + +public class SearchNodes { + + public static Builder builder() { + return new Builder(); + } + + private interface Matcher { + + boolean matches(NodeState node); + + } + + public static class Builder { + + private File path; + + private List matchers = new ArrayList<>(); + + private PrintStream out = System.out; + + private PrintStream err = System.err; + + public Builder withPath(File path) { + this.path = path; + return this; + } + + public Builder withProperty(String name) { + checkNotNull(name, "name"); + matchers.add(node -> node.hasProperty(name)); + return this; + } + + public Builder withChild(String name) { + checkNotNull(name, "name"); + matchers.add(node -> node.hasChildNode(name)); + return this; + } + + public Builder withValue(String name, String value) { + checkNotNull(name, "name"); + checkNotNull(value, "value"); + matchers.add(node -> { + PropertyState p = node.getProperty(name); + if (p == null) { + return false; + } + if (p.isArray()) { + for (String v : p.getValue(Type.STRINGS)) { + if (v.equals(value)) { + return true; + } + } + return false; + } + return p.getValue(Type.STRING).equals(value); + }); + return this; + } + + public Builder withOut(PrintStream out) { + this.out = checkNotNull(out, "out"); + return this; + } + + public Builder withErr(PrintStream err) { + this.err = checkNotNull(err, "err"); + return this; + } + + public SearchNodes build() { + checkArgument(path != null, "path not specified"); + return new SearchNodes(this); + } + + } + + private final File path; + + private final List matchers; + + private final PrintStream out; + + private final PrintStream err; + + private final Set notFoundSegments = new HashSet<>(); + + private SearchNodes(Builder builder) { + this.path = builder.path; + this.matchers = new ArrayList<>(builder.matchers); + this.out = builder.out; + this.err = builder.err; + } + + public int run() { + try (ReadOnlyFileStore fileStore = newFileStore()) { + for (SegmentId segmentId : fileStore.getSegmentIds()) { + try { + processSegment(fileStore, segmentId); + } catch (SegmentNotFoundException e) { + handle(e); + } + } + } catch (Exception e) { + e.printStackTrace(err); + return 1; + } + return 0; + } + + private ReadOnlyFileStore newFileStore() throws Exception { + return FileStoreBuilder.fileStoreBuilder(path).buildReadOnly(); + } + + private Long parseSegmentInfoTimestamp(SegmentId segmentId) { + String segmentInfo = segmentId.getSegment().getSegmentInfo(); + + if (segmentInfo == null) { + err.printf("Segment info not found in %s\n", segmentId); + return null; + } + + JsopTokenizer t = new JsopTokenizer(segmentInfo, 0); + t.read('{'); + JsonObject object = JsonObject.create(t); + + String timestampString = object.getProperties().get("t"); + + if (timestampString == null) { + err.printf("No timestamp found in segment info in %s\n", segmentId); + return null; + } + + long timestamp; + + try { + timestamp = Long.parseLong(timestampString); + } catch (NumberFormatException e) { + err.printf("Invalid timestamp %s found in segment info in %s\n", timestampString, segmentId); + return null; + } + + return timestamp; + } + + private void processSegment(ReadOnlyFileStore fileStore, SegmentId segmentId) { + if (segmentId.isBulkSegmentId()) { + return; + } + + Long timestamp = parseSegmentInfoTimestamp(segmentId); + + if (timestamp == null) { + return; + } + + segmentId.getSegment().forEachRecord((number, type, offset) -> { + if (type != RecordType.NODE) { + return; + } + try { + processRecord(fileStore, timestamp, new RecordId(segmentId, number)); + } catch (SegmentNotFoundException e) { + handle(e); + } + }); + } + + private void processRecord(ReadOnlyFileStore fileStore, long timestamp, RecordId recordId) { + SegmentNodeState nodeState = fileStore.getReader().readNode(recordId); + + boolean matches = true; + + for (Matcher matcher : matchers) { + matches = matches && matcher.matches(nodeState); + } + + if (!matches) { + return; + } + + out.printf("%d\t%s\n", timestamp, recordId); + } + + 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/SearchNodes.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property