/******************************************************************************* * Copyright (c) 2014 Mentor Graphics 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: * Mentor Graphics - initial API and implementation *******************************************************************************/ package com.codesourcery.internal.installer; import java.io.File; import java.util.ArrayList; import java.util.Map.Entry; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionedId; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.codesourcery.installer.IInstallAction; import com.codesourcery.installer.IInstallManifest; import com.codesourcery.installer.IInstallProduct; import com.codesourcery.installer.IProductRange; import com.codesourcery.installer.Installer; /** * Default implementation of {@link com.codesourcery.installer.IInstallManifest}. */ public class InstallManifest implements IInstallManifest { /** File version */ private static final String FILE_VERSION = "1.2"; /** Install element */ private static final String ELEMENT_INSTALL = "install"; /** Products element */ private static final String ELEMENT_PRODUCTS = "products"; /** Product element */ private static final String ELEMENT_PRODUCT = "product"; /** Actions element */ private static final String ELEMENT_ACTIONS = "actions"; /** Action element */ private static final String ELEMENT_ACTION = "action"; /** Units element */ private static final String ELEMENT_UNITS = "units"; /** Unit element */ private static final String ELEMENT_UNIT = "unit"; /** Properties element */ private static final String ELEMENT_PROPERTIES = "properties"; /** Property element */ private static final String ELEMENT_PROPERTY = "property"; /** Location attribute */ private static final String ATTRIBUTE_LOCATION = "location"; /** Install location attribute */ private static final String ATTRIBUTE_INSTALL_LOCATION = "installLocation"; /** Name attribute */ private static final String ATTRIBUTE_NAME = "name"; /** Uninstall Name attribute */ private static final String ATTRIBUTE_UNINSTALL_NAME = "uninstallName"; /** Value attribute */ private static final String ATTRIBUTE_VALUE = "value"; /** Version attribute */ private static final String ATTRIBUTE_VERSION = "version"; /** Identifier attribute */ private static final String ATTRIBUTE_ID = "id"; /** Data path attribute */ private static final String ATTRIBUTE_DATA = "dataLocation"; /** Parent directory names attribute */ private static final String ATTRIBUTE_DIRECTORIES = "directories"; /** Installed products */ private ArrayList<IInstallProduct> products = new ArrayList<IInstallProduct>(); /** Install manifest file */ private File file; /** Loaded version */ private String version = FILE_VERSION; /** Path to installer data directory */ private IPath dataPath; /** Directories to remove during uninstallation */ private String[] directories = new String[0]; /** * Loads an install manifest for the location specified in an install * description. * * @param installLocation Install location * @return Install manifest or <code>null</code> not manifest is found. * @throws CoreException on failure to load the install manifest */ public static InstallManifest loadManifest(IPath installLocation) throws CoreException { InstallManifest manifest = null; if (installLocation != null) { IPath uninstallLocation = installLocation.append(IInstallConstants.UNINSTALL_DIRECTORY); IPath manifestPath = uninstallLocation.append(IInstallConstants.INSTALL_MANIFEST_FILENAME); File manifestFile = manifestPath.toFile(); // Loading existing manifest if (manifestFile.exists()) { manifest = new InstallManifest(); manifest.load(manifestFile); } } return manifest; } /** * Constructor */ public InstallManifest() { dataPath = Installer.getDefault().getDataFolder(); } @Override public IInstallProduct[] getProducts() { return products.toArray(new InstallProduct[products.size()]); } @Override public void addProduct(IInstallProduct product) { if (!products.contains(product)) products.add(product); } @Override public void removeProduct(IInstallProduct product) { products.remove(product); } @Override public IInstallProduct getProduct(String productId) { IInstallProduct product = null; IInstallProduct[] existingProducts = getProducts(); for (IInstallProduct existingProduct : existingProducts) { // Product is already installed if (existingProduct.getId().equals(productId)) { product = existingProduct; break; } } return product; } @Override public IInstallProduct[] getProducts(IProductRange[] ranges) { if (ranges == null) return null; ArrayList<IInstallProduct> foundProducts = new ArrayList<IInstallProduct>(); for (IProductRange range : ranges) { IInstallProduct product = getProduct(range.getId()); if (product != null) { if ((range.getVersionRange() == null) || range.getVersionRange().isIncluded(product.getVersion())) { foundProducts.add(product); } } } return foundProducts.toArray(new IInstallProduct[foundProducts.size()]); } @Override public IPath getDataPath() { return dataPath; } @Override public void save() throws CoreException { save(file); } /** * Converts an absolute path into a path relative to this manifest file. * * @param path Absolute path * @return Relative path */ private IPath toRelativePath(IPath path) { if (path != null) { if (path.isAbsolute()) { path = path.makeRelativeTo(getPath().removeLastSegments(1)); } } return path; } /** * Converts a path relative to this manifest file into an absolute path. * * @param path Relative path * @return Absolute path */ private IPath fromRelativePath(IPath path) { IPath absPath = path; if (!path.isAbsolute()) { absPath = getPath().removeLastSegments(1).append(path); } return absPath; } @Override public void save(File file) throws CoreException { this.file = file; try { // Don't overwrite a previous version. This allows for patching // an old installation without breaking the format used by a // a previous uninstaller. if (Installer.getDefault().getInstallManager().getInstallMode().isPatch() && !FILE_VERSION.equals(version)) { return; } DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document document = docBuilder.newDocument(); // Root element Element installElement = document.createElement(ELEMENT_INSTALL); document.appendChild(installElement); // File version installElement.setAttribute(ATTRIBUTE_VERSION, FILE_VERSION); // Data path installElement.setAttribute(ATTRIBUTE_DATA, getDataPath().toOSString()); // Directory levels installElement.setAttribute(ATTRIBUTE_DIRECTORIES, InstallUtils.getStringFromArray(directories, "/")); // Products root Element productsElement = document.createElement(ELEMENT_PRODUCTS); installElement.appendChild(productsElement); // Products IInstallProduct[] products = getProducts(); for (IInstallProduct product : products) { Element productElement = document.createElement(ELEMENT_PRODUCT); // Product identifier productElement.setAttribute(ATTRIBUTE_ID, product.getId()); // Product name productElement.setAttribute(ATTRIBUTE_NAME, product.getName()); // Product version productElement.setAttribute(ATTRIBUTE_VERSION, product.getVersionString()); // Product uninstall name productElement.setAttribute(ATTRIBUTE_UNINSTALL_NAME, product.getUninstallName()); // Product location productElement.setAttribute(ATTRIBUTE_LOCATION, toRelativePath(product.getLocation()).toOSString()); // Product install location productElement.setAttribute(ATTRIBUTE_INSTALL_LOCATION, toRelativePath(product.getInstallLocation()).toOSString()); productsElement.appendChild(productElement); // Product actions root Element actionsElement = document.createElement(ELEMENT_ACTIONS); productElement.appendChild(actionsElement); // Product actions IInstallAction[] actions = product.getActions(); for (IInstallAction action : actions) { Element actionElement = document.createElement(ELEMENT_ACTION); actionsElement.appendChild(actionElement); actionElement.setAttribute(ATTRIBUTE_ID, action.getId()); action.save(document, actionElement); } // Product install units root Element unitsElement = document.createElement(ELEMENT_UNITS); productElement.appendChild(unitsElement); // Product install units for (IVersionedId unit : product.getInstallUnits()) { Element unitElement = document.createElement(ELEMENT_UNIT); unitsElement.appendChild(unitElement); unitElement.setAttribute(ATTRIBUTE_ID, unit.getId()); unitElement.setAttribute(ATTRIBUTE_VERSION, unit.getVersion().getOriginal()); } // Product properties Element propertiesElement = document.createElement(ELEMENT_PROPERTIES); productElement.appendChild(propertiesElement); Set<Entry<String, String>> properties = product.getProperties().entrySet(); for (Entry<String, String> property : properties) { Element propertyElement = document.createElement(ELEMENT_PROPERTY); propertyElement.setAttribute(ATTRIBUTE_NAME, property.getKey()); propertyElement.setAttribute(ATTRIBUTE_VALUE, property.getValue()); propertiesElement.appendChild(propertyElement); } } file.getParentFile().mkdirs(); if (file.exists()) file.delete(); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(file); // Output to console for testing // StreamResult result = new StreamResult(System.out); // Formatting transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(source, result); } catch (Exception e) { Installer.fail(InstallMessages.Error_SaveManifest, e); } } @Override public void load(File file) throws CoreException { this.file = file; try { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document document = docBuilder.parse(file); NodeList installNodes = document.getElementsByTagName(ELEMENT_INSTALL); version = ((Element)installNodes.item(0)).getAttribute(ATTRIBUTE_VERSION); String data = ((Element)installNodes.item(0)).getAttribute(ATTRIBUTE_DATA); if ((data != null) && !data.isEmpty()) { dataPath = new Path(data); } data = ((Element)installNodes.item(0)).getAttribute(ATTRIBUTE_DIRECTORIES); if ((data != null) && !data.isEmpty()) { directories = InstallUtils.getArrayFromString(data, "/"); } NodeList productNodes = document.getElementsByTagName(ELEMENT_PRODUCT); for (int productIndex = 0; productIndex < productNodes.getLength(); productIndex++) { Node productNode = productNodes.item(productIndex); if (productNode.getNodeType() == Node.ELEMENT_NODE) { Element productElement = (Element)productNode; // Load actions ArrayList<IInstallAction> actions = new ArrayList<IInstallAction>(); NodeList actionsNodes = productElement.getElementsByTagName(ELEMENT_ACTIONS); for (int actionsIndex = 0; actionsIndex < actionsNodes.getLength(); actionsIndex++) { Node actionsNode = actionsNodes.item(actionsIndex); if (actionsNode.getNodeType() == Node.ELEMENT_NODE) { Element actionsElement = (Element)actionsNode; NodeList actionNodes = actionsElement.getElementsByTagName(ELEMENT_ACTION); for (int actionIndex = 0; actionIndex < actionNodes.getLength(); actionIndex++) { Node actionNode = actionNodes.item(actionIndex); if (actionNode.getNodeType() == Node.ELEMENT_NODE) { try { Element actionElement = (Element)actionNode; String id = actionElement.getAttribute(ATTRIBUTE_ID); IInstallAction action = ContributorRegistry.getDefault().createAction(id); if (action != null) { try { action.load(actionElement); actions.add(action); } catch (Exception e) { Installer.log(e); } } else { Installer.log("Deprecated action not supported: " + id); } } catch (Exception e) { Installer.log(e); } } } } } String attrLocation = productElement.getAttribute(ATTRIBUTE_LOCATION); String attrInstallLocation = productElement.getAttribute(ATTRIBUTE_INSTALL_LOCATION); // Create product InstallProduct product = new InstallProduct( productElement.getAttribute(ATTRIBUTE_ID), productElement.getAttribute(ATTRIBUTE_NAME), productElement.getAttribute(ATTRIBUTE_VERSION), productElement.getAttribute(ATTRIBUTE_UNINSTALL_NAME), attrLocation != null ? fromRelativePath(new Path(attrLocation)) : null, attrInstallLocation != null ? fromRelativePath(new Path(attrInstallLocation)) : null); // Add product actions for (IInstallAction action : actions) { product.addAction(action); } // Add product addProduct(product); // Load install units NodeList unitsNodes = productElement.getElementsByTagName(ELEMENT_UNITS); for (int unitsIndex = 0; unitsIndex < unitsNodes.getLength(); unitsIndex++) { Node unitsNode = unitsNodes.item(unitsIndex); if (unitsNode.getNodeType() == Node.ELEMENT_NODE) { Element unitsElement = (Element)unitsNode; NodeList unitNodes = unitsElement.getElementsByTagName(ELEMENT_UNIT); for (int unitIndex = 0; unitIndex < unitNodes.getLength(); unitIndex++) { Node unitNode = unitNodes.item(unitIndex); if (unitNode.getNodeType() == Node.ELEMENT_NODE) { Element unitElement = (Element)unitNode; try { String id = unitElement.getAttribute(ATTRIBUTE_ID); String version = unitElement.getAttribute(ATTRIBUTE_VERSION); VersionedId unit; if ((version != null) && !version.trim().isEmpty()) { unit = new VersionedId(id, version); } else { unit = new VersionedId(id, Version.emptyVersion); } product.addInstallUnit(unit); } catch (Exception e) { Installer.log(e); } } } } } // Load product properties NodeList propertiesNodes = productElement.getElementsByTagName(ELEMENT_PROPERTIES); for (int propertiesIndex = 0; propertiesIndex < propertiesNodes.getLength(); propertiesIndex++) { Node propertiesNode = propertiesNodes.item(propertiesIndex); if (propertiesNode.getNodeType() == Node.ELEMENT_NODE) { Element propertiesElement = (Element)propertiesNode; NodeList propertyNodes = propertiesElement.getElementsByTagName(ELEMENT_PROPERTY); for (int propertyIndex = 0; propertyIndex < propertyNodes.getLength(); propertyIndex++) { Node propertyNode = propertyNodes.item(propertyIndex); if (propertyNode.getNodeType() == Node.ELEMENT_NODE) { Element propertyElement = (Element)propertyNode; try { String name = propertyElement.getAttribute(ATTRIBUTE_NAME); String value = propertyElement.getAttribute(ATTRIBUTE_VALUE); if (name != null) { product.setProperty(name, value); } } catch (Exception e) { Installer.log(e); } } } } } } } } catch (Exception e) { Installer.fail(InstallMessages.Error_LoadManifest, e); } } /** * Sets the names of the parent directories created during installation. * * @param directories Directory names created */ public void setDirectories(String[] directories) { this.directories = directories; } /** * Returns the names of the parent directories created during installation. * * @return Directory name */ public String[] getDirectories() { return directories; } /** * Returns the path to the manifest file. * * @return Manifest file path */ private IPath getPath() { if (file != null) { return new Path(file.getAbsolutePath()); } else { return null; } } @Override public IPath getInstallLocation() { IPath path = getPath(); if ((path != null) && (path.segmentCount() > 2)) { return path.removeLastSegments(2); } else { return null; } } }