KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jsmtpd > config > PluginLoader


1 /*
2  *
3  * Jsmtpd, Java SMTP daemon
4  * Copyright (C) 2005 Jean-Francois POUX, jf.poux@laposte.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  */

21 package org.jsmtpd.config;
22
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.lang.reflect.InvocationTargetException JavaDoc;
26 import java.lang.reflect.Method JavaDoc;
27 import java.lang.reflect.Modifier JavaDoc;
28
29 import javax.xml.parsers.DocumentBuilder JavaDoc;
30 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
31 import javax.xml.parsers.ParserConfigurationException JavaDoc;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.jsmtpd.core.common.IGenericPlugin;
36 import org.jsmtpd.core.common.PluginInitException;
37 import org.jsmtpd.core.common.PluginLoadingException;
38 import org.jsmtpd.core.common.PluginStore;
39 import org.jsmtpd.core.common.acl.IACL;
40 import org.jsmtpd.core.common.delivery.IDeliveryService;
41 import org.jsmtpd.core.common.dnsService.IDNSResolver;
42 import org.jsmtpd.core.common.filter.FilterTreeNode;
43 import org.jsmtpd.core.common.filter.IFilter;
44 import org.jsmtpd.core.common.inputIPFilter.IFilterIP;
45 import org.jsmtpd.core.common.smtpExtension.ISmtpExtension;
46 import org.w3c.dom.Document JavaDoc;
47 import org.w3c.dom.NamedNodeMap JavaDoc;
48 import org.w3c.dom.Node JavaDoc;
49 import org.w3c.dom.NodeList JavaDoc;
50 import org.xml.sax.ErrorHandler JavaDoc;
51 import org.xml.sax.SAXException JavaDoc;
52 import org.xml.sax.SAXParseException JavaDoc;
53
54 /**
55  * Loads the plugins, configure them and stores them in the pluginstore
56  * @author Jean-Francois POUX
57  * @see org.jsmtpd.core.common.PluginStore
58  */

