001: /*****************************************************************************
002: * Java Plug-in Framework (JPF)
003: * Copyright (C) 2004-2007 Dmitry Olshansky
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *****************************************************************************/package org.java.plugin.boot;
019:
020: import java.io.BufferedReader;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.io.InputStreamReader;
025: import java.net.MalformedURLException;
026: import java.util.Collection;
027: import java.util.HashSet;
028: import java.util.LinkedList;
029: import java.util.List;
030: import java.util.Set;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.java.plugin.ObjectFactory;
034: import org.java.plugin.Plugin;
035: import org.java.plugin.PluginManager;
036: import org.java.plugin.PluginManager.PluginLocation;
037: import org.java.plugin.registry.IntegrityCheckReport;
038: import org.java.plugin.registry.ManifestInfo;
039: import org.java.plugin.registry.ManifestProcessingException;
040: import org.java.plugin.registry.PluginRegistry;
041: import org.java.plugin.util.ExtendedProperties;
042: import org.java.plugin.util.IoUtil;
043: import org.java.plugin.util.ResourceManager;
044:
045: /**
046: * Default implementation of the application initializer interface.
047: * <p>
048: * Supported configuration parameters:
049: * <dl>
050: * <dt>org.java.plugin.boot.applicationPlugin</dt>
051: * <dd>ID of plug-in to start. There is no default value for this parameter.
052: * In common scenario, this is the only parameter that you must provide.</dd>
053: * <dt>org.java.plugin.boot.integrityCheckMode</dt>
054: * <dd>Regulates how to check plug-ins integrity when running JPF. Possible
055: * values: <code>full</code>, <code>light</code>, <code>off</code>. The
056: * default value is <code>full</code>.</dd>
057: * <dt>org.java.plugin.boot.pluginsCollector</dt>
058: * <dd>Plug-ins location collector class, for details see
059: * {@link org.java.plugin.boot.PluginsCollector}. Default is
060: * {@link org.java.plugin.boot.DefaultPluginsCollector}.</dd>
061: * <dt>org.java.plugin.boot.pluginsWhiteList</dt>
062: * <dd>Location of the file with plug-in identifiers that should be only
063: * accepted by this application initializer. This is optional parameter.</dd>
064: * <dt>org.java.plugin.boot.pluginsBlackList</dt>
065: * <dd>Location of the file with plug-in identifiers that should not be
066: * accepted by this application initializer. This is optional parameter.</dd>
067: * </dl>
068: * Note that all given configuration parameters are passed to
069: * {@link org.java.plugin.ObjectFactory#newInstance(ExtendedProperties)}
070: * when running JPF (see bellow). This allows you to configure JPF precisely.
071: * </p>
072: * <p><i>Black and white lists of plug-ins</i></p>
073: * <p>
074: * In some situations you may want to temporary exclude some of your plug-ins
075: * from the application scope. This may be achieved with help of while and black
076: * lists - simple plain text files that contain lists of plug-in identifiers to
077: * be included/excluded from the application. Each identifies should be in
078: * separate line. You may provide unique plug-in ID also.
079: * </p>
080: * <p><i>What is application plug-in?</i></p>
081: * <p>
082: * When application starts, the
083: * {@link org.java.plugin.boot.Boot#main(String[])} method executed calling
084: * {@link #initApplication(BootErrorHandler, String[])} to get initialized
085: * instance of {@link org.java.plugin.boot.Application}
086: * (or {@link org.java.plugin.boot.ServiceApplication}) class. The method
087: * {@link #initApplication(BootErrorHandler, String[])} in this implementation
088: * scans plug-in repositories to collect all available plug-in files and folders
089: * (using special class that can be customized),
090: * instantiates JPF and publishes all discovered plug-ins. After that it asks
091: * {@link org.java.plugin.PluginManager} for an <b>Application Plug-in</b> with
092: * ID provided as configuration parameter. Returned class instance is expected
093: * to be of type {@link org.java.plugin.boot.ApplicationPlugin} and it's method
094: * {@link org.java.plugin.boot.ApplicationPlugin#initApplication(ExtendedProperties, String[])}
095: * called.
096: * </p>
097: * <p>
098: * To the mentioned <code>initApplication</code> method passed a subset of
099: * configuration properties whose names start with application plug-in ID
100: * followed with dot character <code>'.'</code> (see
101: * {@link org.java.plugin.util.ExtendedProperties#getSubset(String)} for
102: * details).
103: * </p>
104: * <p>
105: * As a result of the described procedure, the <code>Boot</code> get instance of
106: * {@link org.java.plugin.boot.Application} interface, so it can start
107: * application calling
108: * {@link org.java.plugin.boot.Application#startApplication()} method.
109: * </p>
110: *
111: * @version $Id$
112: */
113: public class DefaultApplicationInitializer implements
114: ApplicationInitializer {
115: protected static final String PARAM_APPLICATION_PLUGIN = "org.java.plugin.boot.applicationPlugin"; //$NON-NLS-1$
116: protected static final String PARAM_INTEGRITY_CHECK_MODE = "org.java.plugin.boot.integrityCheckMode"; //$NON-NLS-1$
117: protected static final String PARAM_PLUGINS_COLLECTOR = "org.java.plugin.boot.pluginsCollector"; //$NON-NLS-1$
118: protected static final String PARAM_PLUGINS_WHITE_LIST = "org.java.plugin.boot.pluginsWhiteList"; //$NON-NLS-1$
119: protected static final String PARAM_PLUGINS_BLACK_LIST = "org.java.plugin.boot.pluginsBlackList"; //$NON-NLS-1$
120:
121: private Log log;
122: private ExtendedProperties config;
123: private String integrityCheckMode;
124: private PluginsCollector collector;
125: private Set<String> whiteList;
126: private Set<String> blackList;
127:
128: /**
129: * Configures this instance and application environment. The sequence is:
130: * <ul>
131: * <li>Configure logging system. There is special code for supporting
132: * <code>Log4j</code> logging system only. All other systems support
133: * come from <code>commons-logging</code> package.</li>
134: * <li>Instantiate and configure {@link PluginsCollector} using
135: * configuration data.</li>
136: * </ul>
137: * @see org.java.plugin.boot.ApplicationInitializer#configure(
138: * org.java.plugin.util.ExtendedProperties)
139: */
140: public void configure(final ExtendedProperties configuration)
141: throws Exception {
142: // Initializing logging system.
143: String log4jConfigKey = "log4j.configuration"; //$NON-NLS-1$
144: if (System.getProperty(log4jConfigKey) == null) {
145: // Trying to find log4j configuration.
146: if (configuration.containsKey(log4jConfigKey)) {
147: System.setProperty(log4jConfigKey, configuration
148: .getProperty(log4jConfigKey));
149: } else {
150: File log4jConfig = new File(configuration
151: .getProperty("applicationRoot") //$NON-NLS-1$
152: + File.separator + "log4j.properties"); //$NON-NLS-1$
153: if (!log4jConfig.isFile()) {
154: log4jConfig = new File(configuration
155: .getProperty("applicationRoot") //$NON-NLS-1$
156: + File.separator + "log4j.xml"); //$NON-NLS-1$
157: }
158: if (log4jConfig.isFile()) {
159: try {
160: System
161: .setProperty(log4jConfigKey, IoUtil
162: .file2url(log4jConfig)
163: .toExternalForm());
164: } catch (MalformedURLException e) {
165: // ignore
166: }
167: }
168: }
169: }
170: log = LogFactory.getLog(getClass());
171: log.info("logging system initialized"); //$NON-NLS-1$
172: log.info("application root is " //$NON-NLS-1$
173: + configuration.getProperty("applicationRoot")); //$NON-NLS-1$
174: config = configuration;
175: integrityCheckMode = configuration.getProperty(
176: PARAM_INTEGRITY_CHECK_MODE, "full"); //$NON-NLS-1$
177: collector = getCollectorInstance(configuration
178: .getProperty(PARAM_PLUGINS_COLLECTOR));
179: collector.configure(configuration);
180: log.debug("plug-ins collector is " + collector); //$NON-NLS-1$
181: try {
182: whiteList = loadList(configuration.getProperty(
183: PARAM_PLUGINS_WHITE_LIST, null));
184: } catch (IOException ioe) {
185: log.warn("failed loading white list", ioe); //$NON-NLS-1$
186: }
187: if (whiteList != null) {
188: log.debug("white list loaded"); //$NON-NLS-1$
189: }
190: try {
191: blackList = loadList(configuration.getProperty(
192: PARAM_PLUGINS_BLACK_LIST, null));
193: } catch (IOException ioe) {
194: log.warn("failed loading black list", ioe); //$NON-NLS-1$
195: }
196: if (blackList != null) {
197: log.debug("black list loaded"); //$NON-NLS-1$
198: }
199: }
200:
201: private Set<String> loadList(final String location)
202: throws IOException {
203: if (location == null) {
204: return null;
205: }
206: final Set<String> result = new HashSet<String>();
207: BufferedReader reader = new BufferedReader(
208: new InputStreamReader(new FileInputStream(location),
209: "UTF-8")); //$NON-NLS-1$
210: try {
211: String line;
212: while ((line = reader.readLine()) != null) {
213: line = line.trim();
214: if (line.length() > 0) {
215: result.add(line);
216: }
217: }
218: } finally {
219: reader.close();
220: }
221: log.debug("read " + result.size() //$NON-NLS-1$
222: + " list items from " + location); //$NON-NLS-1$
223: return result;
224: }
225:
226: private PluginsCollector getCollectorInstance(final String className) {
227: if (className != null) {
228: try {
229: return (PluginsCollector) Class.forName(className)
230: .newInstance();
231: } catch (InstantiationException ie) {
232: log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
233: + className, ie);
234: } catch (IllegalAccessException iae) {
235: log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
236: + className, iae);
237: } catch (ClassNotFoundException cnfe) {
238: log.warn("failed instantiating plug-ins collector " //$NON-NLS-1$
239: + className, cnfe);
240: }
241: }
242: return new DefaultPluginsCollector();
243: }
244:
245: /**
246: * Initializes application. The sequence is:
247: * <ul>
248: * <li>Collect plug-ins locations using configured
249: * {@link PluginsCollector}.</li>
250: * <li>Get {@link PluginManager} instance from {@link ObjectFactory}
251: * using code
252: * <code>ObjectFactory.newInstance(config).createManager()</code>.</li>
253: * <li>Publish collected plug-ins using
254: * {@link PluginManager#publishPlugins(org.java.plugin.PluginManager.PluginLocation[])}.</li>
255: * <li>Check integrity if that's configured.</li>
256: * <li>Get application plug-in and call it
257: * <code>JpfApplication initApplication(Properties)</code> method.</li>
258: * <li>Return received instance of {@link Application} interface.</li>
259: * </ul>
260: *
261: * @see org.java.plugin.boot.ApplicationInitializer#initApplication(
262: * BootErrorHandler, String[])
263: */
264: public Application initApplication(
265: final BootErrorHandler errorHandler, final String[] args)
266: throws Exception {
267: // Prepare parameters to start plug-in manager.
268: log.debug("collecting plug-in locations"); //$NON-NLS-1$
269: Collection<PluginLocation> pluginLocations = collector
270: .collectPluginLocations();
271: log.debug("collected " + pluginLocations.size() //$NON-NLS-1$
272: + " plug-in locations, instantiating plug-in manager"); //$NON-NLS-1$
273: // Create instance of plug-in manager.
274: PluginManager pluginManager = ObjectFactory.newInstance(config)
275: .createManager();
276: pluginLocations = filterPluginLocations(pluginManager
277: .getRegistry(), pluginLocations);
278: log.debug(pluginLocations.size()
279: + " plug-in locations remain after " //$NON-NLS-1$
280: + "applying filters, publishing plug-ins"); //$NON-NLS-1$
281: // Publish discovered plug-in manifests and corresponding plug-in folders.
282: pluginManager.publishPlugins(pluginLocations
283: .toArray(new PluginLocation[pluginLocations.size()]));
284: if (!"off".equalsIgnoreCase(integrityCheckMode)) { //$NON-NLS-1$
285: // Check plug-in's integrity.
286: log.debug("checking plug-ins set integrity"); //$NON-NLS-1$
287: IntegrityCheckReport integrityCheckReport = pluginManager
288: .getRegistry()
289: .checkIntegrity(
290: "light" .equalsIgnoreCase(integrityCheckMode) ? null //$NON-NLS-1$
291: : pluginManager.getPathResolver());
292: log.info("integrity check done: errors - " //$NON-NLS-1$
293: + integrityCheckReport.countErrors()
294: + ", warnings - " //$NON-NLS-1$
295: + integrityCheckReport.countWarnings());
296: if (integrityCheckReport.countErrors() != 0) {
297: // Something wrong in plug-ins set.
298: log
299: .info(integrityCheckReport2str(integrityCheckReport));
300: if (!errorHandler.handleError(ResourceManager
301: .getMessage(Boot.PACKAGE_NAME,
302: "integrityCheckFailed"), //$NON-NLS-1$
303: integrityCheckReport)) {
304: return null;
305: }
306: } else if (log.isDebugEnabled()
307: && ((integrityCheckReport.countErrors() > 0) || (integrityCheckReport
308: .countWarnings() > 0))) {
309: log
310: .debug(integrityCheckReport2str(integrityCheckReport));
311: }
312: }
313: // application plug-in ID
314: String appPluginId = config
315: .getProperty(PARAM_APPLICATION_PLUGIN);
316: log.info("application plug-in is " + appPluginId); //$NON-NLS-1$
317: // get the start-up application plug-in
318: Plugin appPlugin = pluginManager.getPlugin(appPluginId);
319: log.debug("got application plug-in " + appPlugin //$NON-NLS-1$
320: + ", initializing application"); //$NON-NLS-1$
321: if (!(appPlugin instanceof ApplicationPlugin)) {
322: log.error("application plug-in class " //$NON-NLS-1$
323: + appPlugin.getClass().getName()
324: + " doesn't assignable with " //$NON-NLS-1$
325: + ApplicationPlugin.class.getName());
326: throw new ClassCastException(appPlugin.getClass().getName());
327: }
328: return ((ApplicationPlugin) appPlugin).initApplication(config
329: .getSubset(appPluginId + "."), args); //$NON-NLS-1$
330: }
331:
332: protected String integrityCheckReport2str(
333: final IntegrityCheckReport report) {
334: StringBuilder buf = new StringBuilder();
335: buf.append("integrity check report:\r\n"); //$NON-NLS-1$
336: buf.append("-------------- REPORT BEGIN -----------------\r\n"); //$NON-NLS-1$
337: for (IntegrityCheckReport.ReportItem item : report.getItems()) {
338: buf.append("\tseverity=").append(item.getSeverity()) //$NON-NLS-1$
339: .append("; code=").append(item.getCode()) //$NON-NLS-1$
340: .append("; message=").append(item.getMessage()) //$NON-NLS-1$
341: .append("; source=").append(item.getSource()) //$NON-NLS-1$
342: .append("\r\n"); //$NON-NLS-1$
343: }
344: buf.append("-------------- REPORT END -----------------"); //$NON-NLS-1$
345: return buf.toString();
346: }
347:
348: /**
349: * This method may remove unwanted plug-in locations from the given list.
350: * Standard implementation applies black/white lists logic.
351: * @param registry plug-in registry to process manifests
352: * @param pluginLocations collected plug-in locations to be filtered
353: * @throws ManifestProcessingException
354: */
355: protected Collection<PluginLocation> filterPluginLocations(
356: final PluginRegistry registry,
357: final Collection<PluginLocation> pluginLocations)
358: throws ManifestProcessingException {
359: if ((whiteList == null) && (blackList == null)) {
360: return pluginLocations;
361: }
362: final List<PluginLocation> result = new LinkedList<PluginLocation>();
363: for (PluginLocation pluginLocation : pluginLocations) {
364: ManifestInfo manifestInfo = registry
365: .readManifestInfo(pluginLocation
366: .getManifestLocation());
367: if (whiteList != null) {
368: if (isPluginInList(registry, manifestInfo, whiteList)) {
369: result.add(pluginLocation);
370: }
371: continue;
372: }
373: // blackList is not NULL here
374: if (isPluginInList(registry, manifestInfo, blackList)) {
375: continue;
376: }
377: result.add(pluginLocation);
378: }
379: return result;
380: }
381:
382: private boolean isPluginInList(final PluginRegistry registry,
383: final ManifestInfo manifestInfo, final Set<String> list) {
384: if (list.contains(manifestInfo.getId())) {
385: return true;
386: }
387: return list.contains(registry.makeUniqueId(
388: manifestInfo.getId(), manifestInfo.getVersion()));
389: }
390: }
|