blob: a3edf86365381af418b42429b64aa12dcc0624fd [file] [log] [blame]
/*
* 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.spi.cluster;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.UUID;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to manage a unique cluster/repository id for the cluster.
*/
public class ClusterRepositoryInfo {
private static final Logger log = LoggerFactory.getLogger(ClusterRepositoryInfo.class);
public static final String OAK_CLUSTERID_REPOSITORY_DESCRIPTOR_KEY = "oak.clusterid";
public static final String CLUSTER_CONFIG_NODE = ":clusterConfig";
public static final String CLUSTER_ID_PROP = ":clusterId";
private ClusterRepositoryInfo() {
}
/**
* Gets the {# CLUSTER_ID_PROP} if available, if it doesn't it
* creates it and returns the newly created one (or if that
* happened concurrently and another cluster instance won,
* return that one)
* <p>
* Note that this method doesn't require synchronization as
* concurrent execution within the VM would be covered
* within NodeStore's merge and result in a conflict for
* one of the two threads - in which case the looser would
* re-read and find the clusterId set.
*
* @param store the NodeStore instance
* @return the persistent clusterId
*/
@Nullable
public static String getOrCreateId(@NotNull NodeStore store) {
checkNotNull(store, "store is null");
// first try to read an existing clusterId
NodeState root = store.getRoot();
NodeState node = root.getChildNode(CLUSTER_CONFIG_NODE);
if (node.exists() && node.hasProperty(CLUSTER_ID_PROP)) {
// clusterId is set - this is the normal case
return node.getProperty(CLUSTER_ID_PROP).getValue(Type.STRING);
}
// otherwise either the config node or the property doesn't exist.
// then try to create it - but since this could be executed concurrently
// in a cluster, it might result in a conflict. in that case, re-read
// the node
NodeBuilder builder = root.builder();
// choose a new random clusterId
String newRandomClusterId = UUID.randomUUID().toString();
builder.child(CLUSTER_CONFIG_NODE).setProperty(CLUSTER_ID_PROP, newRandomClusterId);
try {
store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
// great, we were able to create it, all good.
log.info("getOrCreateId: created a new clusterId=" + newRandomClusterId);
return newRandomClusterId;
} catch (CommitFailedException e) {
// if we can't commit, then another instance in the cluster
// set the clusterId concurrently. in which case we should now
// see that one
root = store.getRoot();
node = root.getChildNode(CLUSTER_CONFIG_NODE);
if (node.exists() && node.hasProperty(CLUSTER_ID_PROP)) {
// clusterId is set - this is the normal case
return node.getProperty(CLUSTER_ID_PROP).getValue(Type.STRING);
}
// this should not happen
String path = "/" + CLUSTER_CONFIG_NODE + "/" + CLUSTER_ID_PROP;
log.error("getOrCreateId: both setting and then reading of " + path + "failed");
throw new IllegalStateException("Both setting and then reading of " + path + " failed");
}
}
@Nullable
public static String getId(@NotNull NodeStore store) {
checkNotNull(store, "store is null");
// first try to read an existing clusterId
NodeState root = store.getRoot();
NodeState node = root.getChildNode(CLUSTER_CONFIG_NODE);
if (node.exists() && node.hasProperty(CLUSTER_ID_PROP)) {
return node.getProperty(CLUSTER_ID_PROP).getValue(Type.STRING);
}
return null;
}
}