blob: 096eb2639a8cac18563e455be88952fa4f9f90ac [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Edgar Mueller - initial API and implementation
******************************************************************************/
package org.eclipse.emf.ecp.ide.internal.migration;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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.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.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecp.ide.spi.util.EcoreHelper;
import org.eclipse.emf.ecp.ide.spi.util.ViewModelHelper;
import org.eclipse.emf.ecp.view.spi.model.VView;
import org.eclipse.emf.ecp.view.spi.model.VViewModelProperties;
import org.eclipse.emf.ecp.view.spi.model.util.ViewModelPropertiesHelper;
import org.eclipse.emfforms.common.Optional;
import org.eclipse.emfforms.common.internal.validation.ValidationServiceImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Executes a simple XPath-based transformation for migrating the namespace
* fragments of a given view model file.
*
*/
public class ViewMigrationHandler {
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
private final String oldNamespaceFragment;
private final String newNamespaceFragment;
/**
* Default constructor.
*
* @param oldNamespaceFragment the value of the namespace fragment to be replaced
* @param newNamespaceFragment the new namespace fragment to replace the old with
*/
public ViewMigrationHandler(String oldNamespaceFragment, String newNamespaceFragment) {
this.oldNamespaceFragment = oldNamespaceFragment;
this.newNamespaceFragment = newNamespaceFragment;
}
/**
* Execute the migration for all given files.
*
* @param files the set of files to be migrated
* @param monitor a {@link SubMonitor} that allows for reporting progress
* @return a map of file names containing the view models to be migrated
* to the respective {@link Diagnostic}s which have been produced
* while loading the views
*
* @throws ViewMigrationException in case the migration of the view fails
*/
public Map<String, Optional<Diagnostic>> execute(Set<IFile> files, SubMonitor monitor)
throws ViewMigrationException {
final SubMonitor subMonitor = SubMonitor.convert(monitor, files.size());
final Map<String, Optional<Diagnostic>> diagnostics = new LinkedHashMap<String, Optional<Diagnostic>>();
for (final IFile file : files) {
try {
final Optional<Diagnostic> diagnostic = execute(file);
diagnostics.put(file.getName(), diagnostic);
// BEGIN SUPRESS CATCH EXCEPTION
} catch (final Exception throwable) {
Activator.log(throwable);
// END SUPRESS CATCH EXCEPTION
continue;
}
subMonitor.worked(1);
}
return diagnostics;
}
/**
* Execute the migration for a single file and validate it.
*
* @param file the file to be migrated
* @return the validation result after the file has been migrated, or {@link Optional#empty}
* if the view could not be resolved
*
* @throws ViewMigrationException in case the migration of the view fails
*/
public Optional<Diagnostic> execute(final IFile file) throws ViewMigrationException {
try {
file.refreshLocal(0, new NullProgressMonitor());
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final Document doc = docBuilder.parse(file.getContents());
final XPathFactory xPathfactory = XPathFactory.newInstance();
final XPath xpath = xPathfactory.newXPath();
// select all elements with a href attribute
final XPathExpression expr = xpath.compile("//*[@href]"); //$NON-NLS-1$
final NodeList elements = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < elements.getLength(); i++) {
final Node item = elements.item(i);
final Node href = item.getAttributes().getNamedItem("href"); //$NON-NLS-1$
href.setNodeValue(href.getNodeValue().replace(oldNamespaceFragment, newNamespaceFragment));
}
final DOMSource source = new DOMSource(doc);
final PipedInputStream pis = new PipedInputStream();
final PipedOutputStream pos = new PipedOutputStream();
pos.connect(pis);
final StreamResult result = new StreamResult(pos);
final Future<Void> future = executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
file.setContents(pis, true, true, new NullProgressMonitor());
} finally {
pis.close();
}
return null;
}
});
try {
transformer.transform(source, result);
} finally {
pos.close();
}
// block per file, could be optimized
future.get();
return checkView(file);
} catch (final SAXException ex) {
throw new ViewMigrationException(ex);
} catch (final TransformerConfigurationException ex) {
throw new ViewMigrationException(ex);
} catch (final ParserConfigurationException ex) {
throw new ViewMigrationException(ex);
} catch (final IOException ex) {
throw new ViewMigrationException(ex);
} catch (final XPathExpressionException ex) {
throw new ViewMigrationException(ex);
} catch (final TransformerException ex) {
throw new ViewMigrationException(ex);
} catch (final CoreException ex) {
throw new ViewMigrationException(ex);
} catch (final InterruptedException ex) {
throw new ViewMigrationException(ex);
} catch (final ExecutionException ex) {
throw new ViewMigrationException(ex);
}
}
private Optional<Diagnostic> checkView(IFile file) throws IOException {
final LinkedHashSet<String> ecores = new LinkedHashSet<String>();
final VView view = ViewModelHelper.loadView(file, ecores);
try {
if (view != null) {
final VViewModelProperties properties = ViewModelPropertiesHelper.getInhertitedPropertiesOrEmpty(view);
view.setLoadingProperties(properties);
final ValidationServiceImpl validationService = new ValidationServiceImpl();
return Optional.of(validationService.validate(view));
}
return Optional.empty();
} finally {
for (final String registeredEcore : ecores) {
EcoreHelper.unregisterEcore(registeredEcore);
}
}
}
}