001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001-2003, 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.util.ArrayList;
039: import java.util.Date;
040: import java.util.Iterator;
041: import java.util.List;
042: import java.util.Map;
043:
044: import net.sourceforge.cruisecontrol.CruiseControlException;
045: import net.sourceforge.cruisecontrol.SourceControl;
046: import net.sourceforge.cruisecontrol.util.ValidationHelper;
047:
048: /**
049: * This class implements a Compound source control with one triggers
050: * section and one targets section.
051: *
052: * The triggers section contains one or more source controls that act
053: * as triggers for the modifications, i.e. the modificationset
054: * returned will be empty unless one or more of the source
055: * controls in the triggers section returns a non-empty
056: * modification list.
057: *
058: * The targets section contains source controls for targets that will
059: * be built (if modified) if and only if any of the source controls
060: * in the triggers section is modified.
061: *
062: * It is possible to add the trigger modifications to the list of
063: * returned modifications if the "includeTriggerChanges"
064: * attribute is set to true in the <compound...> tag corresponding to
065: * this class.
066: *
067: * The following is an example of how to use this source control in
068: * the config.xml file:
069: *
070: * <modificationset quietperiod="1" >
071: * <compound includeTriggerChanges="false">
072: * <triggers>
073: * <filesystem folder="./mod_file.txt" />
074: * </triggers>
075: * <targets>
076: * <cvs
077: * cvsroot=":pserver:user@cvs_repo.com:/cvs"
078: * />
079: * </targets>
080: * </compound>
081: * </modificationset>
082: *
083: * @author <a href="mailto:will.gwaltney@sas.com">Will Gwaltney</a>
084: */
085: public class Compound implements SourceControl {
086:
087: private SourceControlProperties properties = new SourceControlProperties();
088: private Triggers triggers = null;
089: private Targets targets = null;
090: private boolean includeTriggerChanges = false;
091:
092: public void setProperty(String propertyName) {
093: properties.assignPropertyName(propertyName);
094: }
095:
096: public Map getProperties() {
097: return properties.getPropertiesAndReset();
098: }
099:
100: /**
101: * Returns a list of modifications since the last build.
102: * First check for any modifications from the triggers.
103: * If there are none, then return an empty list. Otherwise
104: * return the modifications from the targets, and from the
105: * triggers also if the includeTriggerChanges member variable
106: * is true.
107: *
108: * @param lastBuild the date and time of the last build
109: * @param now the current date and time
110: *
111: * @return a list of the modifications
112: */
113: public List getModifications(Date lastBuild, Date now) {
114: List triggerMods;
115: List targetMods = new ArrayList();
116:
117: triggerMods = triggers.getModifications(lastBuild, now);
118:
119: if (!triggerMods.isEmpty()) {
120: targetMods = targets.getModifications(lastBuild, now);
121: // make sure we also pass the properties from the underlying targets
122: properties.putAll(targets.getProperties());
123: }
124:
125: if (includeTriggerChanges) {
126: targetMods.addAll(triggerMods);
127: // make sure we also pass the properties from the underlying triggers
128: // TODO: do we really only want this when includeTriggerChanges is set?
129: properties.putAll(triggers.getProperties());
130: }
131:
132: if (!targetMods.isEmpty()) {
133: properties.modificationFound();
134: }
135:
136: return targetMods;
137: }
138:
139: /**
140: * Confirms that there is exactly one triggers block and one targets
141: * block even if the triggers mods are included and the target
142: * block is empty (otherwise you wouldn't need a compound block
143: * to begin with).
144: *
145: * @throws CruiseControlException if the validation fails
146: */
147: public void validate() throws CruiseControlException {
148: ValidationHelper
149: .assertTrue(triggers != null,
150: "Error: there must be exactly one \"triggers\" block in a compound block.");
151: ValidationHelper
152: .assertTrue(targets != null,
153: "Error: there must be exactly one \"targets\" block in a compound block.");
154: }
155:
156: /**
157: * Creates an empty Triggers object and returns it to
158: * the calling routine to be filled.
159: *
160: * @return an empty Triggers object
161: */
162: public Object createTriggers() {
163: Triggers tr = new Triggers(this );
164: this .triggers = tr;
165: return tr;
166: }
167:
168: /**
169: * Creates an empty Targets object and returns it to
170: * the calling routine to be filled.
171: *
172: * @return an empty Targets object
173: */
174: public Object createTargets() {
175: Targets targ = new Targets(this );
176: this .targets = targ;
177: return targ;
178: }
179:
180: /**
181: * Sets whether to include modifications from the triggers
182: * when getModifications() returns the mods list.
183: *
184: * @param changes true to include trigger changes, false otherwise
185: */
186: public void setIncludeTriggerChanges(String changes) {
187: this .includeTriggerChanges = changes.equalsIgnoreCase("true");
188: }
189:
190: /**
191: * Static inner class, used to define a basis for the Targets and Triggers
192: * classes that are used inside the <compound>-tag.
193: */
194: protected static class Entry implements SourceControl {
195:
196: private SourceControlProperties properties = new SourceControlProperties();
197: private List sourceControls = new ArrayList();
198: private SourceControl parent;
199:
200: /**
201: * Public constructor for reflection purposes.
202: *
203: */
204: public Entry() {
205: }
206:
207: /**
208: * Constructor that the Compound class uses to create
209: * an object of this class.
210: *
211: * @param parent the parent of this object (an
212: * object of class Compound)
213: */
214: public Entry(SourceControl parent) {
215: this .parent = parent;
216: }
217:
218: public Map getProperties() {
219: return properties.getPropertiesAndReset();
220: }
221:
222: /**
223: * Returns a list of modifications since the last build
224: * by querying the sourceControl that this object contains.
225: *
226: * @param lastBuild the date and time of the last build
227: * @param now the current date and time
228: *
229: * @return a list of the modifications
230: */
231: public List getModifications(Date lastBuild, Date now) {
232: List retVal = new ArrayList();
233:
234: for (Iterator it = sourceControls.iterator(); it.hasNext();) {
235: SourceControl sourceControl = (SourceControl) it.next();
236: retVal.addAll(sourceControl.getModifications(lastBuild,
237: now));
238: // make sure we also pass the properties from the underlying sourcecontrol
239: properties.putAll(sourceControl.getProperties());
240: }
241:
242: return retVal;
243: }
244:
245: /**
246: * Confirms that the sourceControl that this object wraps
247: * has been set.
248: *
249: * @throws CruiseControlException if the validation fails
250: */
251: public void validate() throws CruiseControlException {
252: if (sourceControls.isEmpty()) {
253: throw new CruiseControlException(
254: "Error: there must be at least one source control in a "
255: + getEntryName() + " block.");
256: }
257: if (parent == null) {
258: throw new CruiseControlException(
259: "Error: "
260: + getEntryName()
261: + " blocks must be contained within compound blocks.");
262: }
263: }
264:
265: /**
266: * Adds a sourcecontrol to the list of sourcecontrols that
267: * this object contains.
268: *
269: * @param sc the sourceControl object to add
270: */
271: public void add(SourceControl sc) {
272: sourceControls.add(sc);
273: }
274:
275: /**
276: * Used by validate() to create a subclass-specific error-message.
277: * @return lower-case version of classname without package, e.g. 'triggers'.
278: */
279: private String getEntryName() {
280: String classname = getClass().getName();
281: int index = classname.lastIndexOf('.');
282: if (index != -1) {
283: return classname.substring(index + 1).toLowerCase();
284: } else {
285: return classname.toLowerCase();
286: }
287: }
288: }
289:
290: }
|