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;
037:
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.Map;
041:
042: import org.apache.log4j.Logger;
043: import org.jdom.Attribute;
044: import org.jdom.Element;
045: import net.sourceforge.cruisecontrol.config.DefaultPropertiesPlugin;
046:
047: /**
048: * Instantiates a project from a JDOM Element. Supports the use of Ant-like patterns in
049: * attribute values that look like this: <code>${propertyname}</code>
050: */
051: public class ProjectXMLHelper implements ProjectHelper {
052: // TODO: extract out generic Helper methods
053: private static final Logger LOG = Logger
054: .getLogger(ProjectXMLHelper.class);
055:
056: private Map projectProperties;
057: private PluginRegistry projectPlugins;
058:
059: private final CruiseControlController controller;
060:
061: public ProjectXMLHelper() {
062: this (new HashMap(), PluginRegistry
063: .createRegistry(PluginRegistry
064: .loadDefaultPluginRegistry()), null);
065: }
066:
067: public ProjectXMLHelper(Map projectProperties,
068: PluginRegistry projectPlugins) {
069: this (projectProperties, projectPlugins, null);
070: }
071:
072: public ProjectXMLHelper(Map projectProperties,
073: PluginRegistry projectPlugins,
074: CruiseControlController controller) {
075: this .projectProperties = projectProperties;
076: this .projectPlugins = projectPlugins;
077: this .controller = controller;
078: }
079:
080: /**
081: * TODO: also check that instantiated class implements/extends correct interface/class
082: */
083: public Object configurePlugin(Element pluginElement,
084: boolean skipChildElements) throws CruiseControlException {
085: String name = pluginElement.getName();
086: PluginXMLHelper pluginHelper = new PluginXMLHelper(this ,
087: controller);
088: String pluginName = pluginElement.getName();
089:
090: LOG.debug("configuring plugin " + pluginElement.getName()
091: + " skip " + skipChildElements);
092:
093: if (projectPlugins.isPluginRegistered(pluginName)) {
094: Object pluginInstance = getConfiguredPlugin(pluginHelper,
095: pluginElement.getName());
096: if (pluginInstance != null) { // preconfigured
097: return pluginHelper.configure(pluginElement,
098: pluginInstance, skipChildElements);
099: }
100: return pluginHelper.configure(pluginElement, projectPlugins
101: .getPluginClass(pluginName), skipChildElements);
102: } else {
103: throw new CruiseControlException("Unknown plugin for: <"
104: + name + ">");
105: }
106: }
107:
108: /**
109: * Get a [partially] configured plugin instance given its plugin name.
110: * @param pluginName
111: * @return <code>null</code> if the plugin was never configured.
112: * @throws CruiseControlException
113: * if the registered class cannot be loaded,
114: * if a property cannot be resolved,
115: * if the plugin configuration fails
116: */
117: private Object getConfiguredPlugin(PluginXMLHelper pluginHelper,
118: String pluginName) throws CruiseControlException {
119: final Class pluginClass = projectPlugins
120: .getPluginClass(pluginName);
121: if (pluginClass == null) {
122: return null;
123: }
124: Object configuredPlugin = null;
125: Element pluginElement = projectPlugins
126: .getPluginConfig(pluginName);
127: if (pluginElement != null) {
128: // FIXME
129: // the only reason we have to do this here
130: // is because the plugins registered in the ROOT registry have not had their properties parsed.
131: // See CruiseControlController.addPluginsToRootRegistry
132: parsePropertiesInElement(pluginElement);
133: configuredPlugin = pluginHelper.configure(pluginElement,
134: pluginClass, false);
135: }
136: return configuredPlugin;
137: }
138:
139: /**
140: * Recurses through an Element tree, substituting resolved values
141: * for any property macros.
142: *
143: * @param element The Element to parse
144: *
145: * @throws CruiseControlException if a property cannot be resolved
146: */
147: private void parsePropertiesInElement(Element element)
148: throws CruiseControlException {
149: parsePropertiesInElement(element, this .projectProperties,
150: CruiseControlConfig.FAIL_UPON_MISSING_PROPERTY);
151: }
152:
153: /**
154: * Registers one or more properties as defined in a property element.
155: *
156: * @param propertyElement The element from which we will register properties
157: * @throws CruiseControlException
158: */
159: public static DefaultPropertiesPlugin registerProperty(Map props,
160: Element propertyElement, boolean failIfMissing)
161: throws CruiseControlException {
162:
163: parsePropertiesInElement(propertyElement, props, failIfMissing);
164:
165: Object o = new ProjectXMLHelper().configurePlugin(
166: propertyElement, false);
167: if (!(o instanceof DefaultPropertiesPlugin)) {
168: throw new CruiseControlException(
169: "Properties element does not extend DefaultPropertiesPlugin interface."
170: + " Check your CC global plugin configuration.");
171: }
172: DefaultPropertiesPlugin propertiesObject = (DefaultPropertiesPlugin) o;
173: propertiesObject.loadProperties(props, failIfMissing);
174: return propertiesObject;
175: }
176:
177: // FIXME Helper extract ?
178: /**
179: * Parses a string by replacing all occurrences of a property macro with
180: * the resolved value of the property. Nested macros are allowed - the
181: * inner most macro will be resolved first, moving out from there.
182: *
183: * @param string The string to be parsed
184: * @return The parsed string
185: * @throws CruiseControlException if a property cannot be resolved
186: */
187: public static String parsePropertiesInString(Map props,
188: String string, boolean failIfMissing)
189: throws CruiseControlException {
190: if (string != null) {
191: int startIndex = string.indexOf("${");
192: if (startIndex != -1) {
193: int openedBrackets = 1;
194: int lastStartIndex = startIndex + 2;
195: int endIndex;
196: do {
197: endIndex = string.indexOf("}", lastStartIndex);
198: int otherStartIndex = string.indexOf("${",
199: lastStartIndex);
200: if (otherStartIndex != -1
201: && otherStartIndex < endIndex) {
202: openedBrackets++;
203: lastStartIndex = otherStartIndex + 2;
204: } else {
205: openedBrackets--;
206: if (openedBrackets == 0) {
207: break;
208: }
209: lastStartIndex = endIndex + 1;
210: }
211: } while (true);
212: if (endIndex < startIndex + 2) {
213: throw new CruiseControlException(
214: "Unclosed brackets in " + string);
215: }
216: String property = string.substring(startIndex + 2,
217: endIndex);
218: // not necessarily resolved
219: String propertyName = parsePropertiesInString(props,
220: property, failIfMissing);
221: String value = "".equals(propertyName) ? ""
222: : (String) props.get(propertyName);
223: if (value == null) {
224: if (failIfMissing) {
225: throw new CruiseControlException(
226: "Property \""
227: + propertyName
228: + "\" is not defined. Please check the order in which you have used your properties.");
229: } else {
230: // we don't resolve missing properties
231: value = "${" + propertyName + "}";
232: }
233: }
234: LOG.debug("Replacing the string \"" + propertyName
235: + "\" with \"" + value + "\".");
236: string = string.substring(0, startIndex)
237: + value
238: + parsePropertiesInString(props, string
239: .substring(endIndex + 1), failIfMissing);
240: }
241: }
242: return string;
243:
244: }
245:
246: // FIXME Helper extract ?
247: public static void parsePropertiesInElement(Element element,
248: Map props, boolean failIfMissing)
249: throws CruiseControlException {
250:
251: // Recurse through the element tree - depth first
252: for (Iterator children = element.getChildren().iterator(); children
253: .hasNext();) {
254: parsePropertiesInElement((Element) children.next(), props,
255: failIfMissing);
256: }
257:
258: // Parse the attribute value strings
259: for (Iterator attributes = element.getAttributes().iterator(); attributes
260: .hasNext();) {
261: Attribute attribute = (Attribute) attributes.next();
262: attribute.setValue(parsePropertiesInString(props, attribute
263: .getValue(), failIfMissing));
264: }
265:
266: // Parse the element's text
267: String text = element.getTextTrim();
268: if (text.length() > 0) {
269: element.setText(parsePropertiesInString(props, text,
270: failIfMissing));
271: }
272: }
273:
274: public static void setProperty(Map props, String name,
275: String parsedValue) {
276: ProjectXMLHelper.LOG.debug("Setting property \"" + name
277: + "\" to \"" + parsedValue + "\".");
278: props.put(name, parsedValue);
279: }
280: }
|