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 org.apache.log4j.Logger;
039: import org.jdom.Attribute;
040: import org.jdom.Element;
041:
042: import java.lang.reflect.Method;
043: import java.util.HashMap;
044: import java.util.HashSet;
045: import java.util.Iterator;
046: import java.util.Map;
047: import java.util.Set;
048:
049: /**
050: * Helps mapping the XML to object by instantiating and initializing beans.
051: *
052: * Some plugins can be {@link SelfConfiguringPlugin self-configuring}. For the others, the
053: * the {@link #configureObject(org.jdom.Element, Object, boolean)} defines the operations
054: * to be performed.
055: */
056: public class PluginXMLHelper {
057: private static final Logger LOG = Logger
058: .getLogger(PluginXMLHelper.class);
059: private ProjectHelper projectHelper;
060: private final CruiseControlController controller;
061:
062: public PluginXMLHelper(ProjectHelper plugins) {
063: this (plugins, null);
064: }
065:
066: public PluginXMLHelper(ProjectHelper plugins,
067: CruiseControlController controller) {
068: projectHelper = plugins;
069: this .controller = controller;
070: }
071:
072: /**
073: * Given a JDOM Element and a class, this method will instantiate an object
074: * of type pluginClass, and {@link #configure(org.jdom.Element, Class, boolean) configure} the element.
075: * <p>
076: * When the plugin is {@link SelfConfiguringPlugin self-configuring} the plugin takes
077: * the responsibility of {@link SelfConfiguringPlugin#configure(org.jdom.Element) configuring itself}
078: *
079: * <p>{@link #configure(org.jdom.Element, Object, boolean)} to use when one already has an instance.
080: *
081: * @param objectElement the JDOM Element defining the plugin configuration
082: * @param pluginClass the class to instantiate
083: * @param skipChildElements <code>false</code> to recurse the configuration, <code>true</code> otherwise
084: * @return fully configured Object
085: * @see #configure(org.jdom.Element, Object, boolean)
086: * @throws CruiseControlException
087: * if the plugin class cannot be instantiated,
088: * if the configuration fails
089: */
090: public Object configure(Element objectElement, Class pluginClass,
091: boolean skipChildElements) throws CruiseControlException {
092:
093: Object pluginInstance = instantiatePlugin(pluginClass);
094: return configure(objectElement, pluginInstance,
095: skipChildElements);
096:
097: }
098:
099: /**
100: * Instantiate a plugin
101: * @param pluginClass
102: * @return The instantiated plugin
103: * @throws CruiseControlException if the plugin class cannot be instantiated
104: */
105: private Object instantiatePlugin(Class pluginClass)
106: throws CruiseControlException {
107: Object pluginInstance;
108: try {
109: pluginInstance = pluginClass.getConstructor(null)
110: .newInstance(null);
111: if (pluginInstance instanceof ControllerAware) {
112: ((ControllerAware) pluginInstance)
113: .setController(controller);
114: }
115: } catch (Exception e) {
116: LOG.fatal("Could not instantiate class", e);
117: throw new CruiseControlException(
118: "Could not instantiate class: "
119: + pluginClass.getName());
120: }
121: return pluginInstance;
122: }
123:
124: /**
125: * Same as {@link #configure(org.jdom.Element, Class, boolean)}, except that
126: * the client already has a pluginInstance.
127: * @throws CruiseControlException if the configuration fails
128: */
129: public Object configure(Element objectElement,
130: Object pluginInstance, boolean skipChildElements)
131: throws CruiseControlException {
132:
133: LOG.debug("configure " + objectElement.getName() + " instance "
134: + pluginInstance.getClass() + " self configuring: "
135: + (pluginInstance instanceof SelfConfiguringPlugin)
136: + " skip:" + skipChildElements);
137: if (pluginInstance instanceof SelfConfiguringPlugin) {
138: ((SelfConfiguringPlugin) pluginInstance)
139: .configure(objectElement);
140: } else {
141: configureObject(objectElement, pluginInstance,
142: skipChildElements);
143: }
144: return pluginInstance;
145: }
146:
147: /**
148: * Configure the specified plugin object given the JDOM Element defining the plugin configuration.
149: *
150: * <ul>
151: * <li>calls setters that corresponds to element attributes</li>
152: * <li>calls <code>public Yyy createXxx()</code> methods that corresponds to non-plugins child elements
153: * (i.e. known by the instance class). The returned instance must be assignable to the Yyy type</li>
154: * <li>calls <code>public void add(Xxx)</code> methods that corresponds to child elements which are
155: * plugins themselves, e.g. which will require asking the ProjectXMLHelper to
156: * {@link ProjectXMLHelper#configurePlugin(org.jdom.Element, boolean) configure the plugin}</li>
157: * </ul>
158: *
159: * @param objectElement the JDOM Element defining the plugin configuration
160: * @param object the instance to configure to instantiate
161: * @param skipChildElements <code>false</code> to recurse the configuration, <code>true</code> otherwise
162: */
163: protected void configureObject(Element objectElement,
164: Object object, boolean skipChildElements)
165: throws CruiseControlException {
166:
167: LOG.debug("configuring object " + objectElement.getName()
168: + " object " + object.getClass() + " skip "
169: + skipChildElements);
170:
171: Map setters = new HashMap();
172: Map creators = new HashMap();
173: Set adders = new HashSet();
174:
175: Method[] methods = object.getClass().getMethods();
176: for (int i = 0; i < methods.length; i++) {
177: final Method method = methods[i];
178: final String name = method.getName();
179: if (name.startsWith("set")) {
180: setters.put(name.substring("set".length())
181: .toLowerCase(), method);
182: } else if (name.startsWith("create")) {
183: creators.put(name.substring("create".length())
184: .toLowerCase(), method);
185: } else if (name.equals("add")
186: && method.getParameterTypes().length == 1) {
187: adders.add(method);
188: }
189: }
190:
191: setFromAttributes(objectElement, setters, object);
192:
193: if (!skipChildElements) {
194: Iterator childElementIterator = objectElement.getChildren()
195: .iterator();
196: while (childElementIterator.hasNext()) {
197: Element childElement = (Element) childElementIterator
198: .next();
199: if (creators.containsKey(childElement.getName()
200: .toLowerCase())) {
201: LOG.debug("treating child with creator "
202: + childElement.getName());
203: try {
204: Method method = (Method) creators
205: .get(childElement.getName()
206: .toLowerCase());
207: Object childObject = method
208: .invoke(object, null);
209: configureObject(childElement, childObject,
210: false);
211: } catch (Exception e) {
212: throw new CruiseControlException(e.getMessage());
213: }
214: } else {
215: // instanciate object from element via registry
216: Object childObject = projectHelper.configurePlugin(
217: childElement, false);
218:
219: Method adder = null;
220: // iterate over adders to find one that will take the object
221: for (Iterator iterator = adders.iterator(); iterator
222: .hasNext();) {
223: Method method = (Method) iterator.next();
224: Class type = method.getParameterTypes()[0];
225: if (type.isAssignableFrom(childObject
226: .getClass())) {
227: adder = method;
228: break;
229: }
230: }
231:
232: if (adder != null) {
233: try {
234: LOG.debug("treating child with adder "
235: + childElement.getName()
236: + " adding " + childObject);
237: adder.invoke(object,
238: new Object[] { childObject });
239: } catch (Exception e) {
240: LOG.fatal("Error configuring plugin.", e);
241: }
242: } else {
243: throw new CruiseControlException(
244: "Nested element: '"
245: + childElement.getName()
246: + "' is not supported for the <"
247: + objectElement.getName()
248: + "> tag.");
249: }
250: }
251: }
252: }
253: }
254:
255: private void setFromAttributes(Element objectElement, Map setters,
256: Object object) throws CruiseControlException {
257: for (Iterator iter = objectElement.getAttributes().iterator(); iter
258: .hasNext();) {
259: Attribute attribute = (Attribute) iter.next();
260: callSetter(attribute.getName(), attribute.getValue(),
261: setters, object);
262: }
263: }
264:
265: private void callSetter(String propName, String propValue,
266: Map setters, Object object) throws CruiseControlException {
267:
268: if (setters.containsKey(propName.toLowerCase())) {
269: LOG.debug("Setting " + propName.toLowerCase() + " to "
270: + propValue);
271: try {
272: Method method = (Method) setters.get(propName
273: .toLowerCase());
274: Class[] parameters = method.getParameterTypes();
275: if (String.class.isAssignableFrom(parameters[0])) {
276: method.invoke(object, new Object[] { propValue });
277: } else if (int.class.isAssignableFrom(parameters[0])) {
278: method.invoke(object, new Object[] { Integer
279: .valueOf(propValue) });
280: } else if (long.class.isAssignableFrom(parameters[0])) {
281: method.invoke(object, new Object[] { Long
282: .valueOf(propValue) });
283: } else if (boolean.class
284: .isAssignableFrom(parameters[0])) {
285: method.invoke(object, new Object[] { Boolean
286: .valueOf(propValue) });
287: } else {
288: LOG.error("rCouldn't invoke setter "
289: + propName.toLowerCase());
290: }
291: } catch (Exception e) {
292: LOG.fatal("Error configuring plugin.", e);
293: }
294: } else {
295: throw new CruiseControlException("Attribute: '" + propName
296: + "' is not supported for class: '"
297: + object.getClass().getName() + "'.");
298: }
299: }
300:
301: }
|