Java tutorial
/* * 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.explorer; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Maps.newTreeMap; import static com.google.common.collect.Sets.intersection; import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newTreeSet; import static com.google.common.escape.Escapers.builder; import static java.util.Collections.sort; import static javax.jcr.PropertyType.BINARY; import static javax.jcr.PropertyType.STRING; import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION; import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; import static org.apache.jackrabbit.oak.commons.PathUtils.concat; import static org.apache.jackrabbit.oak.commons.PathUtils.elements; import static org.apache.jackrabbit.oak.commons.json.JsopBuilder.prettyPrint; import static org.apache.jackrabbit.oak.json.JsopDiff.diffToJsop; import java.awt.*; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; 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.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; class NodeStoreTree extends JPanel implements TreeSelectionListener, Closeable { private static final long serialVersionUID = 1L; private final static int MAX_CHAR_DISPLAY = Integer.getInteger("max.char.display", 60); private static final String newline = "\n"; private static void printGcRoots(StringBuilder sb, Map<UUID, Set<Entry<UUID, String>>> links, UUID uuid, String space, String inc) { Set<Entry<UUID, String>> roots = links.remove(uuid); if (roots == null || roots.isEmpty()) { return; } // TODO is sorting by file name needed? for (Entry<UUID, String> r : roots) { sb.append(space).append(r.getKey()).append("[").append(r.getValue()).append("]").append(newline); printGcRoots(sb, links, r.getKey(), space + inc, inc); } } private static void printPaths(List<String> paths, StringBuilder sb) { if (paths.isEmpty()) { return; } sb.append("Repository content references:").append(newline); for (String p : paths) { sb.append(p).append(newline); } } private static void printPropertyState(ExplorerBackend store, PropertyState state, String parentFile, StringBuilder b) { if (store.isPersisted(state)) { printRecordId(store.getRecordId(state), store.getFile(state), parentFile, b); } else { printSimpleClassName(state, b); } } private static void printNodeState(ExplorerBackend store, NodeState state, String parentTarFile, StringBuilder b) { if (store.isPersisted(state)) { printRecordId(store.getRecordId(state), store.getFile(state), parentTarFile, b); } else { printSimpleClassName(state, b); } } private static void printRecordId(String recordId, String file, String parentFile, StringBuilder l) { l.append(" (").append(recordId); if (file != null && !file.equals(parentFile)) { l.append(" in ").append(file); } l.append(")"); } private static void printSimpleClassName(Object o, StringBuilder l) { l.append(" (").append(o.getClass().getSimpleName()).append(")"); } private final ExplorerBackend backend; private Map<String, Set<UUID>> index; private DefaultTreeModel treeModel; private final JTree tree; private final JTextArea log; private Map<String, Long[]> sizeCache; private final boolean skipSizeCheck; NodeStoreTree(ExplorerBackend backend, JTextArea log, boolean skipSizeCheck) throws IOException { super(new GridLayout(1, 0)); this.backend = backend; this.log = log; this.skipSizeCheck = skipSizeCheck; tree = new JTree(); tree.getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); tree.addTreeSelectionListener(this); tree.setExpandsSelectedPaths(true); refreshStore(); refreshModel(); JScrollPane scrollPane = new JScrollPane(tree); add(scrollPane); } private void refreshStore() throws IOException { backend.open(); } private void refreshModel() { index = backend.getTarReaderIndex(); sizeCache = newHashMap(); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( new NamePathModel("/", "/", backend.getHead(), sizeCache, skipSizeCheck, backend), true); treeModel = new DefaultTreeModel(rootNode); addChildren(rootNode); tree.setModel(treeModel); } void reopen() throws IOException { close(); refreshStore(); refreshModel(); } @Override public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node == null) { return; } // load child nodes: try { addChildren(node); updateStats(node); } catch (IllegalStateException ex) { ex.printStackTrace(); StringBuilder sb = new StringBuilder(); sb.append(ex.getMessage()); sb.append(newline); NamePathModel model = (NamePathModel) node.getUserObject(); NodeState state = model.getState(); String recordId = backend.getRecordId(state); if (recordId != null) { sb.append("Record "); sb.append(recordId); sb.append(newline); } setText(sb.toString()); } } private void setText(String s) { log.setText(s); log.setCaretPosition(0); } private void addChildren(DefaultMutableTreeNode parent) { NamePathModel model = (NamePathModel) parent.getUserObject(); if (model.isLoaded()) { return; } List<NamePathModel> kids = newArrayList(); for (ChildNodeEntry ce : model.getState().getChildNodeEntries()) { NamePathModel c = new NamePathModel(ce.getName(), concat(model.getPath(), ce.getName()), ce.getNodeState(), sizeCache, skipSizeCheck, backend); kids.add(c); } sort(kids); for (NamePathModel c : kids) { DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(c, true); treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); } model.loaded(); } private void updateStats(DefaultMutableTreeNode parent) { NamePathModel model = (NamePathModel) parent.getUserObject(); StringBuilder sb = new StringBuilder(); sb.append(model.getPath()); sb.append(newline); NodeState state = model.getState(); String tarFile = ""; if (backend.isPersisted(state)) { String recordId = backend.getRecordId(state); sb.append("Record ").append(recordId); tarFile = backend.getFile(state); if (tarFile != null) { sb.append(" in ").append(tarFile); } sb.append(newline); String templateId = backend.getTemplateRecordId(state); String f = backend.getTemplateFile(state); sb.append("TemplateId "); sb.append(templateId); if (f != null && !f.equals(tarFile)) { sb.append(" in ").append(f); } sb.append(newline); } sb.append("Size: "); sb.append(" direct: "); sb.append(byteCountToDisplaySize(model.getSize()[0])); sb.append("; linked: "); sb.append(byteCountToDisplaySize(model.getSize()[1])); sb.append(newline); sb.append("Properties (count: ").append(state.getPropertyCount()).append(")"); sb.append(newline); Map<String, String> propLines = newTreeMap(); for (PropertyState ps : state.getProperties()) { StringBuilder l = new StringBuilder(); l.append(" - ").append(ps.getName()).append(" = {").append(ps.getType()).append("} "); if (ps.getType().isArray()) { int count = ps.count(); l.append("(count ").append(count).append(") ["); String separator = ", "; int max = 50; if (ps.getType() == Type.BINARIES) { separator = newline + " "; max = Integer.MAX_VALUE; l.append(separator); } for (int i = 0; i < Math.min(count, max); i++) { if (i > 0) { l.append(separator); } l.append(toString(ps, i, tarFile)); } if (count > max) { l.append(", ... (").append(count).append(" values)"); } if (ps.getType() == Type.BINARIES) { l.append(separator); } l.append("]"); } else { l.append(toString(ps, 0, tarFile)); } printPropertyState(backend, ps, tarFile, l); propLines.put(ps.getName(), l.toString()); } for (String l : propLines.values()) { sb.append(l); sb.append(newline); } sb.append("Child nodes (count: ").append(state.getChildNodeCount(Long.MAX_VALUE)).append(")"); sb.append(newline); Map<String, String> childLines = newTreeMap(); for (ChildNodeEntry ce : state.getChildNodeEntries()) { StringBuilder l = new StringBuilder(); l.append(" + ").append(ce.getName()); NodeState c = ce.getNodeState(); printNodeState(backend, c, tarFile, l); childLines.put(ce.getName(), l.toString()); } for (String l : childLines.values()) { sb.append(l); sb.append(newline); } if ("/".equals(model.getPath())) { sb.append("File Reader Index"); sb.append(newline); for (String path : backend.getTarFiles()) { sb.append(path); sb.append(newline); } sb.append("----------"); } setText(sb.toString()); } private String toString(PropertyState ps, int index, String tarFile) { if (ps.getType().tag() == BINARY) { Blob b = ps.getValue(Type.BINARY, index); String info = "<"; info += b.getClass().getSimpleName() + ";"; info += "ref:" + safeGetReference(b) + ";"; info += "id:" + b.getContentIdentity() + ";"; info += safeGetLength(b) + ">"; for (Entry<UUID, String> e : backend.getBulkSegmentIds(b).entrySet()) { info += newline + " Bulk Segment Id " + e.getKey(); if (e.getValue() != null && !e.getValue().equals(tarFile)) { info += " in " + e.getValue(); } } return info; } else if (ps.getType().tag() == STRING) { return displayString(ps.getValue(Type.STRING, index)); } else { return ps.getValue(Type.STRING, index); } } private static String displayString(String value) { if (MAX_CHAR_DISPLAY > 0 && value.length() > MAX_CHAR_DISPLAY) { value = value.substring(0, MAX_CHAR_DISPLAY) + "... (" + value.length() + " chars)"; } String escaped = builder().setSafeRange(' ', '~').addEscape('"', "\\\"").addEscape('\\', "\\\\").build() .escape(value); return '"' + escaped + '"'; } private String safeGetReference(Blob b) { try { return b.getReference(); } catch (IllegalStateException e) { // missing BlobStore probably } return "[BlobStore not available]"; } private String safeGetLength(Blob b) { try { return byteCountToDisplaySize(b.length()); } catch (IllegalStateException e) { // missing BlobStore probably } return "[BlobStore not available]"; } void printTarInfo(String file) { if (file == null || file.length() == 0) { return; } StringBuilder sb = new StringBuilder(); Set<UUID> uuids = newHashSet(); for (Entry<String, Set<UUID>> e : index.entrySet()) { if (e.getKey().endsWith(file)) { sb.append("SegmentNodeState references to ").append(new File(e.getKey()).getName()); sb.append(newline); uuids = e.getValue(); break; } } Set<UUID> inMem = intersection(backend.getReferencedSegmentIds(), uuids); if (!inMem.isEmpty()) { sb.append("In Memory segment references: "); sb.append(newline); sb.append(inMem); sb.append(newline); } List<String> paths = newArrayList(); filterNodeStates(uuids, paths, backend.getHead(), "/", backend); printPaths(paths, sb); sb.append(newline); try { Map<UUID, List<UUID>> graph = backend.getTarGraph(file); sb.append("Tar graph:").append(newline); for (Entry<UUID, List<UUID>> entry : graph.entrySet()) { sb.append(entry.getKey()).append('=').append(entry.getValue()).append(newline); } sb.append(newline); } catch (IOException e) { sb.append("Error getting tar graph:").append(e).append(newline); } setText(sb.toString()); } void printSegmentReferences(String sid) { if (sid == null || sid.length() == 0) { return; } UUID id = null; try { id = UUID.fromString(sid.trim()); } catch (IllegalArgumentException e) { setText(e.getMessage()); return; } StringBuilder sb = new StringBuilder(); sb.append("References to segment ").append(id); sb.append(newline); for (Entry<String, Set<UUID>> e : index.entrySet()) { if (e.getValue().contains(id)) { sb.append("Tar file: ").append(new File(e.getKey()).getName()); sb.append(newline); break; } } List<String> paths = newArrayList(); filterNodeStates(newHashSet(id), paths, backend.getHead(), "/", backend); printPaths(paths, sb); Map<UUID, Set<Entry<UUID, String>>> links = newHashMap(); try { backend.getGcRoots(id, links); } catch (IOException e) { sb.append(newline); sb.append(e.getMessage()); } if (!links.isEmpty()) { sb.append("Segment GC roots:"); sb.append(newline); printGcRoots(sb, links, id, " ", " "); } setText(sb.toString()); } private static void filterNodeStates(Set<UUID> uuids, List<String> paths, NodeState state, String path, ExplorerBackend store) { Set<String> localPaths = newTreeSet(); for (PropertyState ps : state.getProperties()) { if (store.isPersisted(ps)) { String recordId = store.getRecordId(ps); UUID id = store.getSegmentId(ps); if (uuids.contains(id)) { if (ps.getType().tag() == STRING) { String val = ""; if (ps.count() > 0) { // only shows the first value, do we need more? val = displayString(ps.getValue(Type.STRING, 0)); } localPaths.add(path + ps.getName() + " = " + val + " [SegmentPropertyState<" + ps.getType() + ">@" + recordId + "]"); } else { localPaths .add(path + ps + " [SegmentPropertyState<" + ps.getType() + ">@" + recordId + "]"); } } if (ps.getType().tag() == BINARY) { // look for extra segment references for (int i = 0; i < ps.count(); i++) { Blob b = ps.getValue(Type.BINARY, i); for (Entry<UUID, String> e : store.getBulkSegmentIds(b).entrySet()) { if (!e.getKey().equals(id) && uuids.contains(e.getKey())) { localPaths.add(path + ps + " [SegmentPropertyState<" + ps.getType() + ">@" + recordId + "]"); } } } } } } String stateId = store.getRecordId(state); if (uuids.contains(store.getSegmentId(state))) { localPaths.add(path + " [SegmentNodeState@" + stateId + "]"); } String templateId = store.getTemplateRecordId(state); if (uuids.contains(store.getTemplateSegmentId(state))) { localPaths.add(path + "[Template@" + templateId + "]"); } paths.addAll(localPaths); for (ChildNodeEntry ce : state.getChildNodeEntries()) { filterNodeStates(uuids, paths, ce.getNodeState(), path + ce.getName() + "/", store); } } void printDiff(String input) { StringBuilder sb = new StringBuilder(); if (input == null || input.trim().length() == 0) { setText("Usage <recordId> <recordId> [<path>]"); return; } String[] tokens = input.trim().split(" "); if (tokens.length != 2 && tokens.length != 3) { setText("Usage <recordId> <recordId> [<path>]"); return; } NodeState node1; NodeState node2; try { node1 = backend.readNodeState(tokens[0]); node2 = backend.readNodeState(tokens[1]); } catch (IllegalArgumentException ex) { sb.append("Unknown argument: "); sb.append(input); sb.append(newline); sb.append("Error: "); sb.append(ex.getMessage()); sb.append(newline); setText(sb.toString()); return; } String path = "/"; if (tokens.length == 3) { path = tokens[2]; } for (String name : elements(path)) { node1 = node1.getChildNode(name); node2 = node2.getChildNode(name); } sb.append("SegmentNodeState diff "); sb.append(tokens[0]); sb.append(" vs "); sb.append(tokens[1]); sb.append(" at "); sb.append(path); sb.append(newline); sb.append("--------"); sb.append(newline); sb.append(prettyPrint(diffToJsop(node1, node2))); setText(sb.toString()); } boolean revert(String revision) { return safeRevert(revision, false); } private boolean safeRevert(String revision, boolean rollback) { String head = backend.getRecordId(backend.getHead()); backend.setRevision(revision); try { refreshModel(); if (!rollback) { setText("Switched head revision to " + revision); } } catch (Exception e) { StringBuilder sb = new StringBuilder(); sb.append("Unable to switch head revision to "); sb.append(revision); sb.append(newline); sb.append(" "); sb.append(e.getMessage()); sb.append(newline); sb.append("Will rollback to "); sb.append(head); setText(sb.toString()); return safeRevert(head, true); } return !rollback; } void printPCMInfo() { setText(backend.getPersistedCompactionMapStats()); } private static class NamePathModel implements Comparable<NamePathModel> { private final ExplorerBackend backend; private final String name; private final String path; private final boolean skipSizeCheck; private boolean loaded = false; private Long[] size = { -1L, -1L }; NamePathModel(String name, String path, NodeState state, Map<String, Long[]> sizeCache, boolean skipSizeCheck, ExplorerBackend backend) { this.backend = backend; this.name = name; this.path = path; this.skipSizeCheck = skipSizeCheck; if (!skipSizeCheck && backend.isPersisted(state)) { this.size = exploreSize(state, sizeCache, backend); } } void loaded() { loaded = true; } boolean isLoaded() { return loaded; } @Override public String toString() { if (skipSizeCheck) { return name; } if (size[1] > 0) { return name + " (" + byteCountToDisplaySize(size[0]) + ";" + byteCountToDisplaySize(size[1]) + ")"; } if (size[0] > 0) { return name + " (" + byteCountToDisplaySize(size[0]) + ")"; } return name; } public String getPath() { return path; } public NodeState getState() { return loadState(); } private NodeState loadState() { NodeState n = backend.getHead(); for (String p : elements(path)) { n = n.getChildNode(p); } return n; } @Override public int compareTo(NamePathModel o) { int s = size[0].compareTo(o.size[0]); if (s != 0) { return -1 * s; } s = size[1].compareTo(o.size[1]); if (s != 0) { return -1 * s; } if ("root".equals(name)) { return 1; } else if ("root".equals(o.name)) { return -1; } return name.compareTo(o.name); } public Long[] getSize() { return size; } } private static Long[] exploreSize(NodeState ns, Map<String, Long[]> sizeCache, ExplorerBackend store) { String key = store.getRecordId(ns); if (sizeCache.containsKey(key)) { return sizeCache.get(key); } Long[] s = { 0L, 0L }; List<String> names = newArrayList(ns.getChildNodeNames()); if (names.contains("root")) { List<String> temp = newArrayList(); int poz = 0; // push 'root' to the beginning for (String n : names) { if (n.equals("root")) { temp.add(poz, n); poz++; } else { temp.add(n); } } names = temp; } for (String n : names) { NodeState k = ns.getChildNode(n); String ckey = store.getRecordId(k); if (sizeCache.containsKey(ckey)) { // already been here, record size under 'link' Long[] ks = sizeCache.get(ckey); s[1] = s[1] + ks[0] + ks[1]; } else { Long[] ks = exploreSize(k, sizeCache, store); s[0] = s[0] + ks[0]; s[1] = s[1] + ks[1]; } } for (PropertyState ps : ns.getProperties()) { for (int j = 0; j < ps.count(); j++) { if (ps.getType().tag() == Type.BINARY.tag()) { Blob b = ps.getValue(Type.BINARY, j); boolean skip = store.isExternal(b); if (!skip) { s[0] = s[0] + b.length(); } } else { s[0] = s[0] + ps.size(j); } } } sizeCache.put(key, s); return s; } @Override public void close() throws IOException { backend.close(); } }