59 public class PluginLoader implements ErrorHandler JavaDoc {
60
61     /**
62      * Loaded plugins are stored in this instance
63      */

64     private PluginStore store = PluginStore.getInstance();
65
66     /**
67      * To catch parse errors at end of init
68      */

69     private SAXParseException JavaDoc lastEx = null;
70
71     /**
72      * DOM document instance of plugin config file
73      */

74     private Document JavaDoc document;
75     /**
76      * This is the root node of the filter tree
77      */

78     private FilterTreeNode filterRoot = null;
79     /**
80      * Log4j instance
81      */

82     private Log log = LogFactory.getLog(PluginLoader.class);
83
84     private String JavaDoc xmlFileName;
85     
86     /**
87      * Initialise : set logger and parse xml file, checking against the xsd file.
88      * @throws SAXException
89      * @throws IOException
90      * @throws ParserConfigurationException
91      * @throws ElementNotFoundException thrown when the xml file does not contain an element
92      */

93     public void init() throws SAXException JavaDoc, IOException JavaDoc, ParserConfigurationException JavaDoc, ElementNotFoundException {
94         DocumentBuilderFactory JavaDoc factory = DocumentBuilderFactory.newInstance();
95         factory.setNamespaceAware(true);
96         factory.setValidating(true);
97
98         try {
99             factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
100         } catch (IllegalArgumentException JavaDoc x) {
101             x.printStackTrace();
102         }
103         InputStream JavaDoc xsd = this.getClass().getClassLoader().getResourceAsStream("jsmtpd-plugin-config.xsd");
104         if (xsd == null)
105             throw new IOException JavaDoc("File jsmtpd-plugin-config.xsd not found, not in classpath");
106         factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", xsd);
107
108         DocumentBuilder JavaDoc builder;
109         builder = factory.newDocumentBuilder();
110         builder.setErrorHandler(this);
111
112         InputStream JavaDoc in = this.getClass().getClassLoader().getResourceAsStream(xmlFileName);
113         if (in == null)
114             throw new IOException JavaDoc("File "+xmlFileName+" not found, not in classpath");
115
116         document = builder.parse(in);
117
118         if (lastEx != null) {
119             SAXException JavaDoc toThrow = new SAXException JavaDoc(lastEx);
120             lastEx = null;
121             throw toThrow;
122         }
123
124     }
125
126     /**
127      * Loads the DNS plugin
128      * @throws PluginLoaderException
129      */

130     public void loadDNSService() throws PluginLoaderException {
131
132         NodeList JavaDoc tmp = document.getElementsByTagName("DNSSetup");
133         Node dnsset = tmp.item(0);
134         NamedNodeMap JavaDoc map = dnsset.getAttributes();
135         String JavaDoc className = map.getNamedItem("class").getNodeValue();
136         IGenericPlugin dns = loadGenericPlugin(className, dnsset);
137         try {
138             dns.initPlugin();
139         } catch (PluginInitException e) {
140             throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
141         }
142         store.setResolver((IDNSResolver) dns);
143     }
144
145     /**
146      * Loads the local delivery service
147      * @throws PluginLoaderException
148      */

149
150     public void loadLocalDeliveryService() throws PluginLoaderException {
151
152         NodeList JavaDoc tmp = document.getElementsByTagName("LocalDeliveryService");
153         Node ldsset = tmp.item(0);
154         NamedNodeMap JavaDoc map = ldsset.getAttributes();
155         String JavaDoc className = map.getNamedItem("class").getNodeValue();
156         IGenericPlugin lds = loadGenericPlugin(className, ldsset);
157         try {
158             lds.initPlugin();
159         } catch (PluginInitException e) {
160             throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
161         }
162         store.setLocalDeliveryService((IDeliveryService) lds);
163     }
164
165     /**
166      * loads the remote delivery service
167      * @throws PluginLoaderException
168      */

169     public void loadRemoteDeliveryService() throws PluginLoaderException {
170
171         NodeList JavaDoc tmp = document.getElementsByTagName("RemoteDeliveryService");
172         Node rdsset = tmp.item(0);
173         NamedNodeMap JavaDoc map = rdsset.getAttributes();
174         String JavaDoc className = map.getNamedItem("class").getNodeValue();
175         IGenericPlugin rds = loadGenericPlugin(className, rdsset);
176         try {
177             rds.initPlugin();
178         } catch (PluginInitException e) {
179             throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
180         }
181         store.setRemoteDeliveryService((IDeliveryService) rds);
182     }
183
184     /**
185      * Load the acl plugin
186      * @throws PluginLoaderException
187      */

188     public void loadACL() throws PluginLoaderException {
189
190         NodeList JavaDoc tmp = document.getElementsByTagName("ACLSetup");
191         Node aclset = tmp.item(0);
192         NamedNodeMap JavaDoc map = aclset.getAttributes();
193         String JavaDoc className = map.getNamedItem("class").getNodeValue();
194         IGenericPlugin acl = loadGenericPlugin(className, aclset);
195         try {
196             acl.initPlugin();
197         } catch (PluginInitException e) {
198             throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
199         }
200         store.setAcl((IACL) acl);
201     }
202
203     /**
204      * Builds an image of the filterchain tree specified in the xml config file,
205      * and associates the logical names of filters to instances stored in pluginstore
206      * @throws ModuleNotFoundException
207      */

208     public void loadFilterTree() throws ModuleNotFoundException {
209         NodeList JavaDoc tmp = document.getElementsByTagName("bodyFilterTree");
210         Node treeRoot = tmp.item(0);
211
212         NodeList JavaDoc childs = treeRoot.getChildNodes();
213         for (int i = 0; i < childs.getLength(); i++) {
214             Node flt = childs.item(i);
215             if (flt.getNodeName().equals("filter")) {
216                 NamedNodeMap JavaDoc mp = flt.getAttributes();
217                 String JavaDoc logicalName = mp.getNamedItem("name").getNodeValue();
218
219                 log.debug("[tree] Loading filtree with root node [i]" + logicalName);
220                 IGenericPlugin plugin = (IGenericPlugin) PluginStore.getInstance().getPluginByLogicalName(logicalName);
221                 if (plugin instanceof IFilter) {
222                     IFilter module = (IFilter) plugin;
223                     filterRoot = new FilterTreeNode();
224                     filterRoot.setFilter(module);
225                     appendFilterTreeNode(flt, filterRoot);
226                 } else {
227                     log.fatal("The filter " + logicalName + " is not a body filter tree plugin !");
228                     throw new ModuleNotFoundException(logicalName);
229                 }
230             }
231         }
232         PluginStore.getInstance().setRootFilter(filterRoot);
233     }
234
235     /**
236      * Adds 2 nodes to a node
237      * @param node the node to append in the xml file
238      * @param pNode the image node of the xml file
239      * @throws ModuleNotFoundException thrown when the module can't be found in the PluginStore
240      */

241     public void appendFilterTreeNode(Node node, FilterTreeNode pNode) throws ModuleNotFoundException {
242         // input node is a filter
243
// if it has true filter attached
244
// lookup filter, create nod and attahce it to true, then repeat
245

246         NodeList JavaDoc childs = node.getChildNodes();
247         if (childs == null)
248             return;//has no child ?
249

250         for (int i = 0; i < childs.getLength(); i++) {
251             Node tmp = childs.item(i);
252
253             if (tmp.getNodeName().equals("true")) {
254                 NodeList JavaDoc tchilds = tmp.getChildNodes();
255                 if (tchilds != null) {
256                     for (int j = 0; j < tchilds.getLength(); j++) {
257                         Node ch = tchilds.item(j);
258                         if (ch.getNodeName().equals("filter")) {
259                             NamedNodeMap JavaDoc mp = ch.getAttributes();
260                             String JavaDoc modName = mp.getNamedItem("name").getNodeValue();
261                             log.debug("[tree] adding " + modName + " as true node to node " + pNode.getFilter().getPluginName());
262                             IGenericPlugin plugin = (IGenericPlugin) PluginStore.getInstance().getPluginByLogicalName(modName);
263                             if (plugin instanceof IFilter) {
264                                 IFilter flt = (IFilter) plugin;
265                                 FilterTreeNode newNode = new FilterTreeNode();
266                                 newNode.setFilter(flt);
267                                 pNode.setTrueNode(newNode);
268                                 newNode.setParent(pNode);
269                                 appendFilterTreeNode(ch, newNode);
270                             } else {
271                                 log.debug( "The filter " + modName + " is not a body filter tree plugin !");
272                                 throw new ModuleNotFoundException(modName);
273                             }
274                         }
275                     }
276                 }
277             }
278
279             if (tmp.getNodeName().equals("false")) {
280                 NodeList JavaDoc tchilds = tmp.getChildNodes();
281                 if (tchilds != null) {
282                     for (int j = 0; j < tchilds.getLength(); j++) {
283                         Node ch = tchilds.item(j);
284                         if (ch.getNodeName().equals("filter")) {
285                             NamedNodeMap JavaDoc mp = ch.getAttributes();
286                             String JavaDoc modName = mp.getNamedItem("name").getNodeValue();
287                             log.debug("[tree] adding " + modName + " as false node to node " + pNode.getFilter().getPluginName());
288
289                             IGenericPlugin plugin = (IGenericPlugin) PluginStore.getInstance().getPluginByLogicalName(modName);
290                             if (plugin instanceof IFilter) {
291                                 IFilter flt = (IFilter) PluginStore.getInstance().getPluginByLogicalName(modName);
292                                 FilterTreeNode newNode = new FilterTreeNode();
293                                 newNode.setFilter(flt);
294                                 pNode.setFalseNode(newNode);
295                                 newNode.setParent(pNode);
296                                 appendFilterTreeNode(ch, newNode);
297                             } else {
298                                 log.fatal("The filter " + modName + " is not a body filter tree plugin !");
299                                 throw new ModuleNotFoundException(modName);
300                             }
301                         }
302                     }
303                 }
304             }
305
306         }
307
308     }
309
310     /**
311      * Loads the instances of filter plugins
312      * @throws PluginLoaderException
313      */

314     public void loadFilters() throws PluginLoaderException {
315         NodeList JavaDoc tmp = document.getElementsByTagName("filtersetup");
316         Node filterSetup = tmp.item(0);
317
318         NodeList JavaDoc filters = filterSetup.getChildNodes();
319
320         for (int i = 0; i < filters.getLength(); i++) {
321             Node current = filters.item(i);
322             if (current.getNodeName().equals("filterInit")) {
323                 NamedNodeMap JavaDoc map = current.getAttributes();
324                 String JavaDoc className = map.getNamedItem("class").getNodeValue();
325                 String JavaDoc logicalName = map.getNamedItem("name").getNodeValue();
326                 log.debug("\tTrying to load [c]" + className + " as [i]" + logicalName);
327                 IGenericPlugin module = loadGenericPlugin(className, current);
328                 try {
329                     module.initPlugin();
330                 } catch (PluginInitException e) {
331                     throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
332                 }
333                 store.addPlugin(module, logicalName);
334                 log.debug("\tLoaded [c]" + className + " as [i]" + logicalName);
335             }
336         }
337
338     }
339
340     /**
341      * Loads the inputIPFilter chain
342      * @throws ModuleNotFoundException
343      */

344     public void loadInputIPFilterChain() throws ModuleNotFoundException {
345         NodeList JavaDoc tmp = document.getElementsByTagName("inputIPFilterChain");
346         Node treeRoot = tmp.item(0);
347
348         NodeList JavaDoc childs = treeRoot.getChildNodes();
349         for (int i = 0; i < childs.getLength(); i++) {
350             Node flt = childs.item(i);
351             if (flt.getNodeName().equals("ipFilter")) {
352                 NamedNodeMap JavaDoc mp = flt.getAttributes();
353                 String JavaDoc logicalName = mp.getNamedItem("name").getNodeValue();
354
355                 IGenericPlugin plugin = (IGenericPlugin) PluginStore.getInstance().getPluginByLogicalName(logicalName);
356                 if (plugin instanceof IFilterIP) {
357                     IFilterIP module = (IFilterIP) plugin;
358                     PluginStore.getInstance().addInputIPFilters(module);
359                 } else {
360                     log.fatal("Plugin " + logicalName + "is not an input IP Filter");
361                     throw new ModuleNotFoundException(logicalName);
362                 }
363             }
364         }
365         PluginStore.getInstance().setRootFilter(filterRoot);
366     }
367
368     public void loadSmtpExtensions() throws PluginLoaderException {
369         NodeList JavaDoc tmp = document.getElementsByTagName("smtpExtensions");
370         Node treeRoot = tmp.item(0);
371
372         NodeList JavaDoc childs = treeRoot.getChildNodes();
373         for (int i = 0; i < childs.getLength(); i++) {
374             Node extension = childs.item(i);
375             if (extension.getNodeName().equals("smtpExtension")) {
376                 NamedNodeMap JavaDoc mp = extension.getAttributes();
377                 String JavaDoc logicalName = mp.getNamedItem("name").getNodeValue();
378                 String JavaDoc className = mp.getNamedItem("class").getNodeValue();
379                 log.info("Loading SMTP Extension plugin " + logicalName + " of class " + className);
380                 IGenericPlugin plugin = loadGenericPlugin(className, extension);
381                 try {
382                     plugin.initPlugin();
383                 } catch (PluginInitException e) {
384                     throw new PluginLoaderException("The plugin failed to initialize itsefl",e);
385                 }
386                 if (plugin instanceof ISmtpExtension) {
387                     ISmtpExtension module = (ISmtpExtension) plugin;
388                     PluginStore.getInstance().addSmtpExtensions(module);
389                     log.info("Loaded SMTP Extension plugin " + plugin.getPluginName() + " as " + logicalName);
390                 } else {
391                     log.fatal("Plugin " + logicalName + "is not a valid smtp extension");
392                     throw new PluginLoaderException("The plugin "+logicalName+" is not a valid smtp extension");
393                 }
394             }
395         }
396         PluginStore.getInstance().setRootFilter(filterRoot);
397     }
398
399     /**
400      * Load a generic plugin (any services/filters)
401      * @param className the class name to use
402      * @param pNode node in the xml (will determine it's propertyset childs and apply them)
403      * @return the plugin instance configured
404      * @throws PluginLoadingException
405      * @throws IllegalArgumentException
406      * @throws MethodNotFoundException
407      * @throws MethodParameterNotSupported
408      * @throws IllegalAccessException
409      * @throws InvocationTargetException
410      */

411     private IGenericPlugin loadGenericPlugin(String JavaDoc className, Node pNode) throws PluginLoaderException {
412
413         IGenericPlugin module = null;
414         try {
415             module = (IGenericPlugin) Class.forName(className).newInstance();
416         } catch (InstantiationException JavaDoc e1) {
417             throw new PluginLoaderException("Could not instanciate target class",e1);
418         } catch (IllegalAccessException JavaDoc e1) {
419             throw new PluginLoaderException("Illegal access",e1);
420         } catch (ClassNotFoundException JavaDoc e1) {
421             throw new PluginLoaderException("The plugin class does not exists in classpath",e1);
422         }
423
424         NodeList JavaDoc tmp = pNode.getChildNodes();
425         //Apply each propertySet
426
for (int i = 0; i < tmp.getLength(); i++) {
427             Node cur = tmp.item(i);
428             if (cur.getNodeName().equals("propertyset")) {
429                 NamedNodeMap JavaDoc props = cur.getAttributes();
430                 setParam(module, props.getNamedItem("name").getNodeValue(), props.getNamedItem("value").getNodeValue());
431             }
432         }
433
434         return module;
435     }
436
437     /**
438      * Queries the class to find out the function to use
439      * Sets a parameter (eg propertyset in the xml config file) to an instance of plugin
440      * @param plug the plugin instance
441      * @param name name of the parameter
442      * @param value the string value of the parameter, another method will do the casts if necessary
443      * @throws PluginLoaderException
444      */

445     private void setParam(IGenericPlugin plug, String JavaDoc name, String JavaDoc value) throws PluginLoaderException {
446         String JavaDoc rName = name.substring(0,1).toUpperCase()+name.substring(1);
447         String JavaDoc lookup = "set" + rName;
448         Method JavaDoc method = methodLookup(plug.getClass(), lookup);
449         invoke(plug, method, value);
450         log.debug("\t[set][c]:" + plug.getClass() + ", set " + name + "=" + value);
451     }
452
453     /**
454      * Performs a recursive method lookup throught parent classes (extended)
455      * @param clazz originating class
456      * @param methodName
457      * @return the method when found
458      * @throws MethodNotFoundException
459      */

460     private Method JavaDoc methodLookup(Class JavaDoc clazz, String JavaDoc methodName) throws PluginLoaderException {
461         Method JavaDoc[] meths = clazz.getDeclaredMethods();
462         String JavaDoc ucMethodName = "set"+methodName.substring(3,4).toUpperCase() + methodName.substring(4); // replace first charg with UC.
463
for (int i = 0; i < meths.length; i++) {
464             Method JavaDoc method = meths[i];
465             if (method.getName().equals(methodName) || method.getName().equals(ucMethodName)) {
466                 if (Modifier.isPublic(method.getModifiers())) {
467                     Class JavaDoc[] params = method.getParameterTypes();
468                     if ((params == null) || (params.length != 1))
469                         throw new PluginLoaderException("Method " + method.getName() + " must have only one parameter");
470                     return method;
471                 } else {
472                     throw new PluginLoaderException("Method " + method.getName() + " is not public");
473                 }
474             }
475         }
476         //Perform lookup in the parent class if exists
477
Class JavaDoc parent = clazz.getSuperclass();
478         if (parent == null)
479             throw new PluginLoaderException("Method " + methodName + " not found in parent types");
480         else
481             return methodLookup(parent, methodName);
482     }
483
484     /**
485      * invoques a setPropertyName, casts the string value to something required by reflection
486      * @param plug plugin instance
487      * @param meth name of the method (will determine the cast needed)
488      * @param value value to apply
489      * @throws MethodParameterNotSupported
490      * @throws IllegalArgumentException
491      * @throws IllegalAccessException
492      * @throws InvocationTargetException
493      */

494     private void invoke(IGenericPlugin plug, Method JavaDoc meth, String JavaDoc value) throws PluginLoaderException {
495         Class JavaDoc[] params = meth.getParameterTypes();
496         Object JavaDoc[] effectiveParams = new Object JavaDoc[1];
497
498         try {
499             if (params[0].getName().equals("java.lang.String")) {
500                 effectiveParams[0] = (Object JavaDoc) value;
501                 meth.invoke(plug, effectiveParams);
502                 return;
503             }
504
505             if (params[0].getName().equals("int")) {
506                 effectiveParams[0] = new Integer JavaDoc(value); //note: this is correct, the invoke() will cast Interger to int type
507
meth.invoke(plug, effectiveParams);
508                 return;
509             }
510
511             if (params[0].getName().equals("boolean")) {
512                 effectiveParams[0] = new Boolean JavaDoc(value); //note: this is correct, the invoke() will cast Boolean to boolean type
513
meth.invoke(plug, effectiveParams);
514                 return;
515             }
516
517             if (params[0].getName().equals("long")) {
518                 effectiveParams[0] = new Long JavaDoc(value);
519                 meth.invoke(plug, effectiveParams);
520                 return;
521             }
522         } catch (NumberFormatException JavaDoc e) {
523             throw new PluginLoaderException("Could not parse the value from string to number",e);
524         } catch (IllegalArgumentException JavaDoc e) {
525             throw new PluginLoaderException ("Illegal argument",e);
526         } catch (IllegalAccessException JavaDoc e) {
527             throw new PluginLoaderException ("Illegal acces",e);
528         } catch (InvocationTargetException JavaDoc e) {
529             throw new PluginLoaderException ("Invocation target error",e);
530         }
531         
532         throw new PluginLoaderException("This kind of parameter is not supported");
533     }
534
535     public void warning(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
536         lastEx = exception;
537     }
538
539     public void error(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
540         lastEx = exception;
541     }
542
543     public void fatalError(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
544         lastEx = exception;
545     }
546
547     public FilterTreeNode getFilterRoot() {
548         return filterRoot;
549     }
550     public String JavaDoc getXmlFileName() {
551         return xmlFileName;
552     }
553     public void setXmlFileName(String JavaDoc xmlFileName) {
554         this.xmlFileName = xmlFileName;
555     }
556 }
Popular Tags