| /* |
| * 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 java.io.PrintStream; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TimeZone; |
| |
| import org.apache.jackrabbit.oak.plugins.document.ClusterNodeInfoDocument; |
| import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder; |
| import org.apache.jackrabbit.oak.plugins.document.DocumentStore; |
| import org.apache.jackrabbit.oak.plugins.document.rdb.RDBJSONSupport; |
| import org.apache.jackrabbit.oak.run.commons.Command; |
| |
| import com.google.common.io.Closer; |
| |
| import joptsimple.OptionSpec; |
| |
| class ClusterNodesCommand implements Command { |
| |
| @Override |
| public void execute(String... args) throws Exception { |
| Closer closer = Closer.create(); |
| try { |
| String h = "clusternodes mongodb://host:port/database|jdbc:..."; |
| ClusterNodesOptions options = new ClusterNodesOptions(h).parse(args); |
| if (options.isHelp()) { |
| options.printHelpOn(System.out); |
| System.exit(0); |
| } |
| |
| DocumentNodeStoreBuilder<?> builder = Utils.createDocumentMKBuilder(options, closer); |
| |
| if (builder == null) { |
| System.err |
| .println("Clusternodes command only available for DocumentNodeStore backed by MongoDB or RDB persistence"); |
| System.exit(1); |
| } |
| |
| builder.setReadOnlyMode(); |
| DocumentStore ds = builder.getDocumentStore(); |
| |
| try { |
| List<ClusterNodeInfoDocument> all = new ArrayList<>(ClusterNodeInfoDocument.all(ds)); |
| Collections.sort(all, new Comparator<ClusterNodeInfoDocument>() { |
| @Override |
| public int compare(ClusterNodeInfoDocument one, ClusterNodeInfoDocument two) { |
| return Integer.compare(one.getClusterId(), two.getClusterId()); |
| } |
| }); |
| if (options.isRaw()) { |
| printRaw(all); |
| } else { |
| print(all, options.isVerbose()); |
| } |
| } catch (Throwable e) { |
| e.printStackTrace(System.err); |
| } |
| } catch (Throwable e) { |
| throw closer.rethrow(e); |
| } finally { |
| closer.close(); |
| } |
| } |
| |
| private static void print(List<ClusterNodeInfoDocument> docs, boolean verbose) { |
| |
| String sId = "Id"; |
| String sState = "State"; |
| String sStarted = "Started"; |
| String sLeaseEnd = "LeaseEnd"; |
| String sRecoveryBy = "RecoveryBy"; |
| String sLeft = "Left"; |
| String sLastRootRev = "LastRootRev"; |
| String sOakVersion = "OakVersion"; |
| |
| long now = System.currentTimeMillis(); |
| |
| List<String> header = new ArrayList<>(); |
| header.addAll(Arrays.asList(new String[] { sId, sState, sStarted, sLeaseEnd, sLeft, sRecoveryBy })); |
| if (verbose) { |
| header.add(sLastRootRev); |
| header.add(sOakVersion); |
| } |
| |
| SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); |
| df.setTimeZone(TimeZone.getTimeZone("UTC")); |
| |
| List<Map<String, String>> body = new ArrayList<>(); |
| |
| for (ClusterNodeInfoDocument c : docs) { |
| |
| long start = c.getStartTime(); |
| long leaseEnd; |
| long left; |
| try { |
| leaseEnd = c.getLeaseEndTime(); |
| left = (leaseEnd - now) / 1000; |
| } catch (Exception ex) { |
| leaseEnd = 0; |
| left = Long.MIN_VALUE; |
| } |
| |
| Map<String, String> e = new HashMap<>(); |
| e.put(sId, Integer.toString(c.getClusterId())); |
| e.put(sState, c.isActive() ? "ACTIVE" : "INACTIVE"); |
| e.put(sStarted, start <= 0 ? "-" : df.format(new Date(start))); |
| e.put(sLeaseEnd, leaseEnd == 0 ? "-" : df.format(new Date(leaseEnd))); |
| e.put(sLeft, (left < -999) ? "-" : (Long.toString(left) + "s")); |
| e.put(sRecoveryBy, |
| c.getRecoveryBy() == null ? (c.isRecoveryNeeded(now) ? "!" : "-") : Long.toString(c.getRecoveryBy())); |
| |
| if (verbose) { |
| e.put(sLastRootRev, c.getLastWrittenRootRev()); |
| Object oakVersion = c.get("oakVersion"); |
| e.put(sOakVersion, oakVersion == null ? "-" : oakVersion.toString()); |
| } |
| body.add(e); |
| } |
| list(System.out, header, body); |
| } |
| |
| /** |
| * A generic method to print a table, choosing column widths automatically |
| * based both on column title and values. |
| * |
| * @param out |
| * output target |
| * @param header |
| * list of column titles |
| * @param body |
| * list of rows, where each row is a map from column title to |
| * value |
| */ |
| private static void list(PrintStream out, List<String> header, List<Map<String, String>> body) { |
| // find column widths |
| Map<String, Integer> widths = new HashMap<>(); |
| for (String h : header) { |
| widths.put(h, h.length()); |
| } |
| for (Map<String, String> m : body) { |
| for (Map.Entry<String, String> e : m.entrySet()) { |
| int current = widths.get(e.getKey()); |
| int thisone = e.getValue().length(); |
| widths.put(e.getKey(), Math.max(current, thisone)); |
| } |
| } |
| StringBuilder sformat = new StringBuilder(); |
| for (String h : header) { |
| if (sformat.length() != 0) { |
| sformat.append(' '); |
| } |
| sformat.append("%" + widths.get(h) + "s"); |
| } |
| String format = sformat.toString(); |
| |
| out.println(String.format(format, header.toArray())); |
| for (Map<String, String> m : body) { |
| List<String> l = new ArrayList<>(); |
| for (String h : header) { |
| l.add(m.get(h)); |
| } |
| out.println(String.format(format, l.toArray())); |
| } |
| } |
| |
| private static void printRaw(Iterable<ClusterNodeInfoDocument> docs) { |
| Map<Object, Object> rawEntries = new HashMap<>(); |
| for (ClusterNodeInfoDocument c : docs) { |
| Map<Object, Object> entries = new HashMap<>(); |
| for (String k : c.keySet()) { |
| entries.put(k, c.get(k)); |
| } |
| rawEntries.put(Integer.toString(c.getClusterId()), entries); |
| } |
| StringBuilder sb = new StringBuilder(); |
| RDBJSONSupport.appendJsonMap(sb, rawEntries); |
| System.out.println(sb); |
| } |
| |
| private static final class ClusterNodesOptions extends Utils.NodeStoreOptions { |
| |
| final OptionSpec<Void> raw; |
| final OptionSpec<Void> verbose; |
| |
| ClusterNodesOptions(String usage) { |
| super(usage); |
| raw = parser.accepts("raw", "List raw entries in JSON format"); |
| verbose = parser.accepts("verbose", "Be more verbose"); |
| } |
| |
| @Override |
| public ClusterNodesOptions parse(String[] args) { |
| super.parse(args); |
| return this; |
| } |
| |
| boolean isRaw() { |
| return options.has(raw); |
| } |
| |
| boolean isVerbose() { |
| return options.has(verbose); |
| } |
| |
| boolean isHelp() { |
| return options.has(help); |
| } |
| } |
| } |