Utils { public static void checkRevisionAge(DocumentStore store, ClusterNodeInfo info, Clock clock) throws DocumentStoreException { NodeDocument root = store.find(Collection.NODES, getIdFromPath(Path.ROOT)); if (root == null) { return; } int clusterId = info.getId(); Revision rev = root.getLastRev().get(clusterId); if (rev == null) { return; } long now = clock.getTime(); if (rev.getTimestamp() > now) { String msg = String.format("Cluster id %d has a _lastRev %s (%s) " + "newer than current time %s. Please check system time on " + "cluster nodes.", clusterId, rev.toString(), timestampToString(rev.getTimestamp()), timestampToString(now)); throw new DocumentStoreException(msg); } }  static int pathDepth(String path); static int getIdDepth(Path path); @SuppressWarnings("unchecked") static int estimateMemoryUsage(Map<?, Object> map); static String escapePropertyName(String propertyName); static String unescapePropertyName(String key); static boolean isPropertyName(String key); static String getIdFromPath(@NotNull String path); static String getIdFromPath(@NotNull Path path); static StringBuilder encodeHexString(byte[] data, StringBuilder sb); @Nullable static String getParentId(String id); static boolean isLongPath(Path path); static boolean isIdFromLongPath(String id); static String getPathFromId(String id); static int getDepthFromId(String id); static Path getPreviousPathFor(Path path, Revision r, int height); static String getPreviousIdFor(Path path, Revision r, int height); static boolean isPreviousDocId(String id); static boolean isLeafPreviousDocId(String id); static void deepCopyMap(Map<K, Object> source, Map<K, Object> target); static String getKeyLowerLimit(Path path); static String getKeyUpperLimit(Path path); @Nullable static String getParentIdFromLowerLimit(String fromKey); static boolean isCommitted(@Nullable String tag); @NotNull static Revision resolveCommitRevision(@NotNull Revision rev,
                                                 @NotNull String tag); static void closeIfCloseable(Object obj); static String timestampToString(long timestamp); @Nullable static Revision max(@Nullable Revision a, @Nullable Revision b); @Nullable static Revision max(@Nullable Revision a,
                               @Nullable Revision b,
                               @NotNull Comparator<Revision> c); @Nullable static Revision min(@Nullable Revision a, @Nullable Revision b); @Nullable static Revision min(@Nullable Revision a,
                               @Nullable Revision b,
                               @NotNull Comparator<Revision> c); static Iterable<NodeDocument> getAllDocuments(final DocumentStore store); @NotNull static NodeDocument getRootDocument(@NotNull DocumentStore store); static Iterable<NodeDocument> getSelectedDocuments(
            DocumentStore store, String indexedProperty, long startValue, int batchSize); static Iterable<NodeDocument> getSelectedDocuments(
            DocumentStore store, String indexedProperty, long startValue); static boolean isHiddenPath(@NotNull String path); static Iterable<StringValue> asStringValueIterable(
            @NotNull Iterable<String> values); static Iterable<String> pathToId(@NotNull Iterable<String> paths); static long getMaxExternalTimestamp(Iterable<Revision> revisions,
                                               int localClusterId); static Long asLong(@Nullable Number n); @NotNull static RevisionVector getStartRevisions(@NotNull Iterable<ClusterNodeInfoDocument> clusterNodes); static long getMinTimestampForDiff(@NotNull RevisionVector fromRev,
                                              @NotNull RevisionVector toRev,
                                              @NotNull RevisionVector minRevisions); static boolean isGreaterOrEquals(@NotNull RevisionVector a,
                                            @NotNull RevisionVector b); static boolean isLocalChange(@NotNull RevisionVector from,
                                        @NotNull RevisionVector to,
                                        int clusterId); static CloseableIterable<T> abortingIterable(Iterable<T> iterable,
                                                            Predicate<T> p); static void alignWithExternalRevisions(@NotNull NodeDocument rootDoc,
                                                  @NotNull Clock clock,
                                                  int clusterId); static void joinQuietly(Thread... threads); static String getModuleVersion(); static void checkRevisionAge(DocumentStore store,
                                        ClusterNodeInfo info,
                                        Clock clock); static final int PATH_SHORT; static final int PATH_LONG; static final int NODE_NAME_LIMIT; static final Predicate<String> PROPERTY_OR_DELETED; }
@Test public void checkRevisionAge() throws Exception { DocumentStore store = new MemoryDocumentStore(); ClusterNodeInfo info = mock(ClusterNodeInfo.class); when(info.getId()).thenReturn(2); Clock clock = new Clock.Virtual(); clock.waitUntil(System.currentTimeMillis()); Utils.checkRevisionAge(store, info, clock); UpdateOp op = new UpdateOp(Utils.getIdFromPath("/"), true); NodeDocument.setLastRev(op, new Revision(clock.getTime(), 0, 1)); assertTrue(store.create(Collection.NODES, Collections.singletonList(op))); Utils.checkRevisionAge(store, info, clock); long lastRevTime = clock.getTime(); op = new UpdateOp(Utils.getIdFromPath("/"), false); NodeDocument.setLastRev(op, new Revision(lastRevTime, 0, 2)); assertNotNull(store.findAndUpdate(Collection.NODES, op)); Utils.checkRevisionAge(store, info, clock); clock = new Clock.Virtual(); clock.waitUntil(lastRevTime - 1000); try { Utils.checkRevisionAge(store, info, clock); fail("must fail with DocumentStoreException"); } catch (DocumentStoreException e) { assertThat(e.getMessage(), containsString("newer than current time")); } }