blob: 791a598b71ab9da9f49e568a6302050dd566aecd [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.plugins.migration.version;
import static com.google.common.collect.ImmutableSet.of;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.singleton;
import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
import static org.apache.jackrabbit.JcrConstants.JCR_CREATED;
import static org.apache.jackrabbit.JcrConstants.JCR_ISCHECKEDOUT;
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.JcrConstants.JCR_PREDECESSORS;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY;
import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE;
import static org.apache.jackrabbit.JcrConstants.MIX_REFERENCEABLE;
import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
import static org.apache.jackrabbit.oak.plugins.memory.MultiGenericPropertyState.nameProperty;
import static org.apache.jackrabbit.oak.spi.version.VersionConstants.REP_VERSIONSTORAGE;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Set;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import com.google.common.base.Joiner;
import org.apache.jackrabbit.util.ISO8601;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VersionHistoryUtil {
private static final Logger LOG = LoggerFactory.getLogger(VersionHistoryUtil.class);
public static String getRelativeVersionHistoryPath(String versionableUuid) {
return Joiner.on('/').join(concat(
singleton(""),
getRelativeVersionHistoryPathSegments(versionableUuid),
singleton(versionableUuid)));
}
/**
* Constructs the version history path based on the versionable's UUID.
*
* @param versionStorage below which to look for the version.
* @param versionableUuid The String representation of the versionable's UUID.
* @return The NodeState corresponding to the version history, or {@code null}
* if it does not exist.
*/
static NodeState getVersionHistoryNodeState(NodeState versionStorage, String versionableUuid) {
NodeState historyParent = versionStorage;
for (String segment : getRelativeVersionHistoryPathSegments(versionableUuid)) {
historyParent = historyParent.getChildNode(segment);
}
return historyParent.getChildNode(versionableUuid);
}
public static NodeBuilder getVersionHistoryBuilder(NodeBuilder versionStorage, String versionableUuid) {
NodeBuilder history = versionStorage;
for (String segment : getRelativeVersionHistoryPathSegments(versionableUuid)) {
history = history.getChildNode(segment);
}
return history.getChildNode(versionableUuid);
}
private static List<String> getRelativeVersionHistoryPathSegments(String versionableUuid) {
final List<String> segments = new ArrayList<String>();
for (int i = 0; i < 3; i++) {
segments.add(versionableUuid.substring(i * 2, i * 2 + 2));
}
return segments;
}
public static NodeState getVersionStorage(NodeState root) {
return root.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE);
}
public static NodeBuilder getVersionStorage(NodeBuilder root) {
return root.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE);
}
public static NodeBuilder createVersionStorage(NodeBuilder root) {
NodeBuilder vs = root.child(JCR_SYSTEM).child(JCR_VERSIONSTORAGE);
if (!vs.hasProperty(JCR_PRIMARYTYPE)) {
vs.setProperty(JCR_PRIMARYTYPE, REP_VERSIONSTORAGE, Type.NAME);
}
return vs;
}
public static List<String> getVersionableNodes(NodeState root, NodeState versionStorage, TypePredicate isVersionable, Calendar olderThan) {
List<String> paths = new ArrayList<>();
getVersionableNodes(root, versionStorage, isVersionable, olderThan, PathUtils.ROOT_PATH, paths);
return paths;
}
private static void getVersionableNodes(NodeState node, NodeState versionStorage, TypePredicate isVersionable, Calendar olderThan, String path, List<String> paths) {
if (isVersionable.test(node)) {
if (olderThan == null) {
paths.add(path);
} else {
NodeState versionHistory = getVersionHistoryNodeState(versionStorage, node.getString(JCR_UUID));
Calendar lastModified = getVersionHistoryLastModified(versionHistory);
if (lastModified.before(olderThan)) {
paths.add(path);
}
}
}
for (ChildNodeEntry c : node.getChildNodeEntries()) {
getVersionableNodes(c.getNodeState(), versionStorage, isVersionable, olderThan, PathUtils.concat(path, c.getName()), paths);
}
}
public static Calendar getVersionHistoryLastModified(NodeState versionHistory) {
Calendar youngest = Calendar.getInstance();
youngest.setTimeInMillis(0);
for (final ChildNodeEntry entry : versionHistory.getChildNodeEntries()) {
final NodeState version = entry.getNodeState();
if (version.hasProperty(JCR_CREATED)) {
final Calendar created = ISO8601.parse(version.getProperty(JCR_CREATED).getValue(Type.DATE));
if (created.after(youngest)) {
youngest = created;
}
}
}
return youngest;
}
public static void removeVersionProperties(NodeBuilder versionableBuilder, TypePredicate isReferenceable) {
assert versionableBuilder.exists();
removeMixin(versionableBuilder, MIX_VERSIONABLE);
// we don't know if the UUID is otherwise referenced,
// so make sure the node remains referencable
if (!isReferenceable.test(versionableBuilder.getNodeState())) {
addMixin(versionableBuilder, MIX_REFERENCEABLE);
}
versionableBuilder.removeProperty(JCR_VERSIONHISTORY);
versionableBuilder.removeProperty(JCR_PREDECESSORS);
versionableBuilder.removeProperty(JCR_BASEVERSION);
versionableBuilder.removeProperty(JCR_ISCHECKEDOUT);
}
static void addMixin(NodeBuilder builder, String name) {
if (builder.hasProperty(JCR_MIXINTYPES)) {
final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
if (mixins.add(name)) {
builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
}
} else {
builder.setProperty(nameProperty(JCR_MIXINTYPES, of(name)));
}
}
private static void removeMixin(NodeBuilder builder, String name) {
if (builder.hasProperty(JCR_MIXINTYPES)) {
final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
if (mixins.remove(name)) {
if (mixins.isEmpty()) {
builder.removeProperty(JCR_MIXINTYPES);
} else {
builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
}
}
}
}
public static NodeBuilder removeVersions(NodeState root,
List<String> toRemove) {
NodeBuilder rootBuilder = root.builder();
TypePredicate isReferenceable = new TypePredicate(root, MIX_REFERENCEABLE);
NodeBuilder versionStorage = getVersionStorage(rootBuilder);
for (String p : toRemove) {
LOG.info("Removing version history for {}", p);
NodeBuilder b = getBuilder(rootBuilder, p);
String uuid = b.getString(JCR_UUID);
VersionHistoryUtil.removeVersionProperties(b, isReferenceable);
getVersionHistoryBuilder(versionStorage, uuid).remove();
}
return rootBuilder;
}
private static NodeBuilder getBuilder(NodeBuilder root, String path) {
NodeBuilder builder = root;
for (String e : PathUtils.elements(path)) {
builder = builder.child(e);
}
return builder;
}
}