001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.faces.beans;
034:
035: import com.flexive.faces.FxJsfUtils;
036: import com.flexive.faces.plugin.ExtensionPoint;
037: import com.flexive.faces.plugin.Plugin;
038: import com.flexive.faces.plugin.PluginExecutor;
039: import com.flexive.faces.plugin.PluginFactory;
040: import com.flexive.shared.exceptions.FxInvalidParameterException;
041: import org.apache.commons.lang.StringUtils;
042: import org.apache.commons.logging.Log;
043: import org.apache.commons.logging.LogFactory;
044: import org.w3c.dom.Document;
045: import org.w3c.dom.NodeList;
046: import org.xml.sax.SAXException;
047:
048: import javax.xml.parsers.DocumentBuilder;
049: import javax.xml.parsers.DocumentBuilderFactory;
050: import javax.xml.parsers.ParserConfigurationException;
051: import java.io.IOException;
052: import java.io.Serializable;
053: import java.lang.reflect.ParameterizedType;
054: import java.net.URL;
055: import java.util.*;
056: import java.util.concurrent.ConcurrentHashMap;
057: import java.util.concurrent.ConcurrentMap;
058:
059: /**
060: * An application-scoped beans that serves as a plugin registry.
061: *
062: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
063: * @version $Rev: 1 $
064: */
065: public class PluginRegistryBean implements Serializable {
066: private static final long serialVersionUID = -1931267445285693491L;
067: private static final transient Log LOG = LogFactory
068: .getLog(PluginRegistryBean.class);
069: private static final String CONFIG_FILENAME = "META-INF/flexive-plugins-config.xml";
070:
071: private final ConcurrentMap<ExtensionPoint, List<Plugin>> plugins = new ConcurrentHashMap<ExtensionPoint, List<Plugin>>();
072: private final Map<ExtensionPoint, Class> pluginExecutorTypes = new HashMap<ExtensionPoint, Class>();
073:
074: /**
075: * Constructor. Search the classpath for plugin config files and initialize all plugins.
076: */
077: @SuppressWarnings({"unchecked"})
078: public PluginRegistryBean() {
079: try {
080: // find all config files in our application's classpath
081: final Enumeration<URL> configFiles = Thread.currentThread()
082: .getContextClassLoader().getResources(
083: CONFIG_FILENAME);
084: while (configFiles.hasMoreElements()) {
085: final URL configFile = configFiles.nextElement();
086: if (LOG.isDebugEnabled()) {
087: LOG.debug("Processing plugin config: "
088: + configFile.getPath());
089: }
090: final Document document = getXmlDocument(configFile);
091: // get plugin-factory elements
092: final NodeList factories = document
093: .getElementsByTagName("plugin-factory");
094: for (int i = 0; i < factories.getLength(); i++) {
095: // get the FQCN of the PluginFactory instance
096: final String className = StringUtils.trim(factories
097: .item(i).getTextContent());
098: try {
099: // load the factory class
100: final Class<?> factoryClass = Thread
101: .currentThread()
102: .getContextClassLoader().loadClass(
103: className);
104: if (!(PluginFactory.class
105: .isAssignableFrom(factoryClass))) {
106: if (LOG.isErrorEnabled()) {
107: LOG.error("Plugin factory "
108: + className
109: + " does not implement "
110: + PluginFactory.class
111: .getCanonicalName()
112: + " (ignored).");
113: }
114: continue;
115: }
116: try {
117: // instantiate factory
118: final PluginFactory factory = ((Class<PluginFactory>) factoryClass)
119: .newInstance();
120: // initialize plugins
121: if (LOG.isInfoEnabled()) {
122: LOG
123: .info("Adding flexive plugin callbacks from "
124: + className + "...");
125: }
126: // add plugin callbacks
127: factory.initialize(this );
128: } catch (Exception e) {
129: LOG.error(
130: "Failed to instantiate plugin factory (ignored): "
131: + e.getMessage(), e);
132: }
133: } catch (ClassNotFoundException e) {
134: LOG
135: .error("Plugin factory class not found (ignored): "
136: + className);
137: }
138: }
139: }
140: } catch (Exception e) {
141: LOG.error("Failed to initialize plugin registry bean: "
142: + e.getMessage(), e);
143: throw new IllegalArgumentException(e);
144: }
145: }
146:
147: /**
148: * Return the singleton (application-scoped) instance of this managed beans.
149: *
150: * @return the singleton (application-scoped) instance of this managed beans.
151: */
152: public static PluginRegistryBean getInstance() {
153: return (PluginRegistryBean) FxJsfUtils
154: .getManagedBean("fxPluginRegistryBean");
155: }
156:
157: /**
158: * Register a plugin for the given extension point. The extension point must have
159: * its executor type statically bound, i.e. it must not be generic (see {@link ExtensionPoint}). This
160: * method is most likely called from a {@link com.flexive.faces.plugin.PluginFactory} to register plugin callbacks.
161: *
162: * @param extensionPoint the extension point
163: * @param callback the plugin handler to be added
164: */
165: public <PEX extends PluginExecutor> void registerPlugin(
166: ExtensionPoint<PEX> extensionPoint, Plugin<PEX> callback) {
167: // check if the extension point type parameter has been bound
168: if (!(extensionPoint.getClass().getGenericSuperclass() instanceof ParameterizedType)
169: || !(((ParameterizedType) extensionPoint.getClass()
170: .getGenericSuperclass())
171: .getActualTypeArguments()[0] instanceof Class)) {
172: throw new FxInvalidParameterException("extensionPoint",
173: LOG, "ex.jsf.pluginRegistry.executorTypeNotBound",
174: extensionPoint.getClass().getCanonicalName())
175: .asRuntimeException();
176: }
177: // get the executor class from the extension point. this works since we know that
178: // ExtensionPoint has to be subclassed and needs its type parameter to be bound
179: final Class executorClass = (Class) ((ParameterizedType) extensionPoint
180: .getClass().getGenericSuperclass())
181: .getActualTypeArguments()[0];
182: synchronized (plugins) {
183: if (!pluginExecutorTypes.containsKey(extensionPoint)) {
184: pluginExecutorTypes.put(extensionPoint, executorClass);
185: }
186: if (!pluginExecutorTypes.get(extensionPoint)
187: .isAssignableFrom(executorClass)) {
188: throw new FxInvalidParameterException("extensionPoint",
189: LOG,
190: "ex.jsf.pluginRegistry.incompatibleExecutor",
191: pluginExecutorTypes.get(extensionPoint)
192: .getCanonicalName(), callback
193: .getClass().getCanonicalName())
194: .asRuntimeException();
195: }
196: if (!plugins.containsKey(extensionPoint)) {
197: plugins.put(extensionPoint, new ArrayList<Plugin>());
198: }
199: plugins.get(extensionPoint).add(callback);
200: }
201: }
202:
203: /**
204: * Return all plugins registered for the given extension point.
205: *
206: * @param extensionPoint the extension point
207: * @return all plugins registered for the given extension point.
208: */
209: @SuppressWarnings({"unchecked"})
210: public <PEX extends PluginExecutor> List<Plugin<PEX>> getPlugins(
211: ExtensionPoint<PEX> extensionPoint) {
212: final List<? extends Plugin> extensionPoints = plugins
213: .get(extensionPoint);
214: return extensionPoints == null ? new ArrayList<Plugin<PEX>>()
215: : (List<Plugin<PEX>>) extensionPoints;
216: }
217:
218: /**
219: * Execute all registered plugins for {@code extensionPoint} using the given {@code executor} object.
220: *
221: * @param extensionPoint the extension point
222: * @param executor the executor to handle all registered plugins
223: */
224: public <PEX extends PluginExecutor> void execute(
225: ExtensionPoint<PEX> extensionPoint, PEX executor) {
226: final List<Plugin<PEX>> callbacks = getPlugins(extensionPoint);
227: for (Plugin<PEX> callback : callbacks) {
228: callback.apply(executor);
229: }
230: }
231:
232: /**
233: * Remove all plugins of the given extension point.
234: *
235: * @param extensionPoint the extension point to be cleared.
236: */
237: public void clearPlugins(ExtensionPoint extensionPoint) {
238: plugins.remove(extensionPoint);
239: }
240:
241: /**
242: * Return the XML document represented by the given URL.
243: *
244: * @param configFile the config file URL
245: * @return the XML document represented by the given URL.
246: * @throws ParserConfigurationException if the parser could not be instantiated
247: * @throws SAXException if the document could not be parsed
248: * @throws IOException if the file could not be read
249: */
250: private Document getXmlDocument(URL configFile)
251: throws ParserConfigurationException, SAXException,
252: IOException {
253: final DocumentBuilderFactory builderFactory = DocumentBuilderFactory
254: .newInstance();
255: builderFactory.setValidating(false);
256: final DocumentBuilder builder = builderFactory
257: .newDocumentBuilder();
258: return builder.parse(configFile.openStream());
259: }
260:
261: }
|