/******************************************************************************* * Copyright (c) 2016 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is 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: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.windup.model.domain; import static org.jboss.tools.windup.model.domain.WindupConstants.CONFIG_DELETED; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.e4.core.di.annotations.Creatable; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.jdt.core.IPackageFragment; import org.jboss.tools.common.xml.IMemento; import org.jboss.tools.common.xml.XMLMemento; import org.jboss.tools.windup.model.Activator; import org.jboss.tools.windup.model.OptionFacades; import org.jboss.tools.windup.model.OptionFacades.OptionsFacadeManager; import org.jboss.tools.windup.model.util.DocumentUtils; import org.jboss.tools.windup.runtime.WindupRuntimePlugin; import org.jboss.tools.windup.windup.ConfigurationElement; import org.jboss.tools.windup.windup.CustomRuleProvider; import org.jboss.tools.windup.windup.Input; import org.jboss.tools.windup.windup.Issue; import org.jboss.tools.windup.windup.MigrationPath; import org.jboss.tools.windup.windup.Technology; import org.jboss.tools.windup.windup.WindupFactory; import org.jboss.tools.windup.windup.WindupModel; import org.jboss.tools.windup.windup.WindupResult; import org.jboss.windup.bootstrap.help.Help; import org.jboss.windup.tooling.ExecutionResults; import org.jboss.windup.tooling.data.Hint; import org.jboss.windup.tooling.data.Link; import org.jboss.windup.tooling.data.Quickfix; import org.jboss.windup.tooling.data.ReportLink; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import com.google.common.base.Objects; /** * Service for interacting with Windup's model and editing domain. * * TODO: Initially I wanted to design our domain so that it's transactional; however, * I haven't enforced wrapping writes in a transaction, and we may want to. */ @Singleton @Creatable public class ModelService { private static final String DOMAIN_NAME = "org.jboss.tools.windup.WindupEditingDomain"; //$NON-NLS-1$ public static final String CONFIG_XML_PATH = "model/migration-paths/migration-paths.xml"; private static final String TIMESTAMP_FORMAT = "yyyy.MM.dd.HH.mm.ss"; //$NON-NLS-1$ public static IPath reportsDir = Activator.getDefault().getStateLocation().append("reports"); //$NON-NLS-1$ public static final String PROJECT_REPORT_HOME_PAGE = "index.html"; //$NON-NLS-1$ private static final String MODEL_FILE = "windup.xmi"; private OptionsFacadeManager optionsFacadeManager; @Inject private IEventBroker broker; @Inject private WindupDomainListener modelListener; private WindupModel model; private TransactionalEditingDomain domain; @PostConstruct private void initialize() { domain = TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain(DOMAIN_NAME); load(); model.eAdapters().add(modelListener); } @SuppressWarnings("unchecked") public <T extends EditingDomain> T getDomain() { return (T)domain; } public WindupModel getModel() { return model; } private void loadMigrationPaths() { Bundle bundle = FrameworkUtil.getBundle(ModelService.class); URL url = FileLocator.find(bundle, new Path(CONFIG_XML_PATH), null); try (InputStream input = url.openStream()) { XMLMemento root = XMLMemento.createReadRoot(input); for (IMemento element : root.getChildren("path")) { MigrationPath path = WindupFactory.eINSTANCE.createMigrationPath(); model.getMigrationPaths().add(path); path.setId(element.getString("id")); XMLMemento child = (XMLMemento)element.getChild("name"); path.setName(child.getTextData()); child = (XMLMemento)element.getChild("target"); Technology target = WindupFactory.eINSTANCE.createTechnology(); path.setTarget(target); target.setId(child.getString("id")); target.setVersionRange(child.getString("version-range")); child = (XMLMemento)element.getChild("source"); if (child != null) { Technology source = WindupFactory.eINSTANCE.createTechnology(); path.setSource(source); source.setId(child.getString("id")); source.setVersionRange(child.getString("version-range")); } } } catch (IOException e) { Activator.log(e); } } public OptionsFacadeManager getOptionFacadeManager() { if (this.optionsFacadeManager == null) { Help help = WindupRuntimePlugin.findWindupHelpCache(); this.optionsFacadeManager = OptionFacades.createOptionsFacadeManager(help); } return this.optionsFacadeManager; } /** * Executes the provided runnable on the command stack of Windup's editing domain. * * @return true if the runner executed without throwing an exception, false otherwise. */ public boolean write(Runnable runner) { CommandWithResult<Boolean> cmd = new CommandWithResult<Boolean>(domain) { @Override protected void doExecute() { try { runner.run(); } catch (Exception e) { Activator.log(e); setResultObject(Boolean.FALSE); return; } setResultObject(Boolean.TRUE); } }; domain.getCommandStack().execute(cmd); return cmd.getResultObject(); } /** * Executes the provided supplier on the command stack of Windup's editing domain. * * @return the result of the supplier, null if an error occurred. */ public <T> T write(Supplier<T> supplier) { CommandWithResult<T> cmd = new CommandWithResult<T>(domain) { @Override protected void doExecute() { try { setResultObject(supplier.get()); } catch (Exception e) { Activator.log(e); } } }; domain.getCommandStack().execute(cmd); return cmd.getResultObject(); } private void load() { File location = getWindupStateLocation(); Resource resource = createResource(); if (location != null && location.exists()) { try { resource.load(null); } catch (IOException e) { Activator.log(e); return; } model = (WindupModel)resource.getContents().get(0); } else { model = WindupFactory.eINSTANCE.createWindupModel(); resource.getContents().add(model); loadMigrationPaths(); } } public void save() { try { model.eResource().save(null); } catch (IOException e) { Activator.log(e); } } private Resource createResource() { ResourceSet resourceSet = new ResourceSetImpl(); resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put (Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); Resource resource = resourceSet.createResource(getStateURI()); resource.setTrackingModification(true); return resource; } private URI getStateURI() { return URI.createFileURI(getWindupStateLocation().getAbsolutePath()); } private File getWindupStateLocation() { File file = null; try { File bundleFile = FileLocator.getBundleFile(Activator.getDefault().getBundle()); file = new File(bundleFile, MODEL_FILE); if (file != null) { file = file.getCanonicalFile(); } } catch (IOException e) { Activator.log(e); } if (file == null) { // Fall-back to creating it in workspace. file = Activator.getDefault().getStateLocation().append(MODEL_FILE).toFile(); } return file; } public ConfigurationElement findConfigurationElement(String name) { Optional<ConfigurationElement> configuration = model.getConfigurationElements().stream().filter(c -> Objects.equal(name, c.getName())).findFirst(); if (configuration.isPresent()) { return configuration.get(); } return null; } public void deleteConfiguration(ConfigurationElement configuration) { model.getConfigurationElements().remove(configuration); broker.post(CONFIG_DELETED, configuration); } public void deleteIssue(Issue issue) { ((WindupResult)issue.eContainer()).getIssues().remove(issue); } public ConfigurationElement createConfiguration(String name) { CommandWithResult<ConfigurationElement> cmd = new CommandWithResult<ConfigurationElement>(domain) { @Override protected void doExecute() { try { ConfigurationElement configuration = WindupFactory.eINSTANCE.createConfigurationElement(); configuration.setName(name); configuration.setWindupHome(WindupRuntimePlugin.findWindupHome().toPath().toString()); configuration.setGeneratedReportsLocation(getGeneratedReportsBaseLocation(configuration).toOSString()); configuration.setSourceMode(true); configuration.setGenerateReport(true); configuration.setMigrationPath(model.getMigrationPaths().get(1)); model.getConfigurationElements().add(configuration); setResultObject(configuration); save(); } catch (Exception e) { Activator.log(e); } } }; domain.getCommandStack().execute(cmd); return cmd.getResultObject(); } public void createInput(ConfigurationElement configuration, List<IProject> projects) { projects.forEach(project -> { Input input = WindupFactory.eINSTANCE.createInput(); URI uri = WorkspaceResourceUtils.createPlatformPluginURI(project.getFullPath()); input.setName(project.getName()); input.setUri(uri.toString()); configuration.getInputs().add(input); }); } public void deleteProjects(ConfigurationElement configuration, Set<IProject> projects) { projects.forEach(project -> { Optional<Input> input = configuration.getInputs().stream().filter(i -> { return i.getName().equals(project.getName()); }).findFirst(); if (input.isPresent()) { configuration.getInputs().remove(input.get()); } }); } public void deletePackages(ConfigurationElement configuration, Set<IProject> projects) { for (IPackageFragment fragment : ConfigurationResourceUtil.getCurrentPackages(configuration)) { if (projects.contains(fragment.getJavaProject().getProject())) { URI uri = WorkspaceResourceUtils.createPlatformPluginURI(fragment.getPath()); configuration.getPackages().remove(uri.toString()); } } } public void addPackages(ConfigurationElement configuration, List<IPackageFragment> packages) { List<String> uris = packages.stream().map(p -> { return WorkspaceResourceUtils.createPlatformPluginURI(p.getPath()).toString(); }).collect(Collectors.toList()); configuration.getPackages().addAll(uris); } public void addRulesetRepository(String location) { addRulesetRepository(location, false); } public CustomRuleProvider addRulesetRepository(String location, boolean isExternal) { return write(() -> { CustomRuleProvider repo = WindupFactory.eINSTANCE.createCustomRuleProvider(); repo.setLocationURI(location); repo.setExternal(isExternal); model.getCustomRuleRepositories().add(repo); return repo; }); } public void removePackages(ConfigurationElement configuration, List<IPackageFragment> packages) { List<String> uris = packages.stream().map(p -> { return WorkspaceResourceUtils.createPlatformPluginURI(p.getPath()).toString(); }).collect(Collectors.toList()); configuration.getPackages().removeAll(uris); } public void synch(ConfigurationElement configuration) { for (Iterator<String> iter = configuration.getPackages().iterator(); iter.hasNext();) { IResource resource = (IResource)WorkspaceResourceUtils.findResource(iter.next()); if (resource == null || !resource.exists()) { iter.remove(); } } for (Iterator<Input> iter = configuration.getInputs().iterator(); iter.hasNext();) { Input input = iter.next(); IResource resource = WorkspaceResourceUtils.findResource(input.getUri()); if (resource == null || !resource.exists()) { iter.remove(); } } } public void addDirtyListener(Consumer<Boolean> runner) { domain.getCommandStack().addCommandStackListener((e) -> { runner.accept(model.eResource().isModified()); }); } public void cleanCustomRuleRepositories(ConfigurationElement configuration) { // Delete non-existing registered providers. for (Iterator<CustomRuleProvider> iter = getModel().getCustomRuleRepositories().iterator(); iter.hasNext();) { if (!new File(iter.next().getLocationURI()).exists()) { write(() -> iter.remove()); } } // Delete non-existing referenced locations. for (Iterator<String> iter = configuration.getUserRulesDirectories().iterator(); iter.hasNext();) { if (!new File(iter.next()).exists()) { write(() -> iter.remove()); } } } @PreDestroy private void dispose() { save(); } public ConfigurationElement findConfiguration(String name) { Optional<ConfigurationElement> found = model.getConfigurationElements().stream().filter(configuration -> { return Objects.equal(configuration.getName(), name); }).findFirst(); return found.isPresent() ? found.get() : null; } public IPath getReportPath(ConfigurationElement configuration) { return ModelService.reportsDir.append(configuration.getName()).append(PROJECT_REPORT_HOME_PAGE); } public IPath getGeneratedReportsBaseLocation(ConfigurationElement configuration) { String path = configuration.getName().replaceAll("\\s+", ""); path = path.concat(File.separator); return reportsDir.append(path); } public IPath getGeneratedReportsBaseLocation(Issue issue) { Input input = (Input)issue.eContainer().eContainer(); ConfigurationElement configuration = (ConfigurationElement)input.eContainer(); return getGeneratedReportBaseLocation(configuration, input); } public IPath getGeneratedReportBaseLocation(ConfigurationElement configuration, Input input) { IPath path = getGeneratedReportsBaseLocation(configuration); path = path.append(input.getName()); path = path.append(File.separator); return path; } public IPath getGeneratedReport(ConfigurationElement configuration, Input input) { IPath path = getGeneratedReportBaseLocation(configuration, input); path = path.append(PROJECT_REPORT_HOME_PAGE); return path; } /** * Populates the configuration element with the execution results. */ public void populateConfiguration(ConfigurationElement configuration, Input input, IPath reportDirectory, ExecutionResults results) { WindupResult result = WindupFactory.eINSTANCE.createWindupResult(); result.setExecutionResults(results); input.setWindupResult(result); configuration.setTimestamp(createTimestamp()); configuration.setReportDirectory(reportDirectory.toString()); for (Hint wHint : results.getHints()) { String path = wHint.getFile().getAbsolutePath(); IFile resource = WorkspaceResourceUtils.getResource(path); if (resource == null) { Activator.logErrorMessage("ModelService:: No workspace resource associated with file: " + path); //$NON-NLS-1$ continue; } org.jboss.tools.windup.windup.Hint hint = WindupFactory.eINSTANCE.createHint(); result.getIssues().add(hint); String line = DocumentUtils.getLine(resource, wHint.getLineNumber()-1); hint.setOriginalLineSource(line); for (Quickfix fix : wHint.getQuickfixes()) { org.jboss.tools.windup.windup.QuickFix quickFix = WindupFactory.eINSTANCE.createQuickFix(); quickFix.setQuickFixType(fix.getType().toString()); quickFix.setSearchString(fix.getSearch()); quickFix.setReplacementString(fix.getReplacement()); quickFix.setNewLine(fix.getNewline()); quickFix.setTransformationId(fix.getTransformationID()); quickFix.setName(fix.getName()); if (fix.getFile() != null) { quickFix.setFile(fix.getFile().getAbsolutePath()); } else { // Fallback for quickfixes not assigned to file. Assume quickfix applies to file associated with the hint. quickFix.setFile(path); } hint.getQuickFixes().add(quickFix); } // TODO: I think we might want to change this to project relative for portability. hint.setFileAbsolutePath(wHint.getFile().getAbsolutePath()); hint.setSeverity(wHint.getIssueCategory().getCategoryID().toUpperCase()); hint.setRuleId(wHint.getRuleID()); hint.setEffort(wHint.getEffort()); hint.setTitle(wHint.getTitle()); hint.setHint(wHint.getHint()); hint.setLineNumber(wHint.getLineNumber()); hint.setColumn(wHint.getColumn()); hint.setLength(wHint.getLength()); hint.setSourceSnippet(wHint.getSourceSnippit()); for (Link wLink : wHint.getLinks()) { org.jboss.tools.windup.windup.Link link = WindupFactory.eINSTANCE.createLink(); link.setDescription(wLink.getDescription()); link.setUrl(wLink.getUrl()); hint.getLinks().add(link); } } // TODO: Classifications linkReports(results, result.getIssues()); } private void linkReports(ExecutionResults results, List<Issue> issues) { for (Issue issue : issues) { IFile resource = WorkspaceResourceUtils.getResource(issue.getFileAbsolutePath()); if (resource == null) { Activator.logErrorMessage("ModelService:: No resource associated with issue file: " + issue.getFileAbsolutePath()); continue; } File file = resource.getRawLocation().toFile(); for (ReportLink link : results.getReportLinks()) { if (link.getInputFile().equals(file)) { File report = link.getReportFile(); issue.setGeneratedReportLocation(report.getAbsolutePath()); break; } } } } private SimpleDateFormat getTimestampFormat() { return new SimpleDateFormat(TIMESTAMP_FORMAT); } public String createTimestamp() { return getTimestampFormat().format(new Date()); } private Date getTimestamp(ConfigurationElement configuration) { try { return new SimpleDateFormat(TIMESTAMP_FORMAT).parse(configuration.getTimestamp()); } catch (ParseException e) { Activator.log(e); } return null; } public ConfigurationElement getRecentConfiguration() { ConfigurationElement mostRecentConfiguration = null; for (ConfigurationElement configuration : getModel().getConfigurationElements()) { if (mostRecentConfiguration == null) { mostRecentConfiguration = configuration; } else if (!mostRecentConfiguration.getName().equals(configuration.getName()) && mostRecentConfiguration.getTimestamp() != null && configuration.getTimestamp() != null) { Date thisTime = getTimestamp(mostRecentConfiguration); Date otherTime = getTimestamp(configuration); if (thisTime.before(otherTime)) { mostRecentConfiguration = configuration; } } } return mostRecentConfiguration; } }