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.BufferedInputStream;
039: import java.io.File;
040: import java.io.FileInputStream;
041: import java.io.IOException;
042: import java.util.ArrayList;
043: import java.util.Date;
044: import java.util.Iterator;
045: import java.util.List;
046: import java.util.Map;
047: import java.util.Properties;
048:
049: import net.sourceforge.cruisecontrol.CruiseControlException;
050: import net.sourceforge.cruisecontrol.Modification;
051: import net.sourceforge.cruisecontrol.SourceControl;
052: import net.sourceforge.cruisecontrol.util.ValidationHelper;
053: import net.sourceforge.cruisecontrol.util.IO;
054:
055: import org.apache.log4j.Logger;
056: import org.jdom.Element;
057: import org.jdom.JDOMException;
058: import org.jdom.Namespace;
059: import org.jdom.input.SAXBuilder;
060:
061: /**
062: * Checks binary dependencies listed in a Maven project rather than in a
063: * repository.
064: *
065: * <pre>
066: * Modifications 20060626 (jarkko.viinamaki at removethis.tietoenator.com):
067: * - made POM scanning namespace aware. Dependencies were not detected if project.xml
068: * had schema definition in the project element
069: * - added support for "ejb-client" dependency type
070: * - added echo for detected snapshot dependencies
071: * - added support for build.properties or other similiar properties file which contains
072: * key=value tags to replace ${key} type strings in project.xml
073: * Modifications 20060627
074: * - fixed a bug in replaceVariables method
075: * </pre>
076: *
077: * <at> author Tim Shadel
078: */
079: public class MavenSnapshotDependency implements SourceControl {
080:
081: private SourceControlProperties properties = new SourceControlProperties();
082: private List modifications;
083: private File projectFile;
084: private File propertiesFile;
085: private File localRepository = new File(System
086: .getProperty("user.home")
087: + "/.maven/repository/");
088: private String user;
089:
090: /** enable logging for this class */
091: private static Logger log = Logger
092: .getLogger(MavenSnapshotDependency.class);
093:
094: /**
095: * Set the root folder of the directories that we are going to scan
096: */
097: public void setProjectFile(String s) {
098: projectFile = new File(s);
099: }
100:
101: /**
102: * Sets the .properties file which contains overriding tags for POM.
103: *
104: * Default is build.properties
105: */
106: public void setPropertiesFile(String s) {
107: propertiesFile = new File(s);
108: }
109:
110: /**
111: * Set the path for the local Maven repository
112: */
113: public void setLocalRepository(String s) {
114: localRepository = new File(s);
115: }
116:
117: /**
118: * Set the username listed with changes found in binary dependencies
119: */
120: public void setUser(String s) {
121: user = s;
122: }
123:
124: public void setProperty(String property) {
125: properties.assignPropertyName(property);
126: }
127:
128: public Map getProperties() {
129: return properties.getPropertiesAndReset();
130: }
131:
132: public void validate() throws CruiseControlException {
133: ValidationHelper.assertIsSet(projectFile, "projectFile", this
134: .getClass());
135: ValidationHelper.assertTrue(projectFile.exists(),
136: "Project file '" + projectFile.getAbsolutePath()
137: + "' does not exist.");
138: ValidationHelper
139: .assertFalse(
140: projectFile.isDirectory(),
141: "The directory '"
142: + projectFile.getAbsolutePath()
143: + "' cannot be used as the projectFile for MavenSnapshotDependency.");
144:
145: ValidationHelper.assertTrue(localRepository.exists(),
146: "Local Maven repository '"
147: + localRepository.getAbsolutePath()
148: + "' does not exist.");
149: ValidationHelper.assertTrue(localRepository.isDirectory(),
150: "Local Maven repository '"
151: + localRepository.getAbsolutePath()
152: + "' must be a directory.");
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: modifications = new ArrayList();
166:
167: checkProjectDependencies(projectFile, lastBuild.getTime());
168:
169: return modifications;
170: }
171:
172: /**
173: * Add a Modification to the list of modifications. All modifications are
174: * listed as "change" and all have the same comment.
175: */
176: private void addRevision(File dependency) {
177: Modification mod = new Modification("maven");
178: Modification.ModifiedFile modfile = mod.createModifiedFile(
179: dependency.getName(), dependency.getParent());
180: modfile.action = "change";
181:
182: mod.userName = user;
183: mod.modifiedTime = new Date(dependency.lastModified());
184: mod.comment = "Maven project dependency: timestamp change detected.";
185: modifications.add(mod);
186:
187: properties.modificationFound();
188: }
189:
190: /**
191: * Use Maven library to open project file and check for newer dependencies.
192: * Do not download them, only list them as modifications.
193: */
194: private void checkProjectDependencies(File projectFile,
195: long lastBuild) {
196: List filenames = getSnapshotFilenames(projectFile);
197: Iterator itr = filenames.iterator();
198: while (itr.hasNext()) {
199: String filename = (String) itr.next();
200: File dependency = new File(filename);
201: checkFile(dependency, lastBuild);
202: }
203: }
204:
205: /**
206: * Replaces variables in a string defined as ${key}.
207: *
208: * Values for variables are taken from given properties or System properties.
209: * Replacement is recursive. If ${key} maps to a string which has other ${keyN} values,
210: * those ${keyN} values are replaced also if there is a matching value for them.
211: */
212: String replaceVariables(Properties p, String value) {
213: if (value == null || p == null) {
214: return value;
215: }
216:
217: int i = value.indexOf("${");
218: if (i == -1) {
219: return value;
220: }
221: int pos = 0;
222: while (i != -1) {
223: int j = value.indexOf("}", i);
224: if (j == -1) {
225: break;
226: }
227: String key = value.substring(i + 2, j);
228: // log.info("Tag: " + key);
229:
230: if (p.containsKey(key)) {
231: value = value.substring(0, i) + p.getProperty(key)
232: + value.substring(j + 1);
233: // step one forward from ${ position, otherwise we can get an infinite loop
234: pos = i + 1;
235: } else if (System.getProperty(key) != null) {
236: value = value.substring(0, i) + System.getProperty(key)
237: + value.substring(j + 1);
238: pos = i + 1;
239: } else {
240: // could not replace the value, leave it there
241: pos = j + 1;
242: }
243: // log.info("New value: " + value);
244:
245: i = value.indexOf("${", pos);
246: }
247: return value;
248: }
249:
250: /**
251: * Parse the Maven project file, and file names
252: */
253: List getSnapshotFilenames(File mavenFile) {
254: log.info("Getting a list of dependencies for " + mavenFile);
255:
256: List filenames = new ArrayList();
257: Element mavenElement;
258: SAXBuilder builder = new SAXBuilder(
259: "org.apache.xerces.parsers.SAXParser");
260: try {
261: mavenElement = builder.build(mavenFile).getRootElement();
262: } catch (JDOMException e) {
263: log.error("failed to load project file ["
264: + (mavenFile != null ? mavenFile.getAbsolutePath()
265: : "") + "]", e);
266: return filenames;
267: } catch (IOException e) {
268: log.error("failed to load project file ["
269: + (mavenFile != null ? mavenFile.getAbsolutePath()
270: : "") + "]", e);
271: return filenames;
272: }
273:
274: // load the project properties file if it exists
275: Properties projectProperties = new Properties();
276:
277: if (propertiesFile == null) {
278: propertiesFile = new File(mavenFile.getParent()
279: + "/build.properties");
280: }
281:
282: if (propertiesFile.exists()) {
283:
284: BufferedInputStream in = null;
285: try {
286: FileInputStream fin = new FileInputStream(
287: propertiesFile);
288: in = new BufferedInputStream(fin);
289: projectProperties.load(in);
290: } catch (IOException ex) {
291: log.error("failed to load project properties file ["
292: + propertiesFile.getAbsolutePath() + "]", ex);
293: } finally {
294: IO.close(in);
295: }
296: }
297:
298: // set some default properties
299: projectProperties.put("basedir", mavenFile.getParent());
300:
301: // JAR overrides are currently not implemented. Some guidelines how to do it:
302: // http://jira.public.thoughtworks.org/browse/CC-141
303: /*
304: boolean mavenJarOverride = false;
305:
306: String tmp = projectProperties.getProperty("maven.jar.override");
307: if (tmp != null && (tmp.equalsIgnoreCase("on") || tmp.equalsIgnoreCase("true"))) {
308: mavenJarOverride = true;
309: }
310: */
311:
312: Namespace ns = mavenElement.getNamespace();
313:
314: Element depsRoot = mavenElement.getChild("dependencies", ns);
315:
316: // No dependencies listed at all
317: if (depsRoot == null) {
318: log.warn("No dependencies detected.");
319: return filenames;
320: }
321:
322: List dependencies = depsRoot.getChildren();
323: Iterator itr = dependencies.iterator();
324: while (itr.hasNext()) {
325: Element dependency = (Element) itr.next();
326: String versionText = dependency.getChildText("version", ns);
327:
328: if (versionText != null && versionText.endsWith("SNAPSHOT")) {
329: String groupId = dependency.getChildText("groupId", ns);
330: String artifactId = dependency.getChildText(
331: "artifactId", ns);
332: String id = dependency.getChildText("id", ns);
333: String type = dependency.getChildText("type", ns);
334:
335: // replace variables
336: artifactId = replaceVariables(projectProperties,
337: artifactId);
338: groupId = replaceVariables(projectProperties, groupId);
339: id = replaceVariables(projectProperties, id);
340: versionText = replaceVariables(projectProperties,
341: versionText);
342:
343: if (type == null) {
344: type = "jar";
345: }
346:
347: // Format:
348: // ${repo}/${groupId}/${type}s/${artifactId}-${version}.${type}
349: StringBuffer fileName = new StringBuffer();
350: fileName.append(localRepository.getAbsolutePath());
351: fileName.append('/');
352: if (groupId != null) {
353: fileName.append(groupId);
354: } else {
355: fileName.append(id);
356: }
357: fileName.append('/');
358:
359: if ("ejb-client".equals(type)) {
360: fileName.append("ejb");
361: } else {
362: fileName.append(type);
363: }
364: fileName.append('s');
365: fileName.append('/');
366: if (artifactId != null) {
367: fileName.append(artifactId);
368: } else {
369: fileName.append(id);
370: }
371: fileName.append('-');
372: fileName.append(versionText);
373:
374: if ("ejb-client".equals(type)) {
375: fileName.append("-client");
376: }
377:
378: fileName.append('.');
379: if ("uberjar".equals(type) || "ejb".equals(type)
380: || "plugin".equals(type)
381: || "ejb-client".equals(type)) {
382: fileName.append("jar");
383: } else {
384: fileName.append(type);
385: }
386:
387: File file = new File(fileName.toString());
388:
389: log.info("Snapshot detected: " + fileName);
390:
391: filenames.add(file.getAbsolutePath());
392: }
393: }
394: return filenames;
395: }
396:
397: /** Check for newer timestamps */
398: private void checkFile(File file, long lastBuild) {
399: if ((!file.isDirectory()) && (file.lastModified() > lastBuild)) {
400: addRevision(file);
401: }
402: }
403: }
|