Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.jahia.services.content.impl.jackrabbit; import java.io.*; import java.sql.SQLException; import java.util.List; import javax.jcr.RepositoryException; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.core.JahiaRepositoryCopier; import org.apache.jackrabbit.core.JahiaRepositoryImpl; import org.apache.jackrabbit.core.RepositoryCopier; import org.apache.jackrabbit.core.RepositoryImpl; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.jahia.services.content.JCRContentUtils; import org.jahia.settings.SettingsBean; import org.jahia.utils.DatabaseUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.jdom.xpath.XPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A utility tool to automatically migrate an existing Jackrabbit repository from BLOB storage to a DataStore one, if required. * * @author Sergiy Shyrkov */ public class RepositoryMigrator { private static final Logger logger = LoggerFactory.getLogger(RepositoryMigrator.class); private static Document readConfig(File cfgFile) throws JDOMException, IOException { SAXBuilder saxBuilder = new SAXBuilder(false); saxBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); return saxBuilder.build(cfgFile); } @SuppressWarnings("unchecked") private static void removeCopyPrefix(File sourceConfigFile, File targetConfigFile) throws JDOMException, IOException { Document doc = readConfig(sourceConfigFile); Element repo = doc.getRootElement(); // change back table prefix for (Element param : (List<Element>) XPath.selectNodes(repo, "//param[@name=\"schemaObjectPrefix\"]")) { String prefix = param.getAttributeValue("value"); if (prefix.startsWith("COPY_")) { param.setAttribute("value", StringUtils.substringAfter(prefix, "COPY_")); } } writeToFile(doc, targetConfigFile); logger.info("Removed 'COPY_' prefix in file {} and stored result in {}", sourceConfigFile, targetConfigFile); } @SuppressWarnings("unchecked") private static void removeElements(Element root, String xpath) throws JDOMException { for (Element param : (List<Element>) XPath.selectNodes(root, xpath)) { param.getParent().removeContent(param); } } private static void writeToFile(Document doc, File file) throws IOException { Format customFormat = Format.getPrettyFormat(); customFormat.setLineSeparator(System.getProperty("line.separator")); XMLOutputter xmlOutputter = new XMLOutputter(customFormat); FileWriter out = null; try { out = new FileWriter(file); xmlOutputter.output(doc, out); } finally { IOUtils.closeQuietly(out); } } private File configFile; private boolean keepBackup; private boolean performMigrationToDataStoreIfNeeded; private File repoHome; private File scriptsDir; private SettingsBean settingsBean; private File targetConfigFile; RepositoryMigrator(File configFile, File repoHome, File targetConfigFile, boolean performMigrationToDataStoreIfNeeded) { super(); this.configFile = configFile; this.repoHome = repoHome; this.targetConfigFile = targetConfigFile; this.performMigrationToDataStoreIfNeeded = performMigrationToDataStoreIfNeeded; } @SuppressWarnings("unchecked") private File createTempConfigFile() throws JDOMException, IOException { Document doc = readConfig(targetConfigFile != null ? targetConfigFile : configFile); Element repo = doc.getRootElement(); Namespace ns = repo.getNamespace(); // prefix table names for (Element param : (List<Element>) XPath.selectNodes(repo, "//param[@name=\"schemaObjectPrefix\"]")) { param.setAttribute("value", "COPY_" + param.getAttributeValue("value")); } if (targetConfigFile == null) { // we are migrating from 6.5 BLOB store // do we use DB store for binaries? Element param = (Element) XPath.selectSingleNode(repo, "//param[@name=\"externalBLOBs\"]"); boolean useDatabaseDataStore = param != null && !Boolean.valueOf(param.getAttributeValue("value")); // remove externalBLOBs params removeElements(repo, "//param[@name=\"externalBLOBs\"]"); // remove supportHighlighting param from global SearchIndex element removeElements(repo, "/Repository/SearchIndex/param[@name=\"supportHighlighting\"]"); if (XPath.selectSingleNode(repo, "/Repository/SearchIndex/param[@name=\"indexingConfiguration\"]") == null) { // add separate indexing configuration for global index Element searchIndex = (Element) XPath.selectSingleNode(repo, "/Repository/SearchIndex"); if (searchIndex != null) { searchIndex.addContent(new Element("param", ns).setAttribute("name", "indexingConfiguration") .setAttribute("value", "${rep.home}/indexing_configuration_version.xml")); } } if (XPath.selectSingleNode(repo, "/Repository/DataStore") == null) { // add datastore element Element store = new Element("DataStore", ns); if (useDatabaseDataStore) { store.setAttribute("class", "org.apache.jackrabbit.core.data.db.DbDataStore"); store.addContent(new Element("param").setAttribute("name", "dataSourceName") .setAttribute("value", "jahiaDS")); store.addContent(new Element("param").setAttribute("name", "schemaObjectPrefix") .setAttribute("value", "COPY_JR_")); store.addContent(new Element("param").setAttribute("name", "schemaCheckEnabled") .setAttribute("value", "false")); store.addContent(new Element("param").setAttribute("name", "copyWhenReading") .setAttribute("value", "true")); store.addContent(new Element("param").setAttribute("name", "minRecordLength") .setAttribute("value", "1024")); } else { store.setAttribute("class", "org.apache.jackrabbit.core.data.FileDataStore"); store.addContent(new Element("param").setAttribute("name", "minRecordLength") .setAttribute("value", "1024")); store.addContent(new Element("param").setAttribute("name", "path").setAttribute("value", "${rep.home}/datastore")); } repo.addContent(store); } } File tempConfigFile = new File(configFile.getParentFile(), "repository-migration-temp.xml"); writeToFile(doc, tempConfigFile); logger.info("Created temporary repository configuration for migration at {}", tempConfigFile); return tempConfigFile; } private File createTempRepoHome() throws IOException { File repoHomeCopy = new File(repoHome.getParentFile(), "repository-migration"); repoHomeCopy.mkdir(); File toCopy = new File(repoHome, "indexing_configuration.xml"); if (toCopy.exists()) { FileUtils.copyFileToDirectory(toCopy, repoHomeCopy); } toCopy = new File(repoHome, "indexing_configuration_version.xml"); if (toCopy.exists()) { FileUtils.copyFileToDirectory(toCopy, repoHomeCopy); } logger.info("Using folder {} for migrated repository", repoHomeCopy); return repoHomeCopy; } private void dbExecute(String fileName, String description) throws IOException, SQLException { DatabaseUtils.executeScript(new FileReader(new File(scriptsDir, fileName))); logger.info(description); } private void dbInitSettings() throws IOException, JDOMException { settingsBean = SettingsBean.getInstance(); scriptsDir = new File(settingsBean.getJahiaDatabaseScriptsPath(), "sql/migration/" + getDbType()); logger.info("Migration SQL scripts will be looked up in folder {}", scriptsDir); } private String getDbType() throws JDOMException, IOException { Document doc = readConfig(configFile); Element repo = doc.getRootElement(); String type = ((Element) XPath.selectSingleNode(repo, "/Repository/DataSources/DataSource/param[@name=\"databaseType\"]")).getAttributeValue("value"); return StringUtils.isEmpty(type) || "default".equals(type) ? "derby" : type; } void migrate() { try { JahiaRepositoryConfig sourceCfg = performMigrationToDataStoreIfNeeded ? new JahiaRepositoryConfig(RepositoryConfig.create(configFile.toString(), repoHome.toString())) : null; if (targetConfigFile != null || performMigrationToDataStoreIfNeeded && sourceCfg.getDataStore() == null) { long timer = System.currentTimeMillis(); if (targetConfigFile != null) { logger.info("Will perform repository migration using target configuration file {}", targetConfigFile); } else { logger.info("Will perform repository migration from BLOB store to DataStore"); } boolean clusterActivated = Boolean.getBoolean("cluster.activated"); if (clusterActivated) { // deactivate the cluster for the time of migration System.setProperty("cluster.activated", "false"); sourceCfg = null; // reload the configuration } keepBackup = Boolean.getBoolean("jahia.jackrabbit.backupRepositoryByMigration"); try { dbInitSettings(); dbExecute("jackrabbit-migration-1-create-temp-tables.sql", "Temporary DB tables created"); File tempRepoHome = createTempRepoHome(); File tempConfigFile = createTempConfigFile(); if (sourceCfg == null) { sourceCfg = new JahiaRepositoryConfig( RepositoryConfig.create(configFile.toString(), repoHome.toString())); } JahiaRepositoryConfig targetCfg = new JahiaRepositoryConfig( RepositoryConfig.create(tempConfigFile.toString(), tempRepoHome.toString())); performMigration(sourceCfg, targetCfg); JCRContentUtils.deleteJackrabbitIndexes(repoHome); JCRContentUtils.deleteJackrabbitIndexes(tempRepoHome); // swap configuration with the target one File target = new File(tempConfigFile.getParentFile(), "repository-migration-target.xml"); removeCopyPrefix(tempConfigFile, target); logger.info("Created target repository configuration after migration at {}", target); if (keepBackup) { File configBackup = new File(configFile.getParentFile(), "repository-original.xml"); FileUtils.copyFile(configFile, configBackup); logger.info("Backup original configuration to {}", configBackup); } FileUtils.copyFile(target, configFile); logger.info("Replaced original repository.xml with the target one"); if (!keepBackup) { FileUtils.deleteQuietly(tempConfigFile); FileUtils.deleteQuietly(target); } File workspaceConfig = new File(tempRepoHome, "workspaces/default/workspace.xml"); removeCopyPrefix(workspaceConfig, workspaceConfig); workspaceConfig = new File(tempRepoHome, "workspaces/live/workspace.xml"); removeCopyPrefix(workspaceConfig, workspaceConfig); dbExecute("jackrabbit-migration-2-drop-original-tables.sql", "Original DB tables dropped"); dbExecute("jackrabbit-migration-3-rename-temp-tables.sql", "Temporary DB tables renamed"); swapRepositoryHome(tempRepoHome); logger.info("Complete repository migration took {} ms", (System.currentTimeMillis() - timer)); } catch (Exception e) { logger.warn( "Unable to perform migration from BLOB store to DataStore. Cause: " + e.getMessage(), e); } finally { if (clusterActivated) { // activate the cluster back System.setProperty("cluster.activated", "true"); } } } } catch (Exception e) { logger.warn("Unable to check the source repository configuration." + " Skip DataStore migration check.", e); } } private void performMigration(JahiaRepositoryConfig sourceCfg, JahiaRepositoryConfig targetCfg) throws RepositoryException { logger.info("Start migrating repository..."); long globalTimer = System.currentTimeMillis(); RepositoryImpl source = JahiaRepositoryImpl.create(sourceCfg); RepositoryImpl target = JahiaRepositoryImpl.create(targetCfg); int batchSize = Integer.getInteger("jahia.jackrabbit.persistenceCopierBatchSize", 500); try { if (batchSize <= 1) { new RepositoryCopier(source, target).copy(); } else { new JahiaRepositoryCopier(source, target, batchSize).copy(); } } finally { target.shutdown(); source.shutdown(); } logger.info("Repository data migrated in {} ms", (System.currentTimeMillis() - globalTimer)); } private void swapRepositoryHome(File repoHomeCopy) throws IOException { String repoHomePath = repoHome.getAbsolutePath(); try { if (keepBackup) { File backupDir = new File(repoHome.getParentFile(), "repository-original"); FileUtils.moveDirectory(repoHome, backupDir); logger.info("Backup of the source repository folder at {}", backupDir); } else { try { FileUtils.deleteDirectory(repoHome); } catch (IOException e) { logger.warn("Issue while deleting the " + repoHomePath + " directory, we will try to empty it"); FileUtils.cleanDirectory(repoHome); } } FileUtils.moveDirectory(repoHomeCopy, repoHome); } catch (IOException e) { logger.error("The migration was successful, except the last step: we are unable to delete directory {}." + " Jahia server will be stopped now.", repoHomePath); String repoHomeCopyPath = repoHomeCopy.getAbsolutePath(); logger.error( "Please delete the directory {} manually (perhaps an OS reboot is required to free the file locks)" + " and copy the content of {} to {}", new String[] { repoHomePath, repoHomeCopyPath, repoHomePath }); logger.error( "Also delete the following folders if they are present:\n{}\\index\n{}\\workspaces\\default\\index\n{}\\workspaces\\live\\index", new String[] { repoHomePath, repoHomePath, repoHomePath }); logger.error("Start Jahia server afterwards.", new String[] { repoHomePath, repoHomeCopyPath, repoHomePath }); System.exit(1); } logger.info("Replaced repository with the migrated copy"); } }