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.io.IOException;
039: import java.io.Serializable;
040: import java.util.Arrays;
041: import java.util.HashMap;
042: import java.util.Iterator;
043: import java.util.LinkedList;
044: import java.util.List;
045: import java.util.Map;
046: import java.util.Properties;
047:
048: import org.apache.log4j.Logger;
049: import org.jdom.Attribute;
050: import org.jdom.Element;
051: import net.sourceforge.cruisecontrol.config.PluginPlugin;
052:
053: /**
054: * Handles "registering" plugins that will be used by the CruiseControl
055: * configuration file.
056:
057: * A PluginRegistry can have a parent registry, which it will query for
058: * a plugin if it's not defined in the registry itself. This is used to
059: * enable projects to have their own plugins and override the classname
060: * for a specific plugin, like the labelincrementer.
061: *
062: * The root-registry contains the default list of plugins, i.e. those
063: * that are already registered like AntBuilder that don't have to be registered
064: * separately in the configuration file.
065: *
066: * The registry keeps track of the {@link #getPluginConfig(String) plugin configurations}
067: * in order to allow full plugin preconfigurations (default properties + nested elements).
068: *
069: * @see PluginXMLHelper
070: */
071: public final class PluginRegistry implements Serializable {
072:
073: private static final Logger LOG = Logger
074: .getLogger(PluginRegistry.class);
075:
076: /**
077: * The only instance of the root plugin registry.
078: * This contains the default plugins and the plugins that are defined
079: * external to projects.
080: */
081: private static final PluginRegistry ROOTREGISTRY = loadDefaultPluginRegistry();
082:
083: /**
084: * @return PluginRegistry with the ROOTREGISTRY as its parent.
085: */
086: public static PluginRegistry createRegistry() {
087: return createRegistry(ROOTREGISTRY);
088: }
089:
090: /**
091: * @return PluginRegistry with the specified registry as its parent.
092: */
093: public static PluginRegistry createRegistry(PluginRegistry parent) {
094: return new PluginRegistry(parent);
095: }
096:
097: /**
098: * The parent registry that will be searched for plugin definitions
099: * if they're not defined in the registry itself. May be null.
100: */
101: private final PluginRegistry parentRegistry;
102:
103: /**
104: * Map of plugins where the key is the plugin name (e.g. ant) and the value is
105: * the fully qualified classname
106: * (e.g. net.sourceforge.cruisecontrol.builders.AntBuilder).
107: */
108: private final Map plugins = new HashMap();
109:
110: /**
111: * Map that holds the DOM element representing the plugin declaration.
112: * Key is the plugin name (as taken from the DOM element),
113: * value is the Element representing the plugin configuration.
114: */
115: private final Map pluginConfigs = new HashMap();
116:
117: /**
118: * Creates a new PluginRegistry with no plugins registered, with the given parent registry.
119: * Only used internally for now, Projects should call createRegistry instead.
120: */
121: private PluginRegistry(PluginRegistry parentRegistry) {
122: this .parentRegistry = parentRegistry;
123: }
124:
125: /**
126: * @param pluginName The name for the plugin, e.g. ant. Note that plugin
127: * names are always treated as case insensitive, so Ant, ant, and AnT are
128: * all treated as the same plugin.
129: *
130: * @param pluginClassname The fully qualified classname for the
131: * plugin class, e.g. net.sourceforge.cruisecontrol.builders.AntBuilder.
132: */
133: public void register(String pluginName, String pluginClassname) {
134: // TODO hide from public interface
135: plugins.put(pluginName.toLowerCase(), pluginClassname);
136: }
137:
138: public void register(PluginPlugin plugin)
139: throws CruiseControlException {
140: String pluginName = plugin.getName();
141: String pluginClassName = plugin.getClassname();
142: Element transformedElement = plugin.getTransformedElement();
143: if (pluginClassName != null) {
144: register(pluginName, pluginClassName);
145: } else {
146: // should be known plugin, then
147: if (!isPluginRegistered(pluginName)) {
148: throw new CruiseControlException("Unknown plugin '"
149: + pluginName
150: + "'; maybe you forgot to specify a classname?");
151: }
152: }
153:
154: if (LOG.isDebugEnabled()) {
155: LOG.debug("storing plugin configuration " + pluginName);
156: }
157: pluginConfigs.put(pluginName, transformedElement);
158: }
159:
160: /**
161: * Registers the given plugin, including plugin configuration.
162: *
163: * @param pluginElement the JDom element that contains the plugin definition.
164: * @deprecated use {@link #register(PluginPlugin)}
165: */
166: public void register(Element pluginElement)
167: throws CruiseControlException {
168: PluginPlugin plugin = (PluginPlugin) new ProjectXMLHelper()
169: .configurePlugin(pluginElement, false);
170: register(plugin);
171: }
172:
173: /**
174: * Registers the given plugin in the root registry, so it will be
175: * available to all projects.
176: *
177: */
178: static void registerToRoot(Element pluginElement)
179: throws CruiseControlException {
180: ROOTREGISTRY.register(pluginElement);
181: }
182:
183: /**
184: * Clears all plugin registrations and defaults in the root registry, so they can be re-registered
185: * when reloading the config file. The default-properties are re-read.
186: */
187: static void resetRootRegistry() {
188: ROOTREGISTRY.pluginConfigs.clear();
189: ROOTREGISTRY.plugins.clear();
190: ROOTREGISTRY.plugins
191: .putAll(loadDefaultPluginRegistry().plugins);
192: }
193:
194: /**
195: * @return Returns null if no plugin has been registered with the specified
196: * name, otherwise a String representing the fully qualified classname
197: * for the plugin class. Note that plugin
198: * names are always treated as case insensitive, so Ant, ant, and AnT are
199: * all treated as the same plugin.
200: * Note: a parent name->class mapping can be overridden by children registries.
201: */
202: public String getPluginClassname(String pluginName) {
203: pluginName = pluginName.toLowerCase();
204: String className = internalGetPluginClassname(pluginName);
205: if (className == null && parentRegistry != null) {
206: className = parentRegistry.getPluginClassname(pluginName);
207: }
208: return className;
209: }
210:
211: /**
212: * @return the class name for this plugin on this registry. May be <code>null</code>
213: * Assumes the pluginName is lower case
214: */
215: private String internalGetPluginClassname(String pluginName) {
216: return (String) plugins.get(pluginName);
217: }
218:
219: /**
220: * @return Returns null if no plugin has been registered with the specified
221: * name, otherwise the Class representing the plugin class. Note that
222: * plugin names are always treated as case insensitive, so Ant, ant,
223: * and AnT are all treated as the same plugin.
224: *
225: * @throws CruiseControlException If the class provided cannot be loaded.
226: */
227: public Class getPluginClass(String pluginName)
228: throws CruiseControlException {
229: if (!isPluginRegistered(pluginName)) {
230: return null;
231: }
232: String pluginClassname = getPluginClassname(pluginName);
233: return instanciatePluginClass(pluginClassname, pluginName);
234: }
235:
236: /**
237: * @param pluginClassname
238: * @param pluginName
239: * @return instantiate the Class representing the plugin class name.
240: * @throws CruiseControlException If the class provided cannot be loaded.
241: */
242: public Class instanciatePluginClass(String pluginClassname,
243: String pluginName) throws CruiseControlException {
244: try {
245: return Class.forName(pluginClassname);
246: } catch (ClassNotFoundException e) {
247: String msg = "Attemping to load plugin named ["
248: + pluginName
249: + "], but couldn't load corresponding class ["
250: + pluginClassname + "].";
251: throw new CruiseControlException(msg);
252: }
253: }
254:
255: public String getPluginName(Class pluginClass) {
256: String pluginName = null;
257:
258: if (parentRegistry != null) {
259: pluginName = parentRegistry.getPluginName(pluginClass);
260: }
261:
262: if (pluginName == null) {
263: for (Iterator i = plugins.entrySet().iterator(); i
264: .hasNext();) {
265: Map.Entry entry = (Map.Entry) i.next();
266: String value = (String) entry.getValue();
267: if (value.equals(pluginClass.getName())) {
268: pluginName = ((String) entry.getKey());
269: break;
270: }
271: }
272: }
273:
274: return pluginName;
275: }
276:
277: public PluginDetail[] getPluginDetails()
278: throws CruiseControlException {
279: List availablePlugins = new LinkedList();
280:
281: if (parentRegistry != null) {
282: availablePlugins.addAll(Arrays.asList(parentRegistry
283: .getPluginDetails()));
284: }
285:
286: for (Iterator i = plugins.keySet().iterator(); i.hasNext();) {
287: String pluginName = (String) i.next();
288: try {
289: Class pluginClass = getPluginClass(pluginName);
290: availablePlugins.add(new GenericPluginDetail(
291: pluginName, pluginClass));
292: } catch (CruiseControlException e) {
293: String message = e.getMessage();
294: // TODO: handle these potential unloadable plugins in a better way
295: if (message.indexOf("starteam") == -1
296: && message.indexOf("harvest") == -1) {
297: throw e;
298: }
299: }
300: }
301:
302: return (PluginDetail[]) availablePlugins
303: .toArray(new PluginDetail[availablePlugins.size()]);
304: }
305:
306: public PluginType[] getPluginTypes() {
307: return PluginType.getTypes();
308: }
309:
310: /**
311: * @return True if this registry or its parent contains
312: * an entry for the plugin specified by the name.
313: * The name is the short name for the plugin, not
314: * the classname, e.g. ant. Note that plugin
315: * names are always treated as case insensitive, so Ant, ant, and AnT are
316: * all treated as the same plugin.
317: *
318: * @throws NullPointerException If a null pluginName is passed, then
319: * a NullPointerException will occur. It's recommended to not pass a
320: * null pluginName.
321: */
322: public boolean isPluginRegistered(String pluginName) {
323: boolean isRegistered = plugins.containsKey(pluginName
324: .toLowerCase());
325: if (!isRegistered && parentRegistry != null) {
326: isRegistered = parentRegistry
327: .isPluginRegistered(pluginName);
328: }
329: return isRegistered;
330: }
331:
332: /**
333: * Returns a PluginRegistry containing all the default plugins.
334: * The key is the plugin name (e.g. ant) and the value is
335: * the fully qualified classname
336: * (e.g. net.sourceforge.cruisecontrol.builders.AntBuilder).
337: * @throws RuntimeException in case of IOException during the reading of the properties-file
338: */
339: static PluginRegistry loadDefaultPluginRegistry() {
340: PluginRegistry rootRegistry = new PluginRegistry(null);
341: Properties pluginDefinitions = new Properties();
342: try {
343: pluginDefinitions.load(PluginRegistry.class
344: .getResourceAsStream("default-plugins.properties"));
345: } catch (IOException e) {
346: throw new RuntimeException(
347: "Failed to load plugin-definitions from default-plugins.properties: "
348: + e);
349: }
350: for (Iterator iter = pluginDefinitions.entrySet().iterator(); iter
351: .hasNext();) {
352: Map.Entry entry = (Map.Entry) iter.next();
353: rootRegistry.register((String) entry.getKey(),
354: (String) entry.getValue());
355: }
356: return rootRegistry;
357: }
358:
359: /**
360: * Get the plugin configuration particular to this plugin, merged with the parents
361: * @throws NullPointerException if pluginName is null
362: */
363: public Element getPluginConfig(String pluginName) {
364: pluginName = pluginName.toLowerCase();
365: String className = getPluginClassname(pluginName);
366: return overridePluginConfig(pluginName, className, null);
367: }
368:
369: /**
370: * Return a merged plugin configuration, taking into account parent classes where appropriate.
371: *
372: * This method is used recursively to fill up the specified pluginConfig Element.
373: *
374: * Properties are taken from parent plugins if they have not been defined in the child.
375: * Nested elements of the parent are always added to the child's config.
376: *
377: * Note: as we have no way to enforce the cardinality of the nested elements, the parent/default nested
378: * elements are always added to the config of the child. The validity of the resulting config then
379: * depends on the config to be correctly specified.
380: *
381: * @param pluginName the name of the plugin to create a config for (must be lower case)
382: * @param pluginClass the mapped class name for the plugin
383: * @param pluginConfig the current config, passed up for completion
384: * @return an Element representing the combination of the various plugin configurations for
385: * the same plugin, following the hierarchy.
386: */
387: private Element overridePluginConfig(final String pluginName,
388: final String pluginClass, Element pluginConfig) {
389: Element pluginElement = (Element) this .pluginConfigs
390: .get(pluginName);
391: // clone the first found plugin config
392: if (pluginElement != null && pluginConfig == null) {
393: pluginElement = (Element) pluginElement.clone();
394: }
395: if (pluginConfig == null) {
396: pluginConfig = pluginElement;
397: } else {
398: // do not override if class names do not match
399: if (pluginElement != null
400: && pluginClass.equals(this
401: .internalGetPluginClassname(pluginName))) {
402: // override properties
403: List attributes = pluginElement.getAttributes();
404: for (int i = 0; i < attributes.size(); i++) {
405: Attribute attribute = (Attribute) attributes.get(i);
406: String name = attribute.getName();
407: if (pluginConfig.getAttribute(name) == null) {
408: pluginConfig.setAttribute(name, attribute
409: .getValue());
410: }
411: }
412: // combine child elements
413: List children = pluginElement.getChildren();
414: for (int i = 0; i < children.size(); i++) {
415: Element child = (Element) children.get(i);
416: pluginConfig.addContent((Element) child.clone());
417: }
418: }
419: }
420: if (this.parentRegistry != null) {
421: pluginConfig = this.parentRegistry.overridePluginConfig(
422: pluginName, pluginClass, pluginConfig);
423: }
424: return pluginConfig;
425: }
426: }
|