/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jcr; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.jcr.RepositoryException; import javax.jcr.nodetype.NodeTypeDefinition; import javax.jcr.query.Query; import org.modeshape.common.collection.Problems; import org.modeshape.common.collection.SimpleProblems; import org.modeshape.common.logging.Logger; import org.modeshape.jcr.JcrRepository.RunningState; import org.modeshape.jcr.cache.CachedNode; import org.modeshape.jcr.cache.ChildReference; import org.modeshape.jcr.cache.ChildReferences; import org.modeshape.jcr.cache.MutableCachedNode; import org.modeshape.jcr.cache.RepositoryCache; import org.modeshape.jcr.cache.SessionCache; import org.modeshape.jcr.query.JcrQuery; import org.modeshape.jcr.value.binary.BinaryStore; import org.modeshape.jcr.value.binary.BinaryStoreException; import org.modeshape.jcr.value.binary.FileSystemBinaryStore; /** * @author Randall Hauch (rhauch@redhat.com) */ public class Upgrades { protected static final Logger LOGGER = Logger.getLogger(Upgrades.class); protected static final int EMPTY_UPGRADES_ID = 0; public static interface Context { /** * Get the repository's running state. * * @return the repository state */ RunningState getRepository(); /** * Get a problems instance which can be used to record failures/warnings/information messages. * * @return a {@link Problems} instance, never null. */ Problems getProblems(); } /** * The standard upgrades for the built-in components and content of ModeShape. */ public static final Upgrades STANDARD_UPGRADES; static { STANDARD_UPGRADES = new Upgrades(ModeShape_3_6_0.INSTANCE, ModeShape_4_0_0_Alpha1.INSTANCE, ModeShape_4_0_0_Beta3.INSTANCE); } private final List<UpgradeOperation> operations = new ArrayList<>(); protected Upgrades( UpgradeOperation... operations ) { int maxId = 0; for (UpgradeOperation op : operations) { assert op.getId() > maxId : "Upgrade operation '" + op + "' has an out-of-order ID ('" + op.getId() + "') that must be greater than all prior upgrades"; maxId = op.getId(); this.operations.add(op); } } /** * Apply any upgrades that are more recent than identified by the last upgraded identifier. * * @param lastId the identifier of the last upgrade that was successfully run against the repository * @param resources the resources for the repository * @return the identifier of the last upgrade applied to the repository; may be the same or greater than {@code lastId} */ public final int applyUpgradesSince( int lastId, Context resources ) { int lastUpgradeId = lastId; for (UpgradeOperation op : operations) { if (op.getId() <= lastId) continue; LOGGER.debug("Upgrade {0}: starting", op); op.apply(resources); LOGGER.debug("Upgrade {0}: complete", op); lastUpgradeId = op.getId(); } return lastUpgradeId; } /** * Determine if an {@link #applyUpgradesSince(int, Context) upgrade} is required given the identifier of the last known * upgrade, which is compared to the identifiers of the registered upgrades. * * @param lastId the identifier of the last known/successful upgrade previously applied to the repository * @return true if this contains at least one upgrade that should be applied to the repository, or false otherwise */ public final boolean isUpgradeRequired( int lastId ) { return getLatestAvailableUpgradeId() > lastId; } /** * Get the identifier of the latest upgrade known to this object. * * @return the latest identifier; 0 if there are no upgrades in this object, or positive number */ public final int getLatestAvailableUpgradeId() { return operations.isEmpty() ? EMPTY_UPGRADES_ID : operations.get(operations.size() - 1).getId(); } protected static abstract class UpgradeOperation { private final int id; protected UpgradeOperation( int id ) { assert id > 0 : "An upgrade operation's identifier must be positive"; this.id = id; } /** * Get the identifier for this upgrade. This should be unique and sortable with respect to all other identifiers. * * @return this upgrade's identifier; always positive */ public int getId() { return id; } /** * Apply this upgrade operation to the supplied running repository. * * @param resources the resources for the repository; never null */ public abstract void apply( Context resources ); @Override public String toString() { return getClass().getSimpleName() + "(step " + id + ")"; } } /** * Upgrade operation handling moving to ModeShape 3.6.0.Final. This consists of:making sure the internal node types are * <ul> * <li>updated to reflect the ACL and mode:lock changes</li> * <li>updating any potential existing lock to the updated mode:lock node type</li> * </ul> */ protected static class ModeShape_3_6_0 extends UpgradeOperation { protected static final UpgradeOperation INSTANCE = new ModeShape_3_6_0(); protected ModeShape_3_6_0() { super(1); } @Override public void apply( Context resources ) { LOGGER.info(JcrI18n.upgrade3_6_0Running); RunningState repository = resources.getRepository(); if (updateInternalNodeTypes(repository)) { updateLocks(repository); } } @SuppressWarnings( "deprecation" ) private void updateLocks( RunningState repository ) { try { SessionCache systemSession = repository.createSystemSession(repository.context(), false); SystemContent systemContent = new SystemContent(systemSession); CachedNode locksNode = systemContent.locksNode(); if (locksNode == null) { return; } ChildReferences childReferences = locksNode.getChildReferences(systemSession); if (childReferences.isEmpty()) { return; } for (ChildReference ref : childReferences) { MutableCachedNode lockNode = systemSession.mutable(ref.getKey()); // remove properties that belong to the old (invalid) node type lockNode.removeProperty(systemSession, ModeShapeLexicon.LOCKED_KEY); lockNode.removeProperty(systemSession, ModeShapeLexicon.SESSION_SCOPE); lockNode.removeProperty(systemSession, ModeShapeLexicon.IS_DEEP); } systemContent.save(); } catch (Exception e) { LOGGER.error(e, JcrI18n.upgrade3_6_0CannotUpdateLocks, e.getMessage()); } } private boolean updateInternalNodeTypes( RunningState repository ) { CndImporter importer = new CndImporter(repository.context()); SimpleProblems problems = new SimpleProblems(); try { importer.importFrom(getClass().getClassLoader().getResourceAsStream(CndImporter.MODESHAPE_BUILT_INS), problems, null); if (!problems.isEmpty()) { LOGGER.error(JcrI18n.upgrade3_6_0CannotUpdateNodeTypes, problems.toString()); return false; } List<NodeTypeDefinition> nodeTypeDefinitions = new ArrayList<NodeTypeDefinition>( importer.getNodeTypeDefinitions()); for (Iterator<NodeTypeDefinition> nodeTypeDefinitionIterator = nodeTypeDefinitions.iterator(); nodeTypeDefinitionIterator.hasNext();) { NodeTypeDefinition nodeTypeDefinition = nodeTypeDefinitionIterator.next(); String name = nodeTypeDefinition.getName(); // keep only the exact types that we know have changed to keep the overhead to a minimum if (ModeShapeLexicon.ACCESS_CONTROLLABLE_STRING.equalsIgnoreCase(name) || ModeShapeLexicon.ACCESS_LIST_NODE_TYPE_STRING.equalsIgnoreCase(name) || ModeShapeLexicon.PERMISSION.getString().equalsIgnoreCase(name) || ModeShapeLexicon.LOCK.getString().equalsIgnoreCase(name)) { continue; } nodeTypeDefinitionIterator.remove(); } repository.nodeTypeManager().registerNodeTypes(nodeTypeDefinitions, false, false, true); } catch (Exception e) { LOGGER.error(e, JcrI18n.upgrade3_6_0CannotUpdateNodeTypes, e.getMessage()); return false; } return true; } } /** * Upgrade operation handling moving to ModeShape 4.0.0.Alpha1. This consists of making sure that the access control metadata * information is correctly stored in the repository metadata and that the {@link ModeShapeLexicon#ACL_COUNT} property * correctly reflects this. */ protected static class ModeShape_4_0_0_Alpha1 extends UpgradeOperation { protected static final UpgradeOperation INSTANCE = new ModeShape_4_0_0_Alpha1(); protected ModeShape_4_0_0_Alpha1() { super(401); } @Override public void apply( Context resources ) { LOGGER.info(JcrI18n.upgrade4_0_0_Alpha1_Running); RunningState runningState = resources.getRepository(); RepositoryCache repositoryCache = runningState.repositoryCache(); try { long nodesWithAccessControl = 0; for (String workspaceName : repositoryCache.getWorkspaceNames()) { JcrSession session = runningState.loginInternalSession(workspaceName); try { JcrQueryManager queryManager = session.getWorkspace().getQueryManager(); Query query = queryManager.createQuery( "select [jcr:name] from [" + ModeShapeLexicon.ACCESS_CONTROLLABLE_STRING + "]", JcrQuery.JCR_SQL2); nodesWithAccessControl = query.execute().getRows().getSize(); } finally { session.logout(); } } if (nodesWithAccessControl == 0) { repositoryCache.setAccessControlEnabled(false); } ExecutionContext context = runningState.context(); SessionCache systemSession = runningState.createSystemSession(context, false); SystemContent systemContent = new SystemContent(systemSession); MutableCachedNode systemNode = systemContent.mutableSystemNode(); systemNode.setProperty(systemSession, context.getPropertyFactory().create(ModeShapeLexicon.ACL_COUNT, nodesWithAccessControl)); systemSession.save(); } catch (RepositoryException e) { LOGGER.error(e, JcrI18n.upgrade4_0_0_Alpha1_Failed, e.getMessage()); } } } /** * Upgrade operation handling moving to ModeShape 4.0.0.Beta3. This consists of upgrading the {@link org.modeshape.jcr.value.binary.FileSystemBinaryStore} * (if one is used) and changing the storage mechanism of the unused files (see MODE-2302). */ protected static class ModeShape_4_0_0_Beta3 extends UpgradeOperation { protected static final UpgradeOperation INSTANCE = new ModeShape_4_0_0_Beta3(); protected ModeShape_4_0_0_Beta3() { super(403); } @Override public void apply( Context resources ) { LOGGER.info(JcrI18n.upgrade4_0_0_Beta3_Running); RunningState runningState = resources.getRepository(); BinaryStore binaryStore = runningState.binaryStore(); try { if (binaryStore instanceof FileSystemBinaryStore) { ((FileSystemBinaryStore)binaryStore).upgradeTrashContentFormat(); } } catch (BinaryStoreException e) { LOGGER.error(e, JcrI18n.upgrade4_0_0_Beta3_Failed, e.getMessage()); } } } }