/*
* 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.core.version;
import java.util.Set;
import java.util.HashSet;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import org.apache.jackrabbit.core.HierarchyManager;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
/**
* The JCR Version Manager implementation is split in several classes in order to
* group related methods together.
* <p>
* this class provides methods for the configuration and baselines related operations.
* <p>
* Implementation note: methods starting with "internal" are considered to be
* executed within a "write operations" block.
*/
abstract public class VersionManagerImplConfig extends VersionManagerImplMerge {
/**
* default logger
*/
private static final Logger log = LoggerFactory.getLogger(VersionManagerImplConfig.class);
/**
* Creates a new version manager for the given session
*
* @param context component context of the current session
* @param stateMgr the underlying state manager
* @param hierMgr local hierarchy manager
*/
protected VersionManagerImplConfig(
SessionContext context, UpdatableItemStateManager stateMgr,
HierarchyManager hierMgr) {
super(context, stateMgr, hierMgr);
}
/**
* Restores the versions recorded in the given baseline below the specified
* path.
* @param parent the parent state
* @param name the name of the new node (tree)
* @param baseline the baseline that recorded the versions
* @return the node id of the configuration
* @throws RepositoryException if an error occurs
*/
protected NodeId restore(NodeStateEx parent, Name name,
InternalBaseline baseline)
throws RepositoryException {
// check if nt:configuration exists
NodeId configId = baseline.getConfigurationId();
NodeId rootId = baseline.getConfigurationRootId();
if (stateMgr.hasItemState(rootId)) {
NodeStateEx existing = parent.getNode(rootId);
String msg = "Configuration for the given baseline already exists at: " + safeGetJCRPath(existing);
log.error(msg);
throw new UnsupportedRepositoryOperationException(msg);
}
// find version for configuration root
VersionSet versions = baseline.getBaseVersions();
InternalVersion rootVersion = null;
for (InternalVersion v: versions.versions().values()) {
if (v.getVersionHistory().getVersionableId().equals(rootId)) {
rootVersion = v;
break;
}
}
if (rootVersion == null) {
String msg = "Internal error: supplied baseline has no version for its configuration root.";
log.error(msg);
throw new RepositoryException(msg);
}
// create new node below parent
WriteOperation ops = startWriteOperation();
try {
if (!stateMgr.hasItemState(configId)) {
// create if nt:configuration node is not exists
internalCreateConfiguration(rootId, configId, baseline.getId());
}
NodeStateEx config = parent.getNode(configId);
// create the root node so that the restore works
InternalFrozenNode fn = rootVersion.getFrozenNode();
NodeStateEx state = parent.addNode(name, fn.getFrozenPrimaryType(), fn.getFrozenId());
state.setMixins(fn.getFrozenMixinTypes());
parent.store();
// and finally restore the config and root
internalRestore(config, baseline, null, false);
ops.save();
return configId;
} catch (ItemStateException e) {
throw new RepositoryException(e);
} finally {
ops.close();
}
}
/**
* Creates a new configuration node.
* <p>
* The nt:confguration is stored within the nt:configurations storage using
* the nodeid of the configuration root (rootId) as path.
*
* @param state the node of the workspace configuration
* @return the node id of the created configuration
* @throws RepositoryException if an error occurs
*/
protected NodeId createConfiguration(NodeStateEx state)
throws RepositoryException {
WriteOperation ops = startWriteOperation();
try {
NodeId configId = internalCreateConfiguration(state.getNodeId(), null, null);
// set configuration reference in state
state.setPropertyValue(NameConstants.JCR_CONFIGURATION, InternalValue.create(configId));
state.store();
ops.save();
return configId;
} catch (ItemStateException e) {
throw new RepositoryException(e);
} finally {
ops.close();
}
}
/**
* Creates a new configuration node.
* <p>
* The nt:confguration is stored within the nt:configurations storage using
* the nodeid of the configuration root (rootId) as path.
*
* @param rootId the id of the configuration root node
* @param configId the id of the configuration node
* @param baseLine id of the baseline version or <code>null</code>
* @return the node id of the created configuration
* @throws RepositoryException if an error occurs
*/
private NodeId internalCreateConfiguration(NodeId rootId,
NodeId configId, NodeId baseLine)
throws RepositoryException {
NodeStateEx configRoot = internalGetConfigRoot();
NodeStateEx configParent = InternalVersionManagerBase.getParentNode(
configRoot,
rootId.toString(),
NameConstants.REP_CONFIGURATIONS);
Name name = InternalVersionManagerBase.getName(rootId.toString());
if (configId == null) {
configId = context.getNodeIdFactory().newNodeId();
}
NodeStateEx config = configParent.addNode(name, NameConstants.NT_CONFIGURATION, configId, true);
Set<Name> mix = new HashSet<Name>();
mix.add(NameConstants.REP_VERSION_REFERENCE);
config.setMixins(mix);
config.setPropertyValue(NameConstants.JCR_ROOT, InternalValue.create(rootId));
// init mix:versionable flags
VersionHistoryInfo vh = vMgr.getVersionHistory(session, config.getState(), null);
// and set the base version and history to the config
InternalValue historyId = InternalValue.create(vh.getVersionHistoryId());
InternalValue versionId = InternalValue.create(
baseLine == null ? vh.getRootVersionId() : baseLine);
config.setPropertyValue(NameConstants.JCR_BASEVERSION, versionId);
config.setPropertyValue(NameConstants.JCR_VERSIONHISTORY, historyId);
config.setPropertyValue(NameConstants.JCR_ISCHECKEDOUT, InternalValue.create(true));
config.setPropertyValues(NameConstants.JCR_PREDECESSORS, PropertyType.REFERENCE, new InternalValue[]{versionId});
configParent.store();
return configId;
}
/**
* Returns the root node of the configurations storage located at
* "/jcr:system/jcr:configurations"
*
* @return the root node
* @throws RepositoryException if an error occurs
*/
private NodeStateEx internalGetConfigRoot() throws RepositoryException {
NodeStateEx system = getNodeStateEx(RepositoryImpl.SYSTEM_ROOT_NODE_ID);
NodeStateEx root = system.getNode(NameConstants.JCR_CONFIGURATIONS, 1);
if (root == null) {
root = system.addNode(
NameConstants.JCR_CONFIGURATIONS,
NameConstants.REP_CONFIGURATIONS,
RepositoryImpl.CONFIGURATIONS_NODE_ID, false);
system.store();
}
return root;
}
}