/* * Copyright 2013 NGDATA nv * Copyright 2008 Outerthought bvba and Schaubroeck nv * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lilyproject.runtime.tools.plugin.genclassloader; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; 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.DOMResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter; import org.apache.maven.shared.artifact.filter.collection.ClassifierFilter; import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter; import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; import org.apache.maven.shared.artifact.filter.collection.TransitivityFilter; import org.apache.maven.shared.artifact.filter.collection.TypeFilter; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * Writes a listing of dependencies in a specific format to a predefined file. * * @goal generate * @requiresDependencyResolution runtime * @description Genenerate a Lily Runtime classloader file for a module. */ public class ClassloaderMojo extends AbstractMojo { /** * @parameter default-value="${project}" * @required * @readonly */ private MavenProject project; /** * Helper class to assist in attaching artifacts to the project instance. project-helper instance, used to * make addition of resources simpler. * * @component */ private MavenProjectHelper projectHelper; /** * @parameter default-value="src/main/lily" */ private String templateDirectory; /** * @parameter default-value="classloader-template.xml" */ private String templateFileName; /** * Where should the file end up? * * @parameter default-value="${project.build.directory}/classes/LILY-INF" */ private String targetDirectory; /** * Filename to use. * * @parameter default-value="classloader.xml" */ private String targetFileName; /** * Indicates whether the project artifact itself should also be included * * @parameter default-value="false" * @optional */ protected boolean includeSelf; /** * If we should exclude transitive dependencies * * @since 2.0 * @optional * @parameter property="excludeTransitive" default-value="false" */ protected boolean excludeTransitive; /** * Comma Separated list of Types to include. Empty String indicates include everything (default). * * @since 2.0 * @parameter property="includeTypes" default-value="" * @optional */ protected String includeTypes; /** * Comma Separated list of Types to exclude. Empty String indicates don't exclude anything (default). * Ignored if includeTypes is used. * * @since 2.0 * @parameter property="excludeTypes" default-value="" * @optional */ protected String excludeTypes; /** * Scope to include. An Empty string indicates all scopes (default). * * @since 2.0 * @parameter property="includeScope" default-value="runtime" * @optional */ protected String includeScope; /** * Scope to exclude. An Empty string indicates no scopes (default). Ignored if includeScope is used. * * @since 2.0 * @parameter property="excludeScope" default-value="provided" * @optional */ protected String excludeScope; /** * Comma Separated list of Classifiers to include. Empty String indicates include everything (default). * * @since 2.0 * @parameter property="includeClassifiers" default-value="" * @optional */ protected String includeClassifiers; /** * Comma Separated list of Classifiers to exclude. Empty String indicates don't exclude anything * (default). Ignored if includeClassifiers is used. * * @since 2.0 * @parameter property="excludeClassifiers" default-value="" * @optional */ protected String excludeClassifiers; /** * Comma Seperated list of Artifact names too exclude. Ignored if includeArtifacts is used. * * @since 2.0 * @optional * @parameter property="excludeArtifactIds" default-value="" */ protected String excludeArtifactIds; /** * Comma Seperated list of Artifact names to include. * * @since 2.0 * @optional * @parameter property="includeArtifactIds" default-value="" */ protected String includeArtifactIds; /** * Comma Seperated list of GroupId Names to exclude. Ignored if includeGroupsIds is used. * * @since 2.0 * @optional * @parameter property="excludeGroupIds" default-value="" */ protected String excludeGroupIds; /** * Comma Seperated list of GroupIds to include. * * @since 2.0 * @optional * @parameter property="includeGroupIds" default-value="" */ protected String includeGroupIds; /** * The dependencies to list in file. */ private Collection<Artifact> dependenciesToList; /** * Destination file. */ private File projectDescriptorFile; /** * Mapping of artifacts with their corresponding dom-element. */ private Map<Entry, Element> entryMap = new HashMap<Entry, Element>(); /** * The value of the share-self attribute in the classloader template, if specified. */ private String shareSelf = null; public void execute() throws MojoExecutionException, MojoFailureException { try { getLog().info("Creating Dependency List..."); doDependencyResolution(); if (dependenciesToList.size() > 0) { createDestinationFile(); // lookup template file File file = new File(project.getBasedir(), templateDirectory + "/" + templateFileName); getLog().debug("Looking for classloader template in location '" + file + "'."); createDependencyListing(file); getLog().info( "Wrote " + dependenciesToList.size() + " dependencies to file " + projectDescriptorFile.getPath()); projectHelper.attachArtifact(project, "xml", "dependencylist", projectDescriptorFile); } } catch (Exception ex) { throw new MojoExecutionException("Error while creating dependency list.", ex); } } private void createDestinationFile() throws IOException { File targetDirectoryFile = new File(targetDirectory); if (!targetDirectoryFile.exists()) { targetDirectoryFile.mkdirs(); } projectDescriptorFile = new File(targetDirectoryFile, targetFileName); if (!projectDescriptorFile.exists()) { projectDescriptorFile.createNewFile(); } } /** * This method uses a Filtering technique as showed by the maven-dependency-plugin. It allows for * including/excluding artifacts in a number of ways. * * @throws MojoExecutionException */ @SuppressWarnings("unchecked") private void doDependencyResolution() throws ArtifactFilterException { FilterArtifacts filter = new FilterArtifacts(); filter.addFilter(new TransitivityFilter(project.getDependencyArtifacts(), this.excludeTransitive)); filter.addFilter(new ScopeFilter(this.includeScope, this.excludeScope)); filter.addFilter(new TypeFilter(this.includeTypes, this.excludeTypes)); filter.addFilter(new ClassifierFilter(this.includeClassifiers, this.excludeClassifiers)); filter.addFilter(new GroupIdFilter(this.includeGroupIds, this.excludeGroupIds)); filter.addFilter(new ArtifactIdFilter(this.includeArtifactIds, this.excludeArtifactIds)); // start with all artifacts. Set<Artifact> artifacts = project.getArtifacts(); if (includeSelf) { artifacts.add(project.getArtifact()); } // perform filtering dependenciesToList = filter.filter(artifacts); } /** * Create a project file containing the dependencies. * * @throws IOException * @throws SAXException */ private void createDependencyListing(File classloaderTemplate) throws IOException, SAXException { if(classloaderTemplate != null && classloaderTemplate.exists()) { getLog().info("Found classloader template, trying to parse it..."); parseClassloaderTemplate(classloaderTemplate); } final boolean hasEntries = entryMap.size() > 0; // fill in file with all dependencies FileOutputStream fos = new FileOutputStream(projectDescriptorFile); TransformerHandler ch = null; TransformerFactory factory = TransformerFactory.newInstance(); if (factory.getFeature(SAXTransformerFactory.FEATURE)) { try { ch = ((SAXTransformerFactory) factory).newTransformerHandler(); // set properties Transformer serializer = ch.getTransformer(); serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); serializer.setOutputProperty(OutputKeys.INDENT, "yes"); serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); // set result Result result = new StreamResult(fos); ch.setResult(result); // start file generation ch.startDocument(); AttributesImpl atts = new AttributesImpl(); if (this.shareSelf != null) { atts.addAttribute("", "share-self", "share-self", "CDATA", this.shareSelf); } ch.startElement("", "classloader", "classloader", atts); atts.clear(); ch.startElement("", "classpath", "classpath", atts); SortedSet<Artifact> sortedArtifacts = new TreeSet<Artifact>(dependenciesToList); Entry entry; String entryShare; String entryVersion; for (Artifact artifact : sortedArtifacts) { atts.addAttribute("", "groupId", "groupId", "CDATA", artifact.getGroupId()); atts.addAttribute("", "artifactId", "artifactId", "CDATA", artifact.getArtifactId()); if (artifact.getClassifier() != null) { atts.addAttribute("", "classifier", "classifier", "CDATA", artifact.getClassifier()); } if (!artifact.getGroupId().equals("org.lilyproject")) { atts.addAttribute("", "version", "version", "CDATA", artifact.getBaseVersion()); } entry = new Entry(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier()); if (hasEntries && entryMap.containsKey(entry)) { entryVersion = extractAttVal(entryMap.get(entry).getAttributes(), "version"); entryShare = extractAttVal(entryMap.get(entry).getAttributes(), "share"); entryMap.remove(entry); if (entryVersion != null && !entryVersion.equals("") && !entryVersion.equals(artifact.getBaseVersion())) { getLog().warn("version conflict between entry in template and artifact on classpath for " + entry); } if(entryShare != null) { atts.addAttribute("", "", "share", "CDATA", entryShare); } } else { // atts.addAttribute("", "", "share", "CDATA", SHARE_DEFAULT); } ch.startElement("", "artifact", "artifact", atts); ch.endElement("", "artifact", "artifact"); atts.clear(); } ch.endElement("", "classpath", "classpath"); ch.endElement("", "classloader", "classloader"); ch.endDocument(); fos.close(); if(entryMap.size() > 0) { getLog().warn("Classloader template contains entries that could not be resolved."); } } catch (TransformerConfigurationException ex) { ex.printStackTrace(); throw new SAXException("Unable to get a TransformerHandler."); } } else { throw new RuntimeException("Could not load SAXTransformerFactory."); } } private void parseClassloaderTemplate(File file) { DOMResult domResult = null; Transformer transformer = null; try { Source xmlSource = new StreamSource(file); TransformerFactory tfFactory = TransformerFactory.newInstance(); if (tfFactory.getFeature(DOMResult.FEATURE)) { transformer = tfFactory.newTransformer(); domResult = new DOMResult(); transformer.transform(xmlSource, domResult); } } catch (TransformerException ex) { throw new RuntimeException("Error parsing file '" + file + "'."); } if (domResult != null && transformer != null) { Document docu = (Document) domResult.getNode(); Element classpath; NodeList list; String shareSelf = docu.getDocumentElement().getAttribute("share-self").trim(); if (shareSelf.length() > 0) { this.shareSelf = shareSelf; } try { classpath = (Element) docu.getElementsByTagName("classpath").item(0); list = classpath.getElementsByTagName("artifact"); } catch (NullPointerException npex) { throw new RuntimeException("Classloader template is invalid."); } NamedNodeMap map; Entry entry; String groupId; String classifier; String artifactId; Element domArtifact; for (int i = 0; i < list.getLength(); i++) { domArtifact = (Element) list.item(i); map = domArtifact.getAttributes(); groupId = extractAttVal(map, "groupId"); artifactId = extractAttVal(map, "artifactId"); classifier = extractAttVal(map, "classifier"); entry = new Entry(groupId, artifactId, classifier); entryMap.put(entry, domArtifact); } } } private String extractAttVal(NamedNodeMap map, String name) { Node node = map.getNamedItem(name); return (node != null) ? node.getNodeValue() : null; } private class Entry { protected String groupId; protected String artifactId; protected String classifier; public Entry(String groupId, String artifactId, String classifier) { this.groupId = groupId; this.artifactId = artifactId; this.classifier = classifier; } /** * @see java.lang.Object#hashCode() */ public int hashCode() { return new HashCodeBuilder(715542779, 122039963).append(this.groupId).append(this.artifactId) .append(this.classifier).toHashCode(); } /** * @see java.lang.Object#equals(Object) */ public boolean equals(Object object) { if (!(object instanceof Entry)) { return false; } Entry rhs = (Entry) object; boolean equal = true; if (!this.groupId.equals(rhs.groupId)) { equal = false; } else if (!this.artifactId.equals(rhs.artifactId)) { equal = false; } else if (!ObjectUtils.equals(this.classifier, rhs.classifier)) { equal = false; } return equal; } @Override public String toString() { return "artifact(" + groupId + "," + artifactId + "," + classifier + ")"; } } }