/*
* 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;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.core.lock.LockManagerImpl;
import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.persistence.PersistenceCopier;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tool for backing up or migrating the entire contents (workspaces,
* version histories, namespaces, node types, etc.) of a repository to
* a new repository. The target repository (if it exists) is overwritten.
* <p>
* No cluster journal records are written in the target repository. If the
* target repository is clustered, it should be the only node in the cluster.
* <p>
* The target repository needs to be fully reindexed after the copy operation.
* The static copy() methods will remove the target search index folders from
* their default locations to trigger automatic reindexing when the repository
* is next started.
*
* @since Apache Jackrabbit 1.6
*/
public class RepositoryCopier {
/**
* Logger instance
*/
private static final Logger logger =
LoggerFactory.getLogger(RepositoryCopier.class);
/**
* Source repository.
*/
private final RepositoryImpl source;
/**
* Target repository.
*/
private final RepositoryImpl target;
/**
* Copies the contents of the repository in the given source directory
* to a repository in the given target directory.
*
* @param source source repository directory
* @param target target repository directory
* @throws RepositoryException if the copy operation fails
* @throws IOException if the target repository can not be initialized
*/
public static void copy(File source, File target)
throws RepositoryException, IOException {
copy(RepositoryConfig.create(source), RepositoryConfig.install(target));
}
/**
* Copies the contents of the repository with the given configuration
* to a repository in the given target directory.
*
* @param source source repository configuration
* @param target target repository directory
* @throws RepositoryException if the copy operation fails
* @throws IOException if the target repository can not be initialized
*/
public static void copy(RepositoryConfig source, File target)
throws RepositoryException, IOException {
copy(source, RepositoryConfig.install(target));
}
/**
* Copies the contents of the source repository with the given
* configuration to a target repository with the given configuration.
*
* @param source source repository configuration
* @param target target repository directory
* @throws RepositoryException if the copy operation fails
*/
public static void copy(RepositoryConfig source, RepositoryConfig target)
throws RepositoryException {
RepositoryImpl repository = RepositoryImpl.create(source);
try {
copy(repository, target);
} finally {
repository.shutdown();
}
}
/**
* Copies the contents of the given source repository to a repository in
* the given target directory.
* <p>
* The source repository <strong>must not be modified</strong> while
* the copy operation is running to avoid an inconsistent copy.
*
* @param source source repository directory
* @param target target repository directory
* @throws RepositoryException if the copy operation fails
* @throws IOException if the target repository can not be initialized
*/
public static void copy(RepositoryImpl source, File target)
throws RepositoryException, IOException {
copy(source, RepositoryConfig.install(target));
}
/**
* Copies the contents of the given source repository to a target
* repository with the given configuration.
* <p>
* The source repository <strong>must not be modified</strong> while
* the copy operation is running to avoid an inconsistent copy.
*
* @param source source repository directory
* @param target target repository directory
* @throws RepositoryException if the copy operation fails
* @throws IOException if the target repository can not be initialized
*/
public static void copy(RepositoryImpl source, RepositoryConfig target)
throws RepositoryException {
RepositoryImpl repository = RepositoryImpl.create(target);
try {
new RepositoryCopier(source, repository).copy();
} finally {
repository.shutdown();
}
// Remove index directories to force re-indexing on next startup
// TODO: There should be a cleaner way to do this
File targetDir = new File(target.getHomeDir());
File repoDir = new File(targetDir, "repository");
FileUtils.deleteQuietly(new File(repoDir, "index"));
File[] workspaces = new File(targetDir, "workspaces").listFiles();
if (workspaces != null) {
for (File workspace : workspaces) {
FileUtils.deleteQuietly(new File(workspace, "index"));
}
}
}
/**
* Creates a tool for copying the full contents of the source repository
* to the given target repository. Any existing content in the target
* repository will be overwritten.
*
* @param source source repository
* @param target target repository
*/
public RepositoryCopier(RepositoryImpl source, RepositoryImpl target) {
this.source = source;
this.target = target;
}
/**
* Copies the full content from the source to the target repository.
* <p>
* The source repository <strong>must not be modified</strong> while
* the copy operation is running to avoid an inconsistent copy.
* <p>
* This method leaves the search indexes of the target repository in
* an
* Note that both the source and the target repository must be closed
* during the copy operation as this method requires exclusive access
* to the repositories.
*
* @throws RepositoryException if the copy operation fails
*/
public void copy() throws RepositoryException {
logger.info(
"Copying repository content from {} to {}",
source.repConfig.getHomeDir(),
target.repConfig.getHomeDir());
try {
copyNamespaces();
copyNodeTypes();
copyVersionStore();
copyWorkspaces();
} catch (Exception e) {
throw new RepositoryException("Failed to copy content", e);
}
}
private void copyNamespaces() throws RepositoryException {
NamespaceRegistry sourceRegistry = source.getNamespaceRegistry();
NamespaceRegistry targetRegistry = target.getNamespaceRegistry();
logger.info("Copying registered namespaces");
Collection<String> existing = Arrays.asList(targetRegistry.getURIs());
for (String uri : sourceRegistry.getURIs()) {
if (!existing.contains(uri)) {
// TODO: what if the prefix is already taken?
targetRegistry.registerNamespace(
sourceRegistry.getPrefix(uri), uri);
}
}
}
private void copyNodeTypes() throws RepositoryException {
NodeTypeRegistry sourceRegistry = source.getNodeTypeRegistry();
NodeTypeRegistry targetRegistry = target.getNodeTypeRegistry();
logger.info("Copying registered node types");
Collection<Name> existing =
Arrays.asList(targetRegistry.getRegisteredNodeTypes());
Collection<QNodeTypeDefinition> register = new ArrayList<QNodeTypeDefinition>();
for (Name name : sourceRegistry.getRegisteredNodeTypes()) {
// TODO: what about modified node types?
if (!existing.contains(name)) {
register.add(sourceRegistry.getNodeTypeDef(name));
}
}
try {
targetRegistry.registerNodeTypes(register);
} catch (InvalidNodeTypeDefException e) {
throw new RepositoryException("Unable to copy node types", e);
}
}
private void copyVersionStore() throws RepositoryException {
logger.info("Copying version histories");
PersistenceCopier copier = new PersistenceCopier(
source.getVersionManagerImpl().getPersistenceManager(),
target.getVersionManagerImpl().getPersistenceManager(),
target.getDataStore());
copier.copy(RepositoryImpl.VERSION_STORAGE_NODE_ID);
copier.copy(RepositoryImpl.ACTIVITIES_NODE_ID);
}
private void copyWorkspaces() throws RepositoryException {
Collection<String> existing = Arrays.asList(target.getWorkspaceNames());
for (String name : source.getWorkspaceNames()) {
logger.info("Copying workspace {}" , name);
if (!existing.contains(name)) {
target.createWorkspace(name);
}
// Copy all the workspace content
PersistenceCopier copier = new PersistenceCopier(
source.getWorkspaceInfo(name).getPersistenceManager(),
target.getWorkspaceInfo(name).getPersistenceManager(),
target.getDataStore());
copier.excludeNode(RepositoryImpl.SYSTEM_ROOT_NODE_ID);
copier.copy(RepositoryImpl.ROOT_NODE_ID);
// Copy all the active open-scoped locks
LockManagerImpl sourceLockManager = source.getLockManager(name);
LockManagerImpl targetLockManager = target.getLockManager(name);
targetLockManager.copyOpenScopedLocksFrom(sourceLockManager);
}
}
}