1 package net.sourceforge.blogentis.plugins.impl;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Enumeration;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38
39 import net.sourceforge.blogentis.om.Blog;
40 import net.sourceforge.blogentis.plugins.BlogPluginService;
41 import net.sourceforge.blogentis.plugins.IBlogExtensionPoint;
42 import net.sourceforge.blogentis.plugins.IExtensionPoint;
43 import net.sourceforge.blogentis.plugins.IPlugin;
44 import net.sourceforge.blogentis.plugins.IPluginService;
45 import net.sourceforge.blogentis.plugins.IPrefs;
46 import net.sourceforge.blogentis.utils.MappedConfiguration;
47
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.apache.turbine.Turbine;
51 import org.apache.turbine.services.InitializationException;
52 import org.apache.turbine.services.TurbineBaseService;
53
54 /***
55 * @author abas
56 */
57 public class BlogPluginServiceImpl
58 extends TurbineBaseService
59 implements IPluginService {
60
61 private static final Log log = LogFactory
62 .getLog(BlogPluginServiceImpl.class);
63
64 private IPlugin[] plugins = null;
65 private ExtensionPointList globalExtensionPoints = new ExtensionPointList();
66 private Map blogExtensionPoints = new HashMap();
67
68 private static final String PLUGIN_DESCRIPTOR = "plugins.resourceName";
69 private static final String PLUGIN_DESCRIPTOR_DEFAULT = "/plugins.list";
70 private static final String PLUGIN_CONFIG = "plugins.configFile";
71 private static final String PLUGIN_CONFIG_DEFAULT = "/WEB-INF/conf/plugins.list";
72
73 /***
74 * Get the URIs of all files named "/plugins.list"
75 *
76 * @return a List of the URIs of all files names "/plugin/list"
77 * @throws IOException
78 * an exception propagated from the ClassLoader.
79 */
80 private List getPluginFileLists()
81 throws IOException {
82 Enumeration e = this.getClass().getClassLoader()
83 .getResources(
84 getConfiguration()
85 .getString(PLUGIN_DESCRIPTOR,
86 PLUGIN_DESCRIPTOR_DEFAULT));
87 ArrayList l = new ArrayList();
88 while (e.hasMoreElements())
89 l.add(e.nextElement());
90 l.add(new File(Turbine.getRealPath(getConfiguration()
91 .getString(PLUGIN_CONFIG, PLUGIN_CONFIG_DEFAULT))).toURL());
92 return l;
93 }
94
95 /***
96 * Reads the classpath URIs in the pluginFileList and returns all plugin
97 * classes defined by these URIs
98 *
99 * The current implementation expects the URIs to point to files that
100 * contain one-per-line class names.
101 *
102 * @param pluginFileList
103 * the list of files to search for plugins
104 * @return a list of class names.
105 * @throws IOException
106 */
107 private List getPluginClassList(List pluginFileList) {
108 ArrayList l = new ArrayList();
109 for(Iterator i = pluginFileList.iterator(); i.hasNext();) {
110 URL url = (URL)i.next();
111 log.debug("Trying file " + url.toString());
112 try {
113 BufferedReader r = new BufferedReader(new InputStreamReader(url
114 .openStream(), "utf-8"));
115 String line;
116 while ((line = r.readLine()) != null) {
117 line = line.trim();
118 if (line.length() == 0 || line.charAt(0) == '#')
119 continue;
120 l.add(line);
121 }
122 } catch (Exception e) {
123 log.warn("Could not load " + url, e);
124 }
125 }
126 return l;
127 }
128
129 /***
130 *
131 * @param pluginClassnameList
132 * @return
133 */
134 private List getPluginClasses(List pluginClassnameList) {
135 ArrayList l = new ArrayList();
136 for(Iterator i = pluginClassnameList.iterator(); i.hasNext();) {
137 String className = (String)i.next();
138 log.debug("Trying class " + className);
139 try {
140 Class c = Class.forName(className);
141 if (!IPlugin.class.isAssignableFrom(c)) {
142 log.warn("Not a plugin: " + className);
143 continue;
144 }
145 l.add(c.newInstance());
146 } catch (Exception e1) {
147 log.warn("Class not found " + className, e1);
148 continue;
149 } catch (LinkageError e) {
150 log.error("Class not found " + className, e);
151 continue;
152 }
153 }
154 return l;
155 }
156
157 /***
158 * Load the plugin classes. Searches in the classpath for all instances of
159 * /plugin.list, which contains a list of classes, one per line. Empty lines
160 * and lines starting with # will be ignored. Each class so found will be
161 * loaded and checked on the implementation of the IPlugin interface.
162 *
163 * @throws Exception
164 */
165 private void initPlugins()
166 throws Exception {
167 List tmp;
168 tmp = getPluginFileLists();
169 tmp = getPluginClassList(tmp);
170 tmp = getPluginClasses(tmp);
171 plugins = (IPlugin[])tmp.toArray(new IPlugin[] {});
172 }
173
174 public void init() {
175 log.debug("Loading plugins...");
176 try {
177 initPlugins();
178 } catch (Exception e1) {
179 log.error("Could not get the list of plugins", e1);
180 plugins = new IPlugin[] {};
181 }
182 setInit(true);
183 for(int i = 0; i < plugins.length; i++) {
184 log.debug("Loading " + plugins[i].getName());
185 try {
186 plugins[i].startPlugin();
187 } catch (InitializationException e) {
188 log
189 .error("Could not initialize plugin "
190 + plugins[i].getName(), e);
191 }
192 }
193 }
194
195 public void shutdown() {
196 for(int i = plugins.length - 1; i >= 0; i--) {
197 plugins[i].stopPlugin();
198 }
199 setInit(false);
200 }
201
202 /***
203 * List the plugins in the order of their definition.
204 *
205 * @return an iterator that returns IPlugin
206 */
207 public Iterator getPlugins() {
208 return Arrays.asList(plugins).iterator();
209 }
210
211 /***
212 * Register a global extension point.
213 *
214 * @param point
215 * the global extension point to register.
216 */
217 public synchronized void registerExtensionPoint(IExtensionPoint point) {
218 globalExtensionPoints.registerExtensionPoint(point);
219 }
220
221 /***
222 * Remove a global extension point.
223 *
224 * @param point
225 * the extension point to remove.
226 */
227 public synchronized void deregisterExtensionPoint(IExtensionPoint point) {
228 globalExtensionPoints.deregisterExtensionPoint(point);
229 }
230
231 /***
232 * Register a blog-specific extension point.
233 *
234 * @param blog
235 * the blog that the extension point will apply.
236 * @param point
237 * the extension point that will be registered.
238 */
239 public synchronized void registerExtensionPoint(Blog blog,
240 IBlogExtensionPoint point) {
241 ExtensionPointList epl = (ExtensionPointList)blogExtensionPoints
242 .get(blog.getName());
243 if (epl == null) {
244 epl = new ExtensionPointList();
245 blogExtensionPoints.put(blog.getName(), epl);
246 }
247 epl.registerExtensionPoint(point);
248 }
249
250 /***
251 * Remove a blog-specific extension point
252 *
253 * @param blog
254 * the blog that should contain the extension point.
255 * @param point
256 * the extension point to remove.
257 */
258 public synchronized void deregisterExtensionPoint(Blog blog,
259 IBlogExtensionPoint point) {
260 ExtensionPointList epl = (ExtensionPointList)blogExtensionPoints
261 .get(blog.getName());
262 if (epl != null)
263 epl.deregisterExtensionPoint(point);
264 }
265
266 public IExtensionPoint locateExtensionPoint(Class className) {
267 return globalExtensionPoints.locateExtensionPoint(className);
268 }
269
270 public synchronized IBlogExtensionPoint locateExtensionPoint(Blog blog,
271 Class className) {
272 ExtensionPointList epl = getBlogExtensionList(blog);
273 return (IBlogExtensionPoint)epl.locateExtensionPoint(className);
274 }
275
276 public synchronized void reloadExtensionPointsForBlog(Blog blog) {
277 if (log.isDebugEnabled())
278 log.debug("Reloading extension points for " + blog.getName());
279 blogExtensionPoints.remove(blog.getName());
280 getBlogExtensionList(blog);
281 }
282
283 private List getValidPluginNames(Blog blog) {
284 boolean doSave = false;
285 MappedConfiguration config =blog.getConfiguration();
286
287 List pluginNames = config.getList(BlogPluginService.PLUGIN_LIST,
288 Collections.EMPTY_LIST);
289
290 List unknown = new ArrayList(pluginNames);
291 for(int j = 0; j < plugins.length; j++)
292 unknown.remove(plugins[j].getClass().getName());
293 pluginNames.removeAll(unknown);
294
295 if (pluginNames.size() == 0) {
296 pluginNames = new ArrayList(plugins.length);
297 for(int i = 0; i < plugins.length; i++)
298 pluginNames.add(plugins[i].getClass().getName());
299 doSave = true;
300 }
301 if (doSave || unknown.size() > 0) {
302 config.clearProperty(BlogPluginService.PLUGIN_LIST);
303 config.addProperty(BlogPluginService.PLUGIN_LIST, pluginNames);
304 try {
305 config.save();
306 } catch (Exception e) {
307 log.error("Could not save configuration for " + blog.getName(),
308 e);
309 }
310 }
311 return pluginNames;
312 }
313
314 private synchronized ExtensionPointList getBlogExtensionList(Blog blog) {
315 ExtensionPointList epl = (ExtensionPointList)blogExtensionPoints
316 .get(blog.getName());
317 if (epl != null)
318 return epl;
319 epl = new ExtensionPointList();
320 blogExtensionPoints.put(blog.getName(), epl);
321 List l = getValidPluginNames(blog);
322 for(Iterator i = l.iterator(); i.hasNext();) {
323 String pluginName = (String)i.next();
324 for(int j = 0; j < plugins.length; j++) {
325 if (plugins[j].getClass().getName().equals(pluginName)) {
326 plugins[j].registerInBlog(blog);
327 }
328 }
329 }
330 return epl;
331 }
332
333 public Iterator getExtensionPoints(Blog blog) {
334 return ((ExtensionPointList)blogExtensionPoints.get(blog.getName()))
335 .iterator();
336 }
337
338 public IPrefs getPreferencesFor(Blog blog, Class extensionClass) {
339 return new PrefsImpl(blog, getClass().getName());
340 }
341 }