| /* |
| * 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.plugins.document; |
| |
| import java.util.List; |
| |
| import com.google.common.collect.ComparisonChain; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS; |
| |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * The format version currently in use by the DocumentNodeStore and written |
| * to the underlying DocumentStore. A version {@link #canRead} the current or |
| * older versions. |
| */ |
| public final class FormatVersion implements Comparable<FormatVersion> { |
| |
| /** |
| * A dummy version when none is available. |
| */ |
| static final FormatVersion V0 = new FormatVersion(0, 0, 0); |
| |
| /** |
| * Format version for Oak 1.0. |
| */ |
| static final FormatVersion V1_0 = new FormatVersion(1, 0, 0); |
| |
| /** |
| * Format version for Oak 1.2. |
| * <p> |
| * Changes introduced with this version: |
| * <ul> |
| * <li>_lastRev entries are only updated for implicit changes (OAK-2131)</li> |
| * </ul> |
| */ |
| static final FormatVersion V1_2 = new FormatVersion(1, 2, 0); |
| |
| /** |
| * Format version for Oak 1.4. |
| * <p> |
| * Changes introduced with this version: |
| * <ul> |
| * <li>journalGC in settings collection (OAK-4528)</li> |
| * <li>startTime in clusterNode entries, revision vector in checkpoint (OAK-3646)</li> |
| * <li>discovery lite with clusterView in settings collection (OAK-2844)</li> |
| * </ul> |
| */ |
| static final FormatVersion V1_4 = new FormatVersion(1, 4, 0); |
| |
| /** |
| * Format version for Oak 1.6. |
| * <p> |
| * Changes introduced with this version: |
| * <ul> |
| * <li>bundle nodes into document (OAK-1312)</li> |
| * <li>journal entries with change set summary for JCR observation (OAK-5101)</li> |
| * </ul> |
| */ |
| static final FormatVersion V1_6 = new FormatVersion(1, 6, 0); |
| |
| /** |
| * Format version for Oak 1.8. |
| * <p> |
| * Changes introduced with this version: |
| * <ul> |
| * <li>SplitDocType.DEFAULT_NO_BRANCH (OAK-5869)</li> |
| * <li>journal entries with invalidate-only changes (OAK-5964)</li> |
| * </ul> |
| */ |
| static final FormatVersion V1_8 = new FormatVersion(1, 8, 0); |
| |
| /** |
| * The ID of the document in the settings collection that contains the |
| * version information. |
| */ |
| private static final String VERSION_ID = "version"; |
| |
| /** |
| * @return well known format versions. |
| */ |
| public static Iterable<FormatVersion> values() { |
| return ImmutableList.of(V0, V1_0, V1_2, V1_4, V1_6, V1_8); |
| } |
| |
| /** |
| * Name of the version property. |
| */ |
| private static final String PROP_VERSION = "_v"; |
| |
| private final int major, minor, micro; |
| |
| private FormatVersion(int major, int minor, int micro) { |
| this.major = major; |
| this.minor = minor; |
| this.micro = micro; |
| } |
| |
| /** |
| * Returns {@code true} if {@code this} version can read data written by the |
| * {@code other} version. |
| * |
| * @param other the version the data was written in. |
| * @return {@code true} if this version can read, {@code false} otherwise. |
| */ |
| public boolean canRead(FormatVersion other) { |
| return compareTo(checkNotNull(other)) >= 0; |
| } |
| |
| /** |
| * Reads the {@link FormatVersion} from the given store. This method |
| * returns {@link FormatVersion#V0} if the store currently does not have a |
| * version set. |
| * |
| * @param store the store to read from. |
| * @return the format version of the store. |
| * @throws DocumentStoreException if an error occurs while reading from the |
| * store. |
| */ |
| @NotNull |
| public static FormatVersion versionOf(@NotNull DocumentStore store) |
| throws DocumentStoreException { |
| checkNotNull(store); |
| FormatVersion v = V0; |
| Document d = store.find(SETTINGS, VERSION_ID); |
| if (d != null) { |
| Object p = d.get(PROP_VERSION); |
| if (p != null) { |
| try { |
| v = valueOf(p.toString()); |
| } catch (IllegalArgumentException e) { |
| throw new DocumentStoreException(e); |
| } |
| } |
| } |
| return v; |
| } |
| |
| /** |
| * Writes this version to the given document store. The write operation will |
| * fail with a {@link DocumentStoreException} if the version change is |
| * considered incompatible or cannot be applied for some other reason. This |
| * includes: |
| * <ul> |
| * <li>An attempt to downgrade the existing version</li> |
| * <li>There are active cluster nodes using an existing version</li> |
| * <li>The version was changed concurrently</li> |
| * </ul> |
| * |
| * @param store the document store. |
| * @return {@code true} if the version in the store was updated, |
| * {@code false} otherwise. This method will also return {@code false} |
| * if the version in the store equals this version and now update was |
| * required. |
| * @throws DocumentStoreException if the write operation fails. Reasons |
| * include: 1) an attempt to downgrade the existing version, 2) there |
| * are active cluster nodes using an existing version, 3) the version |
| * was changed concurrently. |
| */ |
| public boolean writeTo(@NotNull DocumentStore store) |
| throws DocumentStoreException { |
| checkNotNull(store); |
| FormatVersion v = versionOf(store); |
| if (v == this) { |
| // already on this version |
| return false; |
| } |
| if (!canRead(v)) { |
| // never downgrade |
| throw unableToWrite("Version " + this + " cannot read " + v); |
| } |
| List<Integer> active = Lists.newArrayList(); |
| for (ClusterNodeInfoDocument d : ClusterNodeInfoDocument.all(store)) { |
| if (d.isActive()) { |
| active.add(d.getClusterId()); |
| } |
| } |
| if (!active.isEmpty() && v != V0) { |
| throw unableToWrite("There are active cluster nodes: " + active); |
| } |
| if (v == V0) { |
| UpdateOp op = new UpdateOp(VERSION_ID, true); |
| op.set(PROP_VERSION, toString()); |
| if (!store.create(SETTINGS, Lists.newArrayList(op))) { |
| throw concurrentUpdate(); |
| } |
| } else { |
| UpdateOp op = new UpdateOp(VERSION_ID, false); |
| op.equals(PROP_VERSION, v.toString()); |
| op.set(PROP_VERSION, toString()); |
| if (store.findAndUpdate(SETTINGS, op) == null) { |
| throw concurrentUpdate(); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns a format version for the given String representation. This method |
| * either returns one of the well known versions or an entirely new version |
| * if the version is not well known. |
| * |
| * @param s the String representation of a format version. |
| * @return the parsed format version. |
| * @throws IllegalArgumentException if the string is malformed. |
| */ |
| public static FormatVersion valueOf(String s) |
| throws IllegalArgumentException { |
| String[] parts = s.split("\\."); |
| if (parts.length != 3) { |
| throw new IllegalArgumentException(s); |
| } |
| int[] elements = new int[parts.length]; |
| for (int i = 0; i < parts.length; i++) { |
| try { |
| elements[i] = Integer.parseInt(parts[i]); |
| } catch (NumberFormatException e) { |
| throw new IllegalArgumentException(s); |
| } |
| } |
| FormatVersion v = new FormatVersion(elements[0], elements[1], elements[2]); |
| for (FormatVersion known : values()) { |
| if (v.equals(known)) { |
| v = known; |
| break; |
| } |
| } |
| return v; |
| } |
| |
| @Override |
| public String toString() { |
| return major + "." + minor + "." + micro; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof FormatVersion |
| && compareTo((FormatVersion) obj) == 0; |
| } |
| |
| @Override |
| public int compareTo(@NotNull FormatVersion other) { |
| checkNotNull(other); |
| return ComparisonChain.start() |
| .compare(major, other.major) |
| .compare(minor, other.minor) |
| .compare(micro, other.micro) |
| .result(); |
| } |
| |
| private static DocumentStoreException concurrentUpdate() { |
| return unableToWrite("Version was updated concurrently"); |
| } |
| |
| private static DocumentStoreException unableToWrite(String reason) { |
| return new DocumentStoreException( |
| "Unable to write format version. " + reason); |
| } |
| } |