blob: 1c651fb7a2cada9df1cd18db4e1cc72b1bf5b6e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2013 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Johannes Faltermeier - initial API and implementation
******************************************************************************/
package org.eclipse.emf.emfstore.internal.server.startup;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* When using the default EMF XMI persistence HRefs between resources are persisted based on the non-normalized URIs of
* the resources. Since version 1.1 introduced a new URI scheme for EMFStore, files that were persisted with version 1.0
* and prior need to be migrated.
* <p>
* This migrator will update the HRefs in legacy files on the server side.
*
* @author jfaltermeier
*
*/
public class ServerHrefMigrator {
private static final String STORAGE_USS = "storage.uss"; //$NON-NLS-1$
private static final String PROJECT_PREFIX = "project-"; //$NON-NLS-1$
private File backup;
private List<String> corruptProjectIds = new ArrayList<String>();
/**
* Performs the migration, if needed. Creates a backup beforehand.
*
* @return <code>true</code> if migration was successful, <code>false</code> if an error occurred and the server
* startup should be canceled.
*/
public boolean migrate() {
final String serverHome = ServerConfiguration.getServerHome();
// check if migration is needed
if (isMigrationNeeded(serverHome + STORAGE_USS)) {
if (backup != null) {
return false;
}
try {
backup = createBackup(ServerConfiguration.getServerHome(),
ServerConfiguration.getServerHome() + "../backup" + System.currentTimeMillis()); //$NON-NLS-1$
} catch (final IOException ex) {
ModelUtil.logException(
Messages.ServerHrefMigrator_ErrorDuringBackup, ex);
return false;
}
// perform migration
try {
corruptProjectIds = doMigrate(serverHome);
return true;
} catch (final InvocationTargetException ex) {
ModelUtil.logException(
Messages.ServerHrefMigrator_ErrorDuringMigration, ex);
return false;
}
}
return true;
}
private boolean isMigrationNeeded(String pathToServerSpace) {
try {
final String toMatch = getProjectAttribute(pathToServerSpace);
if (toMatch == null) {
return false;
}
return toMatch.contains("projectHistory.uph"); //$NON-NLS-1$
} catch (final ParserConfigurationException ex) {
ModelUtil.logException(
Messages.ServerHrefMigrator_SkipMigration, ex);
} catch (final SAXException ex) {
ModelUtil.logException(
Messages.ServerHrefMigrator_SkipMigration, ex);
} catch (final IOException ex) {
ModelUtil.logException(
Messages.ServerHrefMigrator_SkipMigration, ex);
}
try {
backup = createBackup(ServerConfiguration.getServerHome(),
ServerConfiguration.getServerHome() + "../backup" + System.currentTimeMillis()); //$NON-NLS-1$
} catch (final IOException ex) {
backup = new File(""); //$NON-NLS-1$
ModelUtil.logException(
Messages.ServerHrefMigrator_BackupFailed, ex);
}
return true;
}
/**
* Creates a backup.
*
* @param from path to source
* @param to path to destination
* @return the backup file
* @throws IOException on a IO problem during backup creation
*/
protected File createBackup(String from, String to) throws IOException {
final File sourceFile = new File(from);
final File backupFile = new File(to);
FileUtil.copyDirectory(sourceFile, backupFile);
return backupFile;
}
private List<String> doMigrate(String serverHome) throws InvocationTargetException {
migrateNonContainment(serverHome + STORAGE_USS, "projects", new ServerSpaceRule()); //$NON-NLS-1$
final File serverHomeFile = new File(serverHome);
final File[] projectFiles = serverHomeFile.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(PROJECT_PREFIX);
}
});
final List<String> corruptProjectIds = new ArrayList<String>();
for (final File f : projectFiles) {
try {
final String projectHistoryPath = f.getAbsolutePath() + "/projectHistory.uph"; //$NON-NLS-1$
if (!new File(projectHistoryPath).exists()) {
ModelUtil.logWarning(MessageFormat.format(
Messages.ServerHrefMigrator_HistoryFileDoeNotExists,
projectHistoryPath));
removeReferencesToCorruptProject(serverHome + STORAGE_USS,
f.getName().substring(PROJECT_PREFIX.length()));
continue;
}
migrateContainmentHRefs(projectHistoryPath, "versions", //$NON-NLS-1$
new VersionRule());
final File[] versions = f.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith("version-"); //$NON-NLS-1$
}
});
for (final File version : versions) {
final String versionPath = version.getAbsolutePath();
migrateNonContainment(versionPath, "nextVersion", new VersionMultiRule()); //$NON-NLS-1$
migrateNonContainment(versionPath, "previousVersion", new VersionMultiRule()); //$NON-NLS-1$
migrateNonContainment(versionPath, "ancestorVersion", new VersionMultiRule()); //$NON-NLS-1$
migrateNonContainment(versionPath, "branchedVersions", new VersionMultiRule()); //$NON-NLS-1$
migrateNonContainment(versionPath, "mergedToVersion", new VersionMultiRule()); //$NON-NLS-1$
migrateNonContainment(versionPath, "mergedFromVersion", new VersionMultiRule()); //$NON-NLS-1$
}
} catch (final InvocationTargetException exception) {
ModelUtil.logException(MessageFormat.format(
Messages.ServerHrefMigrator_MigrationFailed, f.getAbsolutePath()), exception);
corruptProjectIds.add(f.getName().substring(PROJECT_PREFIX.length()));
continue;
}
}
return corruptProjectIds;
}
private String getProjectAttribute(String pathToFile) throws ParserConfigurationException, SAXException,
IOException {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final Document doc = docBuilder.parse(pathToFile);
final Node serverSpace = doc.getFirstChild();
final NamedNodeMap attr = serverSpace.getAttributes();
final Node nodeAttr = attr.getNamedItem("projects"); //$NON-NLS-1$
if (nodeAttr == null) {
return null;
}
final String projectsOld = nodeAttr.getTextContent();
final String[] projects = projectsOld.split(" "); //$NON-NLS-1$
if (projects.length < 1) {
return null;
}
return projects[0];
}
/**
* Updates the href attribute in tags with the given name.
*
* @param pathToFile the path of the xmi file to be updated
* @param tagName the tag for which the hrefs are to be updated
* @param rule the rule computing the new value
* @throws InvocationTargetException in case of error
*/
protected void migrateContainmentHRefs(String pathToFile,
String tagName, UpdateXMIAttributeRule rule) throws InvocationTargetException {
try {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final Document doc = docBuilder.parse(pathToFile);
final NodeList tagElements = doc.getElementsByTagName(tagName);
for (int i = 0; i < tagElements.getLength(); i++) {
final Node pS = tagElements.item(i);
final NamedNodeMap attr = pS.getAttributes();
final Node nodeAttr = attr.getNamedItem("href"); //$NON-NLS-1$
final String hrefOld = nodeAttr.getTextContent();
final String hrefNew = rule.getNewAttribute(hrefOld);
nodeAttr.setTextContent(hrefNew);
}
final TransformerFactory transformerFactory = TransformerFactory
.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
final DOMSource source = new DOMSource(doc);
final StreamResult result = new StreamResult(new File(pathToFile));
transformer.transform(source, result);
} catch (final DOMException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final ParserConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final SAXException ex) {
throw new InvocationTargetException(ex);
} catch (final IOException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerFactoryConfigurationError ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerException ex) {
throw new InvocationTargetException(ex);
}
}
private void removeReferencesToCorruptProject(String serverHome, String projectId)
throws InvocationTargetException {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document doc = builder.parse(serverHome);
final XPathFactory xPathfactory = XPathFactory.newInstance();
final XPath xpath = xPathfactory.newXPath();
final XPathExpression expr = xpath.compile("//projects[@id=\"" + projectId + "\"]"); //$NON-NLS-1$ //$NON-NLS-2$
final NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < nl.getLength(); i++) {
nl.item(i).getParentNode().removeChild(nl.item(i));
}
final TransformerFactory transformerFactory = TransformerFactory
.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
final DOMSource source = new DOMSource(doc);
final StreamResult result = new StreamResult(new File(serverHome));
transformer.transform(source, result);
} catch (final ParserConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final SAXException ex) {
throw new InvocationTargetException(ex);
} catch (final IOException ex) {
throw new InvocationTargetException(ex);
} catch (final XPathExpressionException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerException ex) {
throw new InvocationTargetException(ex);
}
}
/**
* Updates the attribute with the given name.
*
* @param pathToFile the path of the xmi file to be updated
* @param tagName the tag for which the attribute contents are to be updated
* @param rule the rule computing the new value
* @throws InvocationTargetException in case of error
*/
protected void migrateNonContainment(String pathToFile,
String tagName, UpdateXMIAttributeRule rule) throws InvocationTargetException {
try {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final Document doc = docBuilder.parse(pathToFile);
final Node serverSpace = doc.getFirstChild();
final NamedNodeMap attr = serverSpace.getAttributes();
final Node nodeAttr = attr.getNamedItem(tagName);
if (nodeAttr == null) {
return;
}
final String attributeOld = nodeAttr.getTextContent();
final String attributeNew = rule.getNewAttribute(attributeOld);
nodeAttr.setTextContent(attributeNew);
final TransformerFactory transformerFactory = TransformerFactory
.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
final DOMSource source = new DOMSource(doc);
final StreamResult result = new StreamResult(new File(pathToFile));
transformer.transform(source, result);
} catch (final DOMException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final ParserConfigurationException ex) {
throw new InvocationTargetException(ex);
} catch (final SAXException ex) {
throw new InvocationTargetException(ex);
} catch (final IOException ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerFactoryConfigurationError ex) {
throw new InvocationTargetException(ex);
} catch (final TransformerException ex) {
throw new InvocationTargetException(ex);
}
}
/**
* Returns a list of project IDs that could not be migrated.
*
* @return the corruptProjectIds
*/
public List<String> getCorruptProjectIds() {
return corruptProjectIds;
}
}