001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol.sourcecontrols;
037:
038: import java.io.File;
039: import java.util.ArrayList;
040: import java.util.Date;
041: import java.util.HashSet;
042: import java.util.Iterator;
043: import java.util.List;
044: import java.util.Map;
045: import java.util.Set;
046:
047: import net.sourceforge.cruisecontrol.CruiseControlException;
048: import net.sourceforge.cruisecontrol.Modification;
049: import net.sourceforge.cruisecontrol.SourceControl;
050: import net.sourceforge.cruisecontrol.util.ValidationHelper;
051:
052: import org.apache.log4j.Logger;
053: import org.apache.maven.artifact.Artifact;
054: import org.apache.maven.artifact.repository.ArtifactRepository;
055: import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
056: import org.apache.maven.artifact.resolver.ArtifactResolutionException;
057: import org.apache.maven.cli.ConsoleDownloadMonitor;
058: import org.apache.maven.embedder.MavenEmbedder;
059: import org.apache.maven.embedder.MavenEmbedderConsoleLogger;
060: import org.apache.maven.embedder.MavenEmbedderException;
061: import org.apache.maven.model.Dependency;
062: import org.apache.maven.model.Model;
063: import org.apache.maven.project.MavenProject;
064: import org.apache.maven.project.ProjectBuildingException;
065: import org.apache.maven.wagon.events.TransferEvent;
066: import org.apache.maven.wagon.events.TransferListener;
067: import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
068:
069: /**
070: * Checks snapshot dependencies listed in a Maven2 pom against the local repositorty.
071: *
072: * Date: Feb 8, 2006
073: * Time: 9:15:47 PM
074: *
075: * @author Dan Rollo
076: */
077: public class Maven2SnapshotDependency implements SourceControl {
078:
079: /** enable logging for this class */
080: private static final Logger LOG = Logger
081: .getLogger(Maven2SnapshotDependency.class);
082:
083: private final SourceControlProperties properties = new SourceControlProperties();
084: private List modifications;
085: private File pomFile;
086: private String user;
087: private File localRepoDir; //@todo Must be null until maven embedder honors alignWithUserInstallation.
088:
089: static final String COMMENT_TIMESTAMP_CHANGE = " timestamp change detected: ";
090: static final String COMMENT_MISSING_IN_LOCALREPO = " missing in local repo: ";
091:
092: /**
093: * @param s the pom.xml file who's snapshot dependencies we are going to scan
094: */
095: public void setPomFile(String s) {
096: pomFile = new File(s);
097: }
098:
099: /**
100: * @param s the path for the local Maven repository.
101: * Normally, this is not set in order to use the default location: user.home/.m2/repository.
102: */
103: //@todo Make "public" when maven embedder honors alignWithUserInstallation
104: void setLocalRepository(String s) {
105: if (s != null) {
106: localRepoDir = new File(s);
107: } else {
108: localRepoDir = null;
109: }
110: }
111:
112: /**
113: * @param s the username listed with changes found in binary dependencies
114: */
115: public void setUser(String s) {
116: user = s;
117: }
118:
119: public void setProperty(String property) {
120: properties.assignPropertyName(property);
121: }
122:
123: public Map getProperties() {
124: return properties.getPropertiesAndReset();
125: }
126:
127: public void validate() throws CruiseControlException {
128:
129: ValidationHelper.assertIsSet(pomFile, "pomFile", this
130: .getClass());
131:
132: ValidationHelper.assertTrue(pomFile.exists(), "Pom file '"
133: + pomFile.getAbsolutePath() + "' does not exist.");
134:
135: ValidationHelper
136: .assertFalse(
137: pomFile.isDirectory(),
138: "The directory '"
139: + pomFile.getAbsolutePath()
140: + "' cannot be used as the pomFile for Maven2SnapshotDependency.");
141:
142: if (localRepoDir != null) {
143: ValidationHelper.assertTrue(localRepoDir.exists(),
144: "Local Maven repository '"
145: + localRepoDir.getAbsolutePath()
146: + "' does not exist.");
147:
148: ValidationHelper.assertTrue(localRepoDir.isDirectory(),
149: "Local Maven repository '"
150: + localRepoDir.getAbsolutePath()
151: + "' must be a directory.");
152: }
153: }
154:
155: /**
156: * The quiet period is ignored. All dependencies changed since the last
157: * build trigger a modification.
158: *
159: * @param lastBuild
160: * date of last build
161: * @param now
162: * IGNORED
163: */
164: public List getModifications(Date lastBuild, Date now) {
165:
166: modifications = new ArrayList();
167:
168: LOG.debug("Reading pom: " + pomFile.getAbsolutePath()
169: + " with lastBuild: " + lastBuild);
170:
171: ArtifactInfo[] artifactsToCheck = getSnapshotInfos();
172:
173: for (int i = 0; i < artifactsToCheck.length; i++) {
174: checkFile(artifactsToCheck[i], lastBuild.getTime());
175: }
176:
177: return modifications;
178: }
179:
180: /**
181: * Add a Modification to the list of modifications. All modifications are
182: * listed as "change" or "missing" if not in local repo.
183: * @param dependency snapshot detected as modified
184: * @param changeType modification type ("change" or "missing")
185: * @param comment constant note according to changeType
186: */
187: private void addRevision(File dependency, String changeType,
188: String comment) {
189: Modification newMod = new Modification("maven2");
190: Modification.ModifiedFile modfile = newMod.createModifiedFile(
191: dependency.getName(), dependency.getParent());
192: modfile.action = changeType;
193:
194: newMod.userName = user;
195: newMod.modifiedTime = new Date(dependency.lastModified());
196: newMod.comment = comment;
197: modifications.add(newMod);
198:
199: properties.modificationFound();
200: }
201:
202: /** Immutable data holder class. */
203: static final class ArtifactInfo {
204: static final String ART_TYPE_PARENT = "parent";
205: static final String ART_TYPE_DEPENDENCY = "dependency";
206:
207: private final Artifact artifact;
208: private final String artifactType;
209: private final File localRepoFile;
210:
211: private ArtifactInfo(final Artifact artifact,
212: final String artifactType, File localRepoFile) {
213: this .artifact = artifact;
214: this .artifactType = artifactType;
215: this .localRepoFile = localRepoFile;
216: }
217:
218: Artifact getArtifact() {
219: return artifact;
220: }
221:
222: String getArtifactType() {
223: return artifactType;
224: }
225:
226: File getLocalRepoFile() {
227: return localRepoFile;
228: }
229:
230: public String toString() {
231: return artifact
232: + ","
233: + artifactType
234: + ","
235: + (localRepoFile != null ? localRepoFile
236: .getAbsolutePath() : null);
237: }
238: }
239:
240: /**
241: * Return a file referring to the given artifact in the local repository.
242: * @param localRepoBaseDir the actual base dir of the active local repository
243: * @param artifact a artifact to be checked in the local repository
244: * @return a file referring to the given artifact in the local repository
245: */
246: //@todo Maybe we can delete this whole method after a while.
247: private static File getArtifactFilename(
248: final File localRepoBaseDir, final Artifact artifact) {
249:
250: LOG
251: .warn("We should not need this approach to finding artifact files. Artifact: "
252: + artifact);
253:
254: // Format:
255: // ${repo}/${groupId,dots as dirs}/${artifactId}/${version}/${artifactId}-${version}[-${classifier}].${type}
256: StringBuffer fileName = new StringBuffer();
257: fileName.append(localRepoBaseDir.getAbsolutePath());
258:
259: fileName.append('/');
260:
261: fileName.append(artifact.getGroupId().replace('.', '/'));
262:
263: fileName.append('/');
264:
265: final String artifactId = artifact.getArtifactId();
266: fileName.append(artifactId);
267:
268: fileName.append('/');
269:
270: final String versionText = artifact.getVersion();
271: fileName.append(versionText);
272:
273: fileName.append('/');
274:
275: fileName.append(artifactId);
276: fileName.append('-');
277: fileName.append(versionText);
278:
279: if (artifact.getClassifier() != null) {
280: fileName.append('-');
281: fileName.append(artifact.getClassifier());
282: }
283:
284: fileName.append('.');
285:
286: final String type = artifact.getType();
287: fileName.append(type != null ? type : "jar");
288:
289: //@todo Handle type="system" and "systemPath", or not if we can delete this whole method.
290:
291: return new File(fileName.toString());
292: }
293:
294: /**
295: * Parse the Maven pom file, and return snapshot artifact info populated with dependencies to be checked.
296: * @return return snapshot artifact info populated with dependencies to be checked
297: */
298: ArtifactInfo[] getSnapshotInfos() {
299:
300: final MavenEmbedder embedder = getMvnEmbedder();
301: try {
302:
303: // With readProjectWithDependencies(), local repo dependencies (+transitive) will be updated if possible
304: final MavenProject projectWithDependencies = getProjectWithDependencies(
305: embedder, pomFile);
306:
307: // use local repo dir from embedder because this is the dir it is actually using
308: final File localRepoBaseDir = new File(embedder
309: .getLocalRepository().getBasedir());
310:
311: final List artifactInfos = new ArrayList();
312:
313: // handle parents and grandparents...
314: findParentSnapshotArtifacts(projectWithDependencies,
315: artifactInfos, localRepoBaseDir, embedder, pomFile);
316:
317: // handle dependencies
318: final Set snapshotArtifacts;
319: if (projectWithDependencies != null) {
320:
321: // projectWithDependencies.getDependencyArtifacts() would exclude transitive artifacts
322: snapshotArtifacts = getSnaphotArtifacts(projectWithDependencies
323: .getArtifacts());
324:
325: } else {
326:
327: // couldn't read project, so try to do some stuff manually
328: snapshotArtifacts = getSnapshotArtifactsManually(embedder);
329: }
330: Artifact artifact;
331: for (Iterator i = snapshotArtifacts.iterator(); i.hasNext();) {
332: artifact = (Artifact) i.next();
333:
334: addArtifactInfo(artifactInfos, artifact,
335: ArtifactInfo.ART_TYPE_DEPENDENCY,
336: localRepoBaseDir);
337: }
338:
339: return (ArtifactInfo[]) artifactInfos
340: .toArray(new ArtifactInfo[artifactInfos.size()]);
341:
342: } finally {
343: try {
344: embedder.stop();
345: } catch (MavenEmbedderException e) {
346: LOG.error("Failed to stop embedded maven2", e);
347: }
348: }
349: }
350:
351: private static void findParentSnapshotArtifacts(
352: MavenProject projectWithDependencies, List artifactInfos,
353: File localRepoBaseDir, MavenEmbedder embedder, File pomFile) {
354: // handle parents and grandparents...
355: if (projectWithDependencies != null) {
356:
357: MavenProject currMvnProject = projectWithDependencies;
358:
359: Artifact parentArtifact = currMvnProject
360: .getParentArtifact();
361: while ((parentArtifact != null)
362: && parentArtifact.isSnapshot()) {
363:
364: addArtifactInfo(artifactInfos, parentArtifact,
365: ArtifactInfo.ART_TYPE_PARENT, localRepoBaseDir);
366: currMvnProject = currMvnProject.getParent();
367:
368: parentArtifact = currMvnProject.getParentArtifact();
369: }
370:
371: } else {
372:
373: // couldn't read project, so try to do some stuff manually
374: MavenProject mavenProject = null;
375: try {
376: mavenProject = embedder.readProject(pomFile);
377: } catch (ProjectBuildingException e) {
378: LOG.error("Failed to read maven2 mavenProject", e);
379: }
380:
381: if (mavenProject != null) {
382:
383: MavenProject currMvnProject = mavenProject;
384:
385: Artifact artifact = currMvnProject.getParentArtifact();
386: while ((artifact != null)
387: && (artifact.getVersion().endsWith(
388: Artifact.SNAPSHOT_VERSION) || artifact
389: .isSnapshot())) {
390:
391: addArtifactInfo(artifactInfos, artifact,
392: ArtifactInfo.ART_TYPE_PARENT,
393: localRepoBaseDir);
394:
395: resolveArtifact(embedder, artifact, mavenProject,
396: embedder.getLocalRepository());
397:
398: currMvnProject = currMvnProject.getParent();
399:
400: artifact = currMvnProject.getParentArtifact();
401: }
402: }
403: }
404: }
405:
406: private static MavenProject getProjectWithDependencies(
407: MavenEmbedder embedder, File pomFile) {
408: // With readProjectWithDependencies(), local repo dependencies (+transitive) will be updated if possible
409: MavenProject projectWithDependencies = null;
410: try {
411:
412: final TransferListener transferListener = new ConsoleDownloadMonitor() {
413: public void transferProgress(
414: TransferEvent transferEvent, byte[] buffer,
415: int length) {
416: // do nothing to avoid lot's of progress messages in logs
417: }
418: };
419:
420: projectWithDependencies = embedder
421: .readProjectWithDependencies(pomFile,
422: transferListener);
423:
424: } catch (ProjectBuildingException e) {
425: LOG.error("Failed to read maven2 projectWithDependencies",
426: e);
427: } catch (ArtifactResolutionException e) {
428: LOG.warn("Failed to resolve artifact", e);
429: } catch (ArtifactNotFoundException e) {
430: LOG.warn("Couldn't find artifact", e);
431: }
432: return projectWithDependencies;
433: }
434:
435: private static void resolveArtifact(MavenEmbedder embedder,
436: Artifact artifact, MavenProject mavenProject,
437: ArtifactRepository localRepo) {
438: try {
439: embedder.resolve(artifact, mavenProject
440: .getPluginArtifactRepositories(), localRepo);
441: } catch (ArtifactResolutionException e) {
442: LOG.debug("Unresolved artifact", e);
443: } catch (ArtifactNotFoundException e) {
444: LOG.debug("Missing artifact", e);
445: }
446: }
447:
448: private static void addArtifactInfo(List artifactInfos,
449: Artifact artifact, String artifactType,
450: File localRepoBaseDir) {
451:
452: final File file;
453: if (artifact.getFile() == null) {
454: file = getArtifactFilename(localRepoBaseDir, artifact);
455: } else {
456: file = artifact.getFile();
457: }
458:
459: artifactInfos
460: .add(new ArtifactInfo(artifact, artifactType, file));
461: }
462:
463: /**
464: * Filter out non-SNAPSHOT artifacts.
465: * @param artifacts all project artifacts, including non-SNAPSHOTS
466: * @return a set of artifacts containing only SNAPSHOTs
467: */
468: private static Set getSnaphotArtifacts(final Set artifacts) {
469:
470: final Set retVal = new HashSet();
471:
472: Artifact artifact;
473: for (Iterator i = artifacts.iterator(); i.hasNext();) {
474: artifact = (Artifact) i.next();
475: LOG.debug("Examining artifact: " + artifact);
476: if (artifact.isSnapshot()) {
477: retVal.add(artifact);
478: }
479: }
480:
481: return retVal;
482: }
483:
484: /**
485: * Doesn't handle transitive deps, nor actually download anything so far.
486: * @param embedder the maven embedder used to read the pomFile
487: * @return a set of artifacts containing only SNAPSHOTs
488: */
489: private Set getSnapshotArtifactsManually(
490: final MavenEmbedder embedder) {
491:
492: final MavenProject mavenProject;
493: try {
494: mavenProject = embedder.readProject(pomFile);
495: } catch (ProjectBuildingException e) {
496: LOG.error("Failed to read maven2 mavenProject", e);
497: return new HashSet();
498: }
499:
500: // override default repo if needed
501: final ArtifactRepository localRepo;
502: if (localRepoDir != null) {
503: try {
504: localRepo = embedder
505: .createLocalRepository(localRepoDir);
506: } catch (ComponentLookupException e) {
507: LOG.error("Error setting maven2 local repo to: "
508: + localRepoDir.getAbsolutePath(), e);
509: throw new RuntimeException(
510: "Error setting maven2 local repo to: "
511: + localRepoDir.getAbsolutePath() + "; "
512: + e.getMessage());
513: }
514: } else {
515: localRepo = embedder.getLocalRepository();
516: }
517:
518: // get snapshot dependencies
519: final Set snapshotArtifacts = getSnapshotDepsManually(embedder,
520: mavenProject);
521:
522: Artifact artifact;
523: for (Iterator i = snapshotArtifacts.iterator(); i.hasNext();) {
524: artifact = (Artifact) i.next();
525: LOG.debug("Manually examining artifact: " + artifact);
526: resolveArtifact(embedder, artifact, mavenProject, localRepo);
527: }
528:
529: return snapshotArtifacts;
530: }
531:
532: private static Set getSnapshotDepsManually(
533: final MavenEmbedder mavenEmbedder,
534: final MavenProject mavenProject) {
535: final Set retVal = new HashSet();
536:
537: // Not really sure if mavenEmbedder.readProject() is any better than mavenEmbedder.readModel()
538: // At this point, which ever is used, it should not update files in the local repo.
539:
540: /*
541: //final Set deps = mavenProject.getDependencyArtifacts(); // This returns null, how to init embedder correctly?
542: final List depsList = mavenProject.getDependencyManagement().getSnapshotDepsManually();
543: //*/
544:
545: //* // should not update files in the local repo.
546: final Model model = mavenProject.getModel();
547: final List depsList = model.getDependencies();
548: //*/
549:
550: LOG
551: .debug("found dependencies manually: "
552: + depsList.toString());
553: Dependency dep;
554: Artifact artifact;
555: for (int i = 0; i < depsList.size(); i++) {
556: dep = (Dependency) depsList.get(i);
557: if (dep.getVersion().endsWith(Artifact.SNAPSHOT_VERSION)) {
558: if (dep.getClassifier() != null) {
559: artifact = mavenEmbedder
560: .createArtifactWithClassifier(dep
561: .getGroupId(), dep.getArtifactId(),
562: dep.getVersion(), dep.getType(),
563: dep.getClassifier());
564: } else {
565: artifact = mavenEmbedder.createArtifact(dep
566: .getGroupId(), dep.getArtifactId(), dep
567: .getVersion(), dep.getScope(), dep
568: .getType());
569: }
570:
571: if (Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
572: // fill in systemPath for file
573: artifact
574: .setFile(new File(
575: dep.getSystemPath(),
576: artifact.getArtifactId()
577: + "-"
578: + artifact.getVersion()
579: + (artifact.getClassifier() != null ? "-"
580: + artifact
581: .getClassifier()
582: : "") + "."
583: + artifact.getType()));
584: }
585: retVal.add(artifact);
586: }
587: }
588:
589: return retVal;
590: }
591:
592: /**
593: * Check for newer timestamps, add modification if change detected.
594: * @param artifactInfo artifact data to be compared against the last build date to determine if modified
595: * @param lastBuild the last build date
596: */
597: private void checkFile(final ArtifactInfo artifactInfo,
598: long lastBuild) {
599: final File file = artifactInfo.localRepoFile;
600: LOG.debug("Checking artifact: " + artifactInfo.getArtifact());
601: if ((!file.isDirectory()) && (file.lastModified() > lastBuild)) {
602:
603: addRevision(file, "change", artifactInfo.artifactType
604: + COMMENT_TIMESTAMP_CHANGE
605: + artifactInfo.getArtifact().getArtifactId());
606:
607: } else if (!file.isDirectory() && !file.exists()) {
608:
609: addRevision(file, "missing", artifactInfo.artifactType
610: + COMMENT_MISSING_IN_LOCALREPO
611: + artifactInfo.getArtifact().getArtifactId());
612: }
613: }
614:
615: private MavenEmbedder getMvnEmbedder() {
616:
617: final MavenEmbedder mvnEmbedder = new MavenEmbedder();
618: final ClassLoader classLoader = Thread.currentThread()
619: .getContextClassLoader();
620: mvnEmbedder.setClassLoader(classLoader);
621: mvnEmbedder.setLogger(new MavenEmbedderConsoleLogger());
622:
623: // what do these really do?
624: //mvnEmbedder.setOffline(true);
625: //mvnEmbedder.setCheckLatestPluginVersion(false);
626: //mvnEmbedder.setUpdateSnapshots(false);
627: //mvnEmbedder.setInteractiveMode(false);
628: //mvnEmbedder.setUsePluginRegistry(false);
629: //mvnEmbedder.setPluginUpdateOverride(true);
630:
631: if (localRepoDir != null) {
632: mvnEmbedder.setLocalRepositoryDirectory(localRepoDir);
633: mvnEmbedder.setAlignWithUserInstallation(false);
634: } else {
635: mvnEmbedder.setAlignWithUserInstallation(true);
636: }
637:
638: try {
639: // embedder start can take a long time when debugging
640: mvnEmbedder.start();
641: } catch (MavenEmbedderException e) {
642: LOG.error("Failed to start embedded maven2", e);
643: }
644:
645: return mvnEmbedder;
646: }
647: }
|