001: package org.andromda.maven.plugin.distribution;
002:
003: import java.io.File;
004: import java.io.FileNotFoundException;
005: import java.io.FileReader;
006: import java.io.IOException;
007:
008: import java.text.Collator;
009:
010: import java.util.ArrayList;
011: import java.util.Collections;
012: import java.util.Comparator;
013: import java.util.Iterator;
014: import java.util.LinkedHashSet;
015: import java.util.List;
016: import java.util.ListIterator;
017: import java.util.Set;
018:
019: import org.apache.maven.archiver.MavenArchiveConfiguration;
020: import org.apache.maven.archiver.MavenArchiver;
021: import org.apache.maven.artifact.Artifact;
022: import org.apache.maven.artifact.factory.ArtifactFactory;
023: import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
024: import org.apache.maven.artifact.repository.ArtifactRepository;
025: import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
026: import org.apache.maven.artifact.resolver.ArtifactResolver;
027: import org.apache.maven.execution.MavenSession;
028: import org.apache.maven.model.Build;
029: import org.apache.maven.model.Model;
030: import org.apache.maven.model.Parent;
031: import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
032: import org.apache.maven.plugin.AbstractMojo;
033: import org.apache.maven.plugin.MojoExecutionException;
034: import org.apache.maven.plugin.MojoFailureException;
035: import org.apache.maven.profiles.DefaultProfileManager;
036: import org.apache.maven.project.MavenProject;
037: import org.apache.maven.project.MavenProjectBuilder;
038: import org.codehaus.plexus.archiver.jar.JarArchiver;
039: import org.codehaus.plexus.util.DirectoryScanner;
040: import org.codehaus.plexus.util.FileUtils;
041: import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
042:
043: /**
044: * A Mojo for assembling a distribution.
045: *
046: * @goal assemble
047: * @phase validate
048: * @author Chad Brandon
049: */
050: public class AssembleMojo extends AbstractMojo {
051: /**
052: * The name of the distribution
053: *
054: * @parameter
055: * @required
056: */
057: private String name;
058:
059: /**
060: * Directory that resources are copied to during the build.
061: *
062: * @parameter expression="${project.build.directory}"
063: * @required
064: */
065: private String workDirectory;
066:
067: /**
068: * The maven project.
069: *
070: * @parameter expression="${project}"
071: * @required
072: * @readonly
073: * @description "the maven project to use"
074: */
075: private MavenProject project;
076:
077: /**
078: * Artifact factory, needed to download source jars for inclusion in
079: * classpath.
080: *
081: * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
082: * @required
083: * @readonly
084: */
085: private ArtifactFactory artifactFactory;
086:
087: /**
088: * Artifact resolver, needed to download source jars for inclusion in
089: * classpath.
090: *
091: * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
092: * @required
093: * @readonly
094: */
095: private ArtifactResolver artifactResolver;
096:
097: /**
098: * @parameter expression="${localRepository}"
099: * @required
100: * @readonly
101: */
102: protected ArtifactRepository localRepository;
103:
104: /**
105: * The archiver.
106: *
107: * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
108: * @required
109: */
110: private JarArchiver binArchiver;
111:
112: /**
113: * @component
114: */
115: private ArtifactMetadataSource artifactMetadataSource;
116:
117: /**
118: * The maven archiver to use.
119: *
120: * @parameter
121: */
122: private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
123:
124: /**
125: * Used to contruct Maven project instances from POMs.
126: *
127: * @component
128: */
129: private MavenProjectBuilder projectBuilder;
130:
131: /**
132: * @parameter expression="${session}"
133: */
134: private MavenSession session;
135:
136: /**
137: * Defines the POMs who's artifacts will be included in the distribution.
138: *
139: * @parameter
140: */
141: private String[] projectIncludes = new String[0];
142:
143: /**
144: * Defines the POMs who's artifacts will be excluded from the distribution.
145: *
146: * @parameter
147: */
148: private String[] projectExcludes = new String[0];
149:
150: /**
151: * The directory in which project artifacts are bundled.
152: *
153: * @parameter
154: */
155: private String artifactDirectory;
156:
157: /**
158: * The directory in which dependencies of project artifacts are bundled.
159: *
160: * @parameter
161: */
162: private String dependencyDirectory;
163:
164: /**
165: * The artifacts that can be excluded from the distribution.
166: *
167: * @parameter
168: */
169: private ArtifactFilter[] artifactExcludes;
170:
171: /**
172: * The locations to include when creating the distribution.
173: *
174: * @parameter
175: */
176: private Location[] locations;
177:
178: /**
179: * All artifacts that are collected and bundled.
180: */
181: private final Set allArtifacts = new LinkedHashSet();
182:
183: /**
184: * The forward slash character.
185: */
186: private static final String FORWARD_SLASH = "/";
187:
188: /**
189: * The extension to give the distribution.
190: *
191: * @parameter expression="zip"
192: * @required
193: */
194: private String extension;
195:
196: /**
197: * @see org.apache.maven.plugin.Mojo#execute()
198: */
199: public void execute() throws MojoExecutionException,
200: MojoFailureException {
201: this .allArtifacts.clear();
202: try {
203: final File distribution = new File(this .workDirectory,
204: this .name + '.' + this .extension);
205: this .getLog().info("Building distribution " + distribution);
206: final File directory = this .getDistributionDirectory();
207: directory.mkdirs();
208: final List artifactList = new ArrayList();
209: final Set projects = this .collectProjects();
210: if (!projects.isEmpty()) {
211: projects.add(this .getRootProject());
212: final Set artifacts = new LinkedHashSet();
213: for (final Iterator iterator = projects.iterator(); iterator
214: .hasNext();) {
215: final MavenProject project = (MavenProject) iterator
216: .next();
217: final Artifact artifact = this .artifactFactory
218: .createArtifact(project.getGroupId(),
219: project.getArtifactId(), project
220: .getVersion(), null,
221: project.getPackaging());
222: final String artifactPath = this .localRepository
223: .pathOf(artifact);
224: final String artifactFileName = artifactPath
225: .replaceAll(".*(\\\\|/+)", "");
226: final String repositoryDirectoryPath = artifactPath
227: .substring(0, artifactPath
228: .indexOf(artifactFileName));
229: final Build build = project.getBuild();
230: final File workDirectory = new File(build
231: .getDirectory());
232: final File distributionDirectory = new File(
233: new File(directory, this .artifactDirectory),
234: repositoryDirectoryPath);
235: if (workDirectory.exists()) {
236: final String finalName = build.getFinalName();
237: final String[] names = workDirectory.list();
238: if (names != null) {
239: final int numberOfArtifacts = names.length;
240: for (int ctr = 0; ctr < numberOfArtifacts; ctr++) {
241: final String name = names[ctr];
242: if (name.indexOf(finalName) != -1
243: && !name.equals(finalName)) {
244: final File distributionFile = new File(
245: distributionDirectory, name);
246: this .bundleFile(artifact, new File(
247: workDirectory, name),
248: distributionFile);
249: }
250: }
251: }
252: } else {
253: this .artifactResolver
254: .resolve(
255: artifact,
256: this .project
257: .getRemoteArtifactRepositories(),
258: this .localRepository);
259: }
260:
261: // - bundle the POM
262: final File repositoryPom = this .constructPom(
263: new File(this .localRepository.getBasedir(),
264: repositoryDirectoryPath), artifact);
265: final File distributionPom = this .constructPom(
266: distributionDirectory, artifact);
267: this .bundleFile(artifact, repositoryPom,
268: distributionPom);
269: artifacts.addAll(project.createArtifacts(
270: artifactFactory, null, null));
271: }
272:
273: final ArtifactResolutionResult result = this .artifactResolver
274: .resolveTransitively(artifacts, this .project
275: .getArtifact(), this .project
276: .getRemoteArtifactRepositories(),
277: this .localRepository,
278: this .artifactMetadataSource);
279:
280: artifacts.addAll(result.getArtifacts());
281:
282: // - remove the project artifacts
283: for (final Iterator iterator = projects.iterator(); iterator
284: .hasNext();) {
285: final MavenProject project = (MavenProject) iterator
286: .next();
287: final Artifact projectArtifact = project
288: .getArtifact();
289: if (projectArtifact != null) {
290: for (final Iterator artifactIterator = artifacts
291: .iterator(); artifactIterator.hasNext();) {
292: final Artifact artifact = (Artifact) artifactIterator
293: .next();
294: final String projectId = projectArtifact
295: .getArtifactId();
296: final String projectGroupId = projectArtifact
297: .getGroupId();
298: final String artifactId = artifact
299: .getArtifactId();
300: final String groupId = artifact
301: .getGroupId();
302: if (artifactId.equals(projectId)
303: && groupId.equals(projectGroupId)) {
304: artifactIterator.remove();
305: }
306: }
307: }
308: }
309:
310: // - bundle the dependant artifacts
311: for (final Iterator iterator = artifacts.iterator(); iterator
312: .hasNext();) {
313: final Artifact artifact = (Artifact) iterator
314: .next();
315: this .bundleArtifact(new File(directory,
316: this .dependencyDirectory), artifact);
317: }
318:
319: artifactList.addAll(this .allArtifacts);
320:
321: Collections
322: .sort(artifactList, new ArtifactComparator());
323: for (final Iterator iterator = artifactList.iterator(); iterator
324: .hasNext();) {
325: this .getLog().info(
326: "bundled: "
327: + ((Artifact) iterator.next())
328: .getId());
329: }
330: this .getLog()
331: .info(
332: "Bundled " + artifactList.size()
333: + " artifacts");
334: }
335:
336: int bundledFilesCount = 0;
337:
338: // - now include all paths found in the given locations
339: if (this .locations != null) {
340: final int numberOfLocations = this .locations.length;
341: for (int ctr = 0; ctr < numberOfLocations; ctr++) {
342: final Location location = this .locations[ctr];
343: final List paths = location.getPaths();
344: if (paths != null) {
345: for (final Iterator iterator = paths.iterator(); iterator
346: .hasNext();) {
347: final String path = (String) iterator
348: .next();
349: final File file = location.getFile(path);
350: File destination = null;
351: final String outputPath = location
352: .getOuputPath();
353: if (outputPath != null
354: && outputPath.trim().length() > 0) {
355: final File outputPathFile = new File(
356: this .getDistributionDirectory(),
357: outputPath);
358:
359: // - directories must end with a slash
360: if (outputPath.endsWith(FORWARD_SLASH)) {
361: destination = new File(
362: outputPathFile, path);
363: } else {
364: destination = outputPathFile;
365: }
366: } else {
367: destination = new File(this
368: .getDistributionDirectory(),
369: path);
370: }
371: if (destination != null) {
372: FileUtils.copyFile(file, destination);
373: if (this .getLog().isDebugEnabled()) {
374: this .getLog().debug(
375: "bundled: " + destination);
376: }
377: bundledFilesCount++;
378: }
379: }
380: }
381: }
382: }
383:
384: this .getLog().info(
385: "Bundled " + bundledFilesCount + " files");
386:
387: final MavenArchiver archiver = new MavenArchiver();
388: archiver.setArchiver(this .binArchiver);
389: archiver.setOutputFile(distribution);
390:
391: archiver.getArchiver().addDirectory(directory,
392: new String[] { "**/*" }, null);
393:
394: // - create archive
395: archiver.createArchive(this .project, this .archive);
396: } catch (final Throwable throwable) {
397: throw new MojoExecutionException(
398: "Error assembling distribution", throwable);
399: }
400: }
401:
402: /**
403: * The root project.
404: */
405: private MavenProject rootProject;
406:
407: /**
408: * Retrieves the root project (i.e. the root parent project)
409: * for this project.
410: *
411: * @return the root project.
412: * @throws MojoExecutionException
413: */
414: private MavenProject getRootProject() throws MojoExecutionException {
415: if (this .rootProject == null) {
416: MavenProject root = null;
417: for (root = this .project.getParent(); root.getParent() != null; root = root
418: .getParent()) {
419: ;
420: }
421: if (root == null) {
422: throw new MojoExecutionException(
423: "No parent could be retrieved for project --> "
424: + this .project.getId()
425: + "', you must specify a parent project");
426: }
427: this .rootProject = root;
428: }
429: return this .rootProject;
430: }
431:
432: /**
433: * Bundles the file from the given <code>artifact</code> into the given
434: * <code>destinationDirectory</code>.
435: *
436: * @param destinationDirectory the directory to which the artifact is
437: * bundled.
438: * @param artifact the artifact to bundle.
439: * @throws IOException
440: */
441: private void bundleArtifact(final File destinationDirectory,
442: final Artifact artifact) throws IOException {
443: File artifactFile = artifact.getFile();
444: if (artifactFile == null) {
445: artifactFile = new File(this .localRepository.getBasedir(),
446: this .localRepository.pathOf(artifact));
447: }
448: final String artifactPath = this .localRepository
449: .pathOf(artifact);
450: final String artifactFileName = artifactPath.replaceAll(
451: ".*(\\\\|/+)", "");
452: final String repositoryDirectoryPath = artifactPath.substring(
453: 0, artifactPath.indexOf(artifactFileName));
454: final File dependencyDirectory = new File(destinationDirectory,
455: repositoryDirectoryPath);
456: this .bundleFile(artifact, artifactFile, new File(
457: destinationDirectory, repositoryDirectoryPath + '/'
458: + artifactFile.getName()));
459: final File repositoryPom = this .constructPom(new File(
460: this .localRepository.getBasedir(),
461: repositoryDirectoryPath), artifact);
462:
463: if (repositoryPom.exists()) {
464: final File distributionPom = this .constructPom(
465: dependencyDirectory, artifact);
466: this .bundleFile(artifact, repositoryPom, distributionPom);
467: }
468: }
469:
470: /**
471: * Constructs the POM file given the <code>directory</code> and the
472: * <code>artifact</code>.
473: *
474: * @param directory the directory.
475: * @param artifact the artifact.
476: * @return the POM file.
477: */
478: private final File constructPom(final File directory,
479: final Artifact artifact) {
480: return new File(directory, artifact.getArtifactId() + '-'
481: + artifact.getVersion() + '.' + POM_TYPE);
482: }
483:
484: /**
485: * Copies the given <code>file</code> to the given
486: * <code>destination</code>.
487: *
488: * @param artifact the artifact that is being bundled.
489: * @param file the file to bundle.
490: * @param destination the destination to which we'll bundle.
491: * @throws IOException
492: */
493: private void bundleFile(final Artifact artifact, final File file,
494: final File destination) throws IOException {
495: boolean writable = true;
496: if (this .artifactExcludes != null) {
497: final String artifactGroupId = artifact.getGroupId();
498: final String artifactArtifactId = artifact.getArtifactId();
499: final int numberOfArtifactExcludes = this .artifactExcludes.length;
500: for (int ctr = 0; ctr < numberOfArtifactExcludes; ctr++) {
501: final ArtifactFilter artifactExclude = this .artifactExcludes[ctr];
502: if (artifactExclude != null) {
503: final String groupId = artifactExclude.getGroupId();
504: final String artifactId = artifactExclude
505: .getArtifactId();
506: final boolean groupIdPresent = groupId != null;
507: final boolean artifactIdPresent = artifactId != null;
508: if (groupIdPresent) {
509: writable = !artifactGroupId.matches(groupId);
510: if (!writable && artifactIdPresent) {
511: writable = !artifactArtifactId
512: .matches(artifactId);
513: }
514: } else if (artifactIdPresent) {
515: writable = !artifactGroupId.matches(artifactId);
516: }
517: }
518: }
519: }
520: if (writable) {
521: this .allArtifacts.add(artifact);
522: FileUtils.copyFile(file, destination);
523: } else {
524: if (this .getLog().isDebugEnabled()) {
525: this .getLog().debug("Excluding: " + artifact.getId());
526: }
527: }
528: }
529:
530: /**
531: * Retrieves all the POMs for the given project.
532: *
533: * @return all poms found.
534: * @throws MojoExecutionException
535: * @throws MojoExecutionException
536: */
537: private List getPoms() throws MojoExecutionException {
538: List poms = new ArrayList();
539: if (this .projectIncludes != null
540: && this .projectIncludes.length > 0) {
541: final File baseDirectory = this .getRootProject()
542: .getBasedir();
543: final DirectoryScanner scanner = new DirectoryScanner();
544: scanner.setBasedir(baseDirectory);
545: scanner.setIncludes(this .projectIncludes);
546: scanner.setExcludes(this .projectExcludes);
547: scanner.scan();
548: for (int ctr = 0; ctr < scanner.getIncludedFiles().length; ctr++) {
549: final File pom = new File(baseDirectory, scanner
550: .getIncludedFiles()[ctr]);
551: if (pom.exists()) {
552: poms.add(pom);
553: }
554: }
555: }
556: return poms;
557: }
558:
559: /**
560: * Retrieves the MavenProject for the given <code>pom</code>.
561: *
562: * @return the maven POM file.
563: * @throws MojoExecutionException
564: * @throws MojoExecutionException
565: */
566: private MavenProject getProject(final File pom)
567: throws MojoExecutionException {
568: // - first attempt to get the existing project from the session
569: MavenProject project = this .getProjectFromSession(pom);
570: if (project == null) {
571: // - if we didn't find it in the session, create it
572: try {
573: project = this .projectBuilder.build(pom, this .session
574: .getLocalRepository(),
575: new DefaultProfileManager(this .session
576: .getContainer()));
577: } catch (Exception exception) {
578: try {
579: // - if we failed, try to build from the repository
580: project = this .projectBuilder.buildFromRepository(
581: this .buildArtifact(pom), this .project
582: .getRemoteArtifactRepositories(),
583: this .localRepository);
584: } catch (final Throwable throwable) {
585: throw new MojoExecutionException(
586: "Project could not be built from pom file "
587: + pom, exception);
588: }
589: }
590: }
591: if (this .getLog().isDebugEnabled()) {
592: this .getLog()
593: .debug("Processing project " + project.getId());
594: }
595: return project;
596: }
597:
598: /**
599: * Constructs an artifact from the given <code>pom</code> file.
600: *
601: * @param pom the POM from which to construct the artifact.
602: * @return the built artifact
603: * @throws FileNotFoundException
604: * @throws IOException
605: * @throws XmlPullParserException
606: */
607: private Artifact buildArtifact(final File pom)
608: throws FileNotFoundException, IOException,
609: XmlPullParserException {
610: final MavenXpp3Reader reader = new MavenXpp3Reader();
611: final Model model = reader.read(new FileReader(pom));
612: String groupId = model.getGroupId();
613: for (Parent parent = model.getParent(); groupId == null
614: && model.getParent() != null; parent = model
615: .getParent()) {
616: groupId = parent.getGroupId();
617: }
618: String version = model.getVersion();
619: for (Parent parent = model.getParent(); version == null
620: && model.getParent() != null; parent = model
621: .getParent()) {
622: version = parent.getVersion();
623: }
624: return this .artifactFactory.createArtifact(groupId, model
625: .getArtifactId(), version, null, model.getPackaging());
626: }
627:
628: /**
629: * The POM file name.
630: */
631: private static final String POM_FILE = "pom.xml";
632:
633: /**
634: * Attempts to retrieve the Maven project for the given <code>pom</code>.
635: *
636: * @param pom the POM to find.
637: * @return the maven project with the matching POM.
638: */
639: private MavenProject getProjectFromSession(final File pom) {
640: MavenProject foundProject = null;
641: for (final Iterator projectIterator = this .session
642: .getSortedProjects().iterator(); projectIterator
643: .hasNext();) {
644: final MavenProject project = (MavenProject) projectIterator
645: .next();
646: final File projectPom = new File(project.getBasedir(),
647: POM_FILE);
648: if (projectPom.equals(pom)) {
649: foundProject = project;
650: }
651: }
652: return foundProject;
653: }
654:
655: /**
656: * Collects all projects from all POMs within the current project.
657: *
658: * @return all collection Maven project instances.
659: * @throws MojoExecutionException
660: */
661: private Set collectProjects() throws MojoExecutionException {
662: final Set projects = new LinkedHashSet();
663: final List poms = this .getPoms();
664: for (ListIterator iterator = poms.listIterator(); iterator
665: .hasNext();) {
666: final File pom = (File) iterator.next();
667: final MavenProject project = this .getProject(pom);
668: if (project != null) {
669: projects.add(project);
670: }
671: }
672: return projects;
673: }
674:
675: /**
676: * The POM artifact type.
677: */
678: private static final String POM_TYPE = "pom";
679:
680: /**
681: * Used to sort artifacts by <code>id</code>.
682: */
683: private final static class ArtifactComparator implements Comparator {
684: private final Collator collator = Collator.getInstance();
685:
686: private ArtifactComparator() {
687: collator.setStrength(Collator.PRIMARY);
688: }
689:
690: public int compare(final Object objectA, final Object objectB) {
691: final Artifact a = (Artifact) objectA;
692: final Artifact b = (Artifact) objectB;
693: return collator.compare(a.getId(), b.getId());
694: }
695: }
696:
697: /**
698: * Gets the directory to which the output is written for the binary
699: * distribution.
700: *
701: * @return the directory output distribution.
702: */
703: private File getDistributionDirectory() {
704: return new File(this .workDirectory + '/' + this.name);
705: }
706: }
|