001: /*---------------------------------------------------------------------------*\
002: $Id: PlugInManager.java 7041 2007-09-09 01:04:47Z bmc $
003: ---------------------------------------------------------------------------
004: This software is released under a BSD-style license:
005:
006: Copyright (c) 2004-2007 Brian M. Clapper. 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 are
010: met:
011:
012: 1. Redistributions of source code must retain the above copyright notice,
013: this list of conditions and the following disclaimer.
014:
015: 2. The end-user documentation included with the redistribution, if any,
016: must include the following acknowlegement:
017:
018: "This product includes software developed by Brian M. Clapper
019: (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
020: copyright (c) 2004-2007 Brian M. Clapper."
021:
022: Alternately, this acknowlegement may appear in the software itself,
023: if wherever such third-party acknowlegements normally appear.
024:
025: 3. Neither the names "clapper.org", "curn", nor any of the names of the
026: project contributors may be used to endorse or promote products
027: derived from this software without prior written permission. For
028: written permission, please contact bmc@clapper.org.
029:
030: 4. Products derived from this software may not be called "curn", nor may
031: "clapper.org" appear in their names without prior written permission
032: of Brian M. Clapper.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
036: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
037: NO EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
038: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
039: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
040: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
041: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044: \*---------------------------------------------------------------------------*/
045:
046: package org.clapper.curn;
047:
048: import org.clapper.util.logging.Logger;
049:
050: import org.clapper.util.io.AndFileFilter;
051: import org.clapper.util.io.OrFileFilter;
052: import org.clapper.util.io.RegexFileFilter;
053: import org.clapper.util.io.DirectoryFilter;
054: import org.clapper.util.io.FileOnlyFilter;
055: import org.clapper.util.io.FileFilterMatchType;
056:
057: import org.clapper.util.classutil.ClassFinder;
058: import org.clapper.util.classutil.ClassFilter;
059: import org.clapper.util.classutil.ClassInfo;
060: import org.clapper.util.classutil.ClassModifiersClassFilter;
061: import org.clapper.util.classutil.AndClassFilter;
062: import org.clapper.util.classutil.OrClassFilter;
063: import org.clapper.util.classutil.AbstractClassFilter;
064: import org.clapper.util.classutil.NotClassFilter;
065: import org.clapper.util.classutil.RegexClassFilter;
066: import org.clapper.util.classutil.SubclassClassFilter;
067:
068: import java.lang.reflect.Modifier;
069:
070: import java.io.FileFilter;
071:
072: import java.util.ArrayList;
073: import java.util.Collection;
074: import java.util.Map;
075: import java.util.TreeSet;
076:
077: import java.util.regex.PatternSyntaxException;
078: import org.clapper.util.classutil.ClassUtil;
079: import org.clapper.util.classutil.ClassUtilException;
080:
081: /**
082: * Responsible for loading the plug-ins and creating the {@link MetaPlugIn}
083: * singleton that's used to run the loaded plug-ins. This functionality is
084: * isolated in a separate class to permit implementing a future feature
085: * that allows run-time substitution of different implementations of
086: * <tt>PlugInManager</tt>.
087: *
088: * @see PlugIn
089: * @see PlugInManager
090: * @see MetaPlugIn
091: * @see Curn
092: *
093: * @version <tt>$Revision: 7041 $</tt>
094: */
095: public class PlugInManager {
096: /*----------------------------------------------------------------------*\
097: Inner Classes
098: \*----------------------------------------------------------------------*/
099:
100: /*----------------------------------------------------------------------*\
101: Private Data Items
102: \*----------------------------------------------------------------------*/
103:
104: /**
105: * For log messages
106: */
107: private static final Logger log = new Logger(PlugInManager.class);
108:
109: /**
110: * The located PlugIns
111: */
112: private static Collection<PlugIn> plugIns = new TreeSet<PlugIn>(
113: new PlugInComparator());
114:
115: /**
116: * Whether or not plug-ins are loaded
117: */
118: private static boolean plugInsLoaded = false;
119:
120: /**
121: * File filter to use when looking for jars, zip files, and directories.
122: */
123: private static FileFilter plugInLocFilter = null;
124:
125: static {
126: try {
127: plugInLocFilter = new OrFileFilter(
128: // must be a directory ...
129:
130: new DirectoryFilter(),
131:
132: // or, must be a file ending in .jar
133:
134: new AndFileFilter(new RegexFileFilter("\\.jar$",
135: FileFilterMatchType.FILENAME),
136: new FileOnlyFilter()),
137:
138: // or, must be a file ending in .zip
139:
140: new AndFileFilter(new RegexFileFilter("\\.zip$",
141: FileFilterMatchType.FILENAME),
142: new FileOnlyFilter()));
143: }
144:
145: catch (PatternSyntaxException ex) {
146: // Should not happen.
147:
148: assert (false);
149: }
150: }
151:
152: /*----------------------------------------------------------------------*\
153: Constructor
154: \*----------------------------------------------------------------------*/
155:
156: /**
157: * Cannot be instantiated.
158: */
159: private PlugInManager() {
160: // Cannot be instantiated.
161: }
162:
163: /*----------------------------------------------------------------------*\
164: Package-visible Methods
165: \*----------------------------------------------------------------------*/
166:
167: /**
168: * Get the list of loaded plug-ins and output handlers. The plug-ins,
169: * which are already instantiated, are storing in the supplied
170: * <tt>Map</tt>. The map is indexed by plug-in displayable name; each
171: * value is the corresponding <tt>PlugIn</tt> class. The plug-in
172: * <tt>OutputHandler</tt> classes are not loaded, so they're stored
173: * in a <tt>Map</tt> that's indexed by short class name.
174: *
175: * @param plugInMap the map for plug-ins
176: *
177: * @throws CurnException on error
178: */
179: static void listPlugIns(final Map<String, Class> plugInMap)
180: throws CurnException {
181: loadPlugIns();
182:
183: for (PlugIn plugIn : plugIns) {
184: String plugInName = plugIn.getPlugInName();
185: if (plugInName != null)
186: plugInMap.put(plugInName, plugIn.getClass());
187: }
188: }
189:
190: /**
191: * Load the plug-ins and create the {@link MetaPlugIn} singleton.
192: *
193: * @throws CurnException on error
194: */
195: static void loadPlugIns() throws CurnException {
196: if (!plugInsLoaded) {
197: MetaPlugIn.createMetaPlugIn();
198:
199: ClassFinder classFinder = new ClassFinder();
200:
201: // Assumes CLASSPATH has been set appropriately by the
202: // Bootstrap class. It's necessary to do it this way to support
203: // the alternate class loader.
204:
205: classFinder.addClassPath();
206:
207: // Configure the ClassFinder's filter for plug-in classes and
208: // output handler classes. Note that the criteria for both are
209: // slightly different, but we search for them at the same time
210: // for performance reasons.
211:
212: ClassFilter classFilter = new OrClassFilter(
213: // Plug-ins
214:
215: new AndClassFilter(
216: // Must implement org.clapper.curn.PlugIn
217:
218: new SubclassClassFilter(PlugIn.class),
219:
220: // Must be concrete
221:
222: new NotClassFilter(
223: new AbstractClassFilter()),
224:
225: // Must be public
226:
227: new ClassModifiersClassFilter(
228: Modifier.PUBLIC),
229:
230: // Weed out certain things
231:
232: new NotClassFilter(new RegexClassFilter(
233: "^java\\.")), new NotClassFilter(
234: new RegexClassFilter("^javax\\."))),
235:
236: // Output handlers
237:
238: new AndClassFilter(
239: // Must implement org.clapper.curn.OutputHandler
240:
241: new SubclassClassFilter(OutputHandler.class),
242:
243: // Must be concrete
244:
245: new NotClassFilter(
246: new AbstractClassFilter()),
247:
248: // Must be public
249:
250: new ClassModifiersClassFilter(
251: Modifier.PUBLIC),
252:
253: // Make sure not to include any of the packaged curn
254: // output handlers.
255:
256: new NotClassFilter(new RegexClassFilter(
257: "^org\\.clapper\\.curn"))));
258:
259: Collection<ClassInfo> classes = new ArrayList<ClassInfo>();
260: classFinder.findClasses(classes, classFilter);
261:
262: // Load any found plug-ins.
263:
264: if (classes.size() == 0)
265: log.info("No plug-ins found.");
266: else
267: loadPlugInClasses(classes);
268:
269: plugInsLoaded = true;
270: }
271: }
272:
273: /*----------------------------------------------------------------------*\
274: Private Methods
275: \*----------------------------------------------------------------------*/
276:
277: /**
278: * Load the plug-ins. For classes implementing PlugIn, the class is
279: * loaded and instantiating. For OutputHandler classes, the class is
280: * just loaded; instantiation is done by curn itself (via the
281: * OutputHandlerFactory class), if the output handler is actually used
282: * in the configuration.
283: *
284: * @param classes classes to load
285: */
286: private static void loadPlugInClasses(
287: final Collection<ClassInfo> classes) {
288: MetaPlugIn metaPlugIn = MetaPlugIn.getMetaPlugIn();
289:
290: int totalPlugInsLoaded = 0;
291:
292: for (ClassInfo classInfo : classes) {
293: String className = classInfo.getClassName();
294: try {
295: // Instantite the plug-in via the default constructor and
296: // add it to the meta-plug-in.
297:
298: Object obj = ClassUtil.instantiateClass(className);
299: if (obj instanceof PlugIn) {
300: PlugIn plugIn = (PlugIn) obj;
301: String plugInName = plugIn.getPlugInName();
302: if (plugInName == null)
303: plugInName = className;
304: log.info("Loaded \"" + plugInName + "\" plug-in");
305: metaPlugIn.addPlugIn(plugIn);
306: plugIns.add(plugIn);
307: totalPlugInsLoaded++;
308: }
309: }
310:
311: catch (ClassUtilException ex) {
312: Throwable cause = ex.getCause();
313: if (cause instanceof IllegalAccessException) {
314: // Not a big deal. Might be one of ours (e.g., MetaPlugIn).
315:
316: log
317: .info("Plug-in "
318: + classInfo.getClassLocation()
319: .getPath()
320: + "("
321: + className
322: + ") has no accessible default constructor.");
323: }
324:
325: else {
326: log.error("Cannot instantiate plug-in \""
327: + classInfo.getClassLocation().getPath()
328: + "(" + className + ")", ex);
329: }
330: }
331:
332: catch (ExceptionInInitializerError ex) {
333: log.error("Default constructor for plug-in \""
334: + classInfo.getClassLocation().getPath() + "("
335: + className + ") threw an exception.", ex
336: .getException());
337: }
338: }
339:
340: if (log.isInfoEnabled()) {
341: int totalClasses = classes.size();
342: log.info("Loaded " + totalPlugInsLoaded + " plug-in"
343: + ((totalPlugInsLoaded == 1) ? "" : "s") + " of "
344: + totalClasses + " plug-in class"
345: + ((totalClasses == 1) ? "" : "es") + " found.");
346: }
347: }
348: }
|