KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > whack > container > ComponentFinder


1 /**
2  * $RCSfile: ComponentFinder.java,v $
3  * $Revision: 1.1 $
4  * $Date: 2005/04/12 07:06:38 $
5  *
6  * Copyright 2005 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */

20
21 package org.jivesoftware.whack.container;
22
23 import org.dom4j.Document;
24 import org.dom4j.Element;
25 import org.dom4j.io.SAXReader;
26 import org.xmpp.component.Component;
27 import org.xmpp.component.ComponentException;
28 import org.xmpp.component.ComponentManager;
29
30 import java.io.File JavaDoc;
31 import java.io.FileFilter JavaDoc;
32 import java.io.FileOutputStream JavaDoc;
33 import java.io.InputStream JavaDoc;
34 import java.util.*;
35 import java.util.concurrent.ScheduledExecutorService JavaDoc;
36 import java.util.concurrent.ScheduledThreadPoolExecutor JavaDoc;
37 import java.util.concurrent.TimeUnit JavaDoc;
38 import java.util.jar.JarEntry JavaDoc;
39 import java.util.jar.JarFile JavaDoc;
40 import java.util.zip.ZipFile JavaDoc;
41
42 /**
43  * Loads and manages components. The <tt>components</tt> directory is monitored for any
44  * new components, and they are dynamically loaded.<p>
45  *
46  * @see Component
47  * @see ServerContainer#start()
48  * @author Matt Tucker
49  * @author Gaston Dombiak
50  */

51 public class ComponentFinder {
52
53     private File JavaDoc componentDirectory;
54     private Map<String JavaDoc,Component> components;
55     private Map<Component,ComponentClassLoader> classloaders;
56     private Map<Component,File JavaDoc> componentDirs;
57     private Map<Component, String JavaDoc> componentDomains;
58     private boolean setupMode = true;
59     private ComponentManager manager;
60     private ScheduledExecutorService JavaDoc executor = null;
61
62     /**
63      * Constructs a new component manager.
64      *
65      * @param componentDir the component directory.
66      */

67     public ComponentFinder(ServerContainer server, File JavaDoc componentDir) {
68         this.componentDirectory = componentDir;
69         components = new HashMap<String JavaDoc,Component>();
70         componentDirs = new HashMap<Component,File JavaDoc>();
71         classloaders = new HashMap<Component,ComponentClassLoader>();
72         componentDomains = new HashMap<Component,String JavaDoc>();
73         manager = server.getManager();
74         setupMode = server.isSetupMode();
75     }
76
77     /**
78      * Starts the service that looks for components.
79      */

80     public void start() {
81         executor = new ScheduledThreadPoolExecutor JavaDoc(1);
82         executor.scheduleWithFixedDelay(new ComponentMonitor(), 0, 10, TimeUnit.SECONDS);
83     }
84
85     /**
86      * Shuts down running components that were found by the service.
87      */

88     public void shutdown() {
89         // Stop the component monitoring service.
90
if (executor != null) {
91             executor.shutdown();
92         }
93         // Shutdown found components.
94
for (String JavaDoc subdomain : componentDomains.values()) {
95             try {
96                 manager.removeComponent(subdomain);
97             } catch (ComponentException e) {
98                 manager.getLog().error("Error shutting down component", e);
99             }
100         }
101         components.clear();
102         componentDirs.clear();
103         classloaders.clear();
104         componentDomains.clear();
105     }
106
107     /**
108      * Returns a Collection of all found components.
109      *
110      * @return a Collection of all found components.
111      */

112     public Collection<Component> getComponents() {
113         return Collections.unmodifiableCollection(components.values());
114     }
115
116     /**
117      * Returns a component by name or <tt>null</tt> if a component with that name does not
118      * exist. The name is the name of the directory that the component is in such as
119      * "broadcast".
120      *
121      * @param name the name of the component.
122      * @return the component.
123      */

124     public Component getComponent(String JavaDoc name) {
125         return components.get(name);
126     }
127
128     /**
129      * Returns the component's directory.
130      *
131      * @param component the component.
132      * @return the component's directory.
133      */

134     public File JavaDoc getComponentDirectory(Component component) {
135         return componentDirs.get(component);
136     }
137
138     /**
139      * Loads a plug-in module into the container. Loading consists of the
140      * following steps:<ul>
141      *
142      * <li>Add all jars in the <tt>lib</tt> dir (if it exists) to the class loader</li>
143      * <li>Add all files in <tt>classes</tt> dir (if it exists) to the class loader</li>
144      * <li>Locate and load <tt>module.xml</tt> into the context</li>
145      * <li>For each jive.module entry, load the given class as a module and start it</li>
146      *
147      * </ul>
148      *
149      * @param componentDir the component directory.
150      */

151     private void loadComponent(File JavaDoc componentDir) {
152         // Do not load & start any component if in setup mode
153
if (setupMode) {
154             return;
155         }
156         manager.getLog().debug("Loading component: " + componentDir.getName());
157         Component component = null;
158         try {
159             File JavaDoc componentConfig = new File JavaDoc(componentDir, "component.xml");
160             if (componentConfig.exists()) {
161                 SAXReader saxReader = new SAXReader();
162                 Document componentXML = saxReader.read(componentConfig);
163                 ComponentClassLoader classLoader = new ComponentClassLoader(componentDir);
164                 String JavaDoc className = componentXML.selectSingleNode("/component/class").getText();
165                 String JavaDoc subdomain = componentXML.selectSingleNode("/component/subdomain").getText();
166                 //component = (Component)classLoader.loadClass(className).newInstance();
167
Class JavaDoc aClass = classLoader.loadClass(className);
168                 component = (Component)aClass.newInstance();
169
170                 manager.addComponent(subdomain, component);
171
172                 components.put(componentDir.getName(), component);
173                 componentDirs.put(component, componentDir);
174                 classloaders.put(component, classLoader);
175                 componentDomains.put(component, subdomain);
176                 // Load any JSP's defined by the component.
177
File JavaDoc webXML = new File JavaDoc(componentDir, "web" + File.separator + "web.xml");
178                 if (webXML.exists()) {
179                     ComponentServlet.registerServlets(this, component, webXML);
180                 }
181                 // If there a <adminconsole> section defined, register it.
182
Element adminElement = (Element)componentXML.selectSingleNode("/component/adminconsole");
183                 if (adminElement != null) {
184                     // If global images are specified, override their URL.
185
Element imageEl = (Element)adminElement.selectSingleNode(
186                             "/component/adminconsole/global/logo-image");
187                     if (imageEl != null) {
188                         imageEl.setText("components/" + componentDir.getName() + "/" + imageEl.getText());
189                     }
190                     imageEl = (Element)adminElement.selectSingleNode(
191                             "/component/adminconsole/global/login-image");
192                     if (imageEl != null) {
193                         imageEl.setText("components/" + componentDir.getName() + "/" + imageEl.getText());
194                     }
195                     // Modify all the URL's in the XML so that they are passed through
196
// the component servlet correctly.
197
/*List urls = adminElement.selectNodes("//@url");
198                     for (int i=0; i<urls.size(); i++) {
199                         Attribute attr = (Attribute)urls.get(i);
200                         attr.setValue("components/" + componentDir.getName() + "/" + attr.getValue());
201                     }
202                     AdminConsole.addModel(componentDir.getName(), adminElement);*/

203                 }
204             }
205             else {
206                 manager.getLog().warn("Component " + componentDir + " could not be loaded: no component.xml file found");
207             }
208         }
209         catch (Exception JavaDoc e) {
210             manager.getLog().error("Error loading component: " + componentDir.getName(), e);
211         }
212     }
213
214     /**
215      * Unloads a component. The {@link ComponentManager#removeComponent(String)} method will be
216      * called and then any resources will be released. The name should be the name of the component
217      * directory and not the name as given by the component meta-data. This method only removes
218      * the component but does not delete the component JAR file. Therefore, if the component JAR
219      * still exists after this method is called, the component will be started again the next
220      * time the component monitor process runs. This is useful for "restarting" components.<p>
221      *
222      * This method is called automatically when a component's JAR file is deleted.
223      *
224      * @param componentName the name of the component to unload.
225      */

226     public void unloadComponent(String JavaDoc componentName) {
227         manager.getLog().debug("Unloading component " + componentName);
228         Component component = components.get(componentName);
229         if (component == null) {
230             return;
231         }
232         File JavaDoc webXML = new File JavaDoc(componentDirectory + File.separator + componentName +
233                 File.separator + "web" + File.separator + "web.xml");
234         if (webXML.exists()) {
235             //AdminConsole.removeModel(componentName);
236
ComponentServlet.unregisterServlets(webXML);
237         }
238
239         ComponentClassLoader classLoader = classloaders.get(component);
240         try {
241             manager.removeComponent(componentDomains.get(component));
242         } catch (ComponentException e) {
243             manager.getLog().error("Error shutting down component", e);
244         }
245         classLoader.destroy();
246         components.remove(componentName);
247         componentDirs.remove(component);
248         classloaders.remove(component);
249         componentDomains.remove(component);
250     }
251
252     public Class JavaDoc loadClass(String JavaDoc className, Component component) throws ClassNotFoundException JavaDoc,
253             IllegalAccessException JavaDoc, InstantiationException JavaDoc
254     {
255         ComponentClassLoader loader = classloaders.get(component);
256         return loader.loadClass(className);
257     }
258
259     /**
260      * Returns the name of a component. The value is retrieved from the component.xml file
261      * of the component. If the value could not be found, <tt>null</tt> will be returned.
262      * Note that this value is distinct from the name of the component directory.
263      *
264      * @param component the component.
265      * @return the component's name.
266      */

267     public String JavaDoc getName(Component component) {
268         String JavaDoc name = getElementValue(component, "/component/name");
269         if (name != null) {
270             return name;
271         }
272         else {
273             return componentDirs.get(component).getName();
274         }
275     }
276
277     /**
278      * Returns the description of a component. The value is retrieved from the component.xml file
279      * of the component. If the value could not be found, <tt>null</tt> will be returned.
280      *
281      * @param component the component.
282      * @return the component's description.
283      */

284     public String JavaDoc getDescription(Component component) {
285         return getElementValue(component, "/component/description");
286     }
287
288     /**
289      * Returns the author of a component. The value is retrieved from the component.xml file
290      * of the component. If the value could not be found, <tt>null</tt> will be returned.
291      *
292      * @param component the component.
293      * @return the component's author.
294      */

295     public String JavaDoc getAuthor(Component component) {
296         return getElementValue(component, "/component/author");
297     }
298
299     /**
300      * Returns the version of a component. The value is retrieved from the component.xml file
301      * of the component. If the value could not be found, <tt>null</tt> will be returned.
302      *
303      * @param component the component.
304      * @return the component's version.
305      */

306     public String JavaDoc getVersion(Component component) {
307         return getElementValue(component, "/component/version");
308     }
309
310     /**
311      * Returns the value of an element selected via an xpath expression from
312      * a component's component.xml file.
313      *
314      * @param component the component.
315      * @param xpath the xpath expression.
316      * @return the value of the element selected by the xpath expression.
317      */

318     private String JavaDoc getElementValue(Component component, String JavaDoc xpath) {
319         File JavaDoc componentDir = componentDirs.get(component);
320         if (componentDir == null) {
321             return null;
322         }
323         try {
324             File JavaDoc componentConfig = new File JavaDoc(componentDir, "component.xml");
325             if (componentConfig.exists()) {
326                 SAXReader saxReader = new SAXReader();
327                 Document componentXML = saxReader.read(componentConfig);
328                 Element element = (Element)componentXML.selectSingleNode(xpath);
329                 if (element != null) {
330                     return element.getTextTrim();
331                 }
332             }
333         }
334         catch (Exception JavaDoc e) {
335             manager.getLog().error(e);
336         }
337         return null;
338     }
339
340     /**
341      * A service that monitors the component directory for components. It periodically
342      * checks for new component JAR files and extracts them if they haven't already
343      * been extracted. Then, any new component directories are loaded.
344      */

345     private class ComponentMonitor implements Runnable JavaDoc {
346
347         public void run() {
348             try {
349                 File JavaDoc [] jars = componentDirectory.listFiles(new FileFilter JavaDoc() {
350                     public boolean accept(File JavaDoc pathname) {
351                         String JavaDoc fileName = pathname.getName().toLowerCase();
352                         return (fileName.endsWith(".jar") || fileName.endsWith(".war"));
353                     }
354                 });
355
356                 for (int i=0; i<jars.length; i++) {
357                     File JavaDoc jarFile = jars[i];
358                     String JavaDoc componentName = jarFile.getName().substring(
359                             0, jarFile.getName().length()-4).toLowerCase();
360                     // See if the JAR has already been exploded.
361
File JavaDoc dir = new File JavaDoc(componentDirectory, componentName);
362                     // If the JAR hasn't been exploded, do so.
363
if (!dir.exists()) {
364                         unzipComponent(componentName, jarFile, dir);
365                     }
366                     // See if the JAR is newer than the directory. If so, the component
367
// needs to be unloaded and then reloaded.
368
else if (jarFile.lastModified() > dir.lastModified()) {
369                         unloadComponent(componentName);
370                         // Ask the system to clean up references.
371
System.gc();
372                         while (!deleteDir(dir)) {
373                             manager.getLog().error("Error unloading component " + componentName + ". " +
374                                     "Will attempt again momentarily.");
375                             Thread.sleep(5000);
376                         }
377                         // Now unzip the component.
378
unzipComponent(componentName, jarFile, dir);
379                     }
380                 }
381
382                 File JavaDoc [] dirs = componentDirectory.listFiles(new FileFilter JavaDoc() {
383                     public boolean accept(File JavaDoc pathname) {
384                         return pathname.isDirectory();
385                     }
386                 });
387
388                 for (int i=0; i<dirs.length; i++) {
389                     File JavaDoc dirFile = dirs[i];
390                     // If the component hasn't already been started, start it.
391
if (!components.containsKey(dirFile.getName())) {
392                         loadComponent(dirFile);
393                     }
394                 }
395
396                 // See if any currently running components need to be unloaded
397
// due to its JAR file being deleted.
398
if (components.size() > jars.length + 1) {
399                     // Build a list of components to delete first so that the components
400
// keyset is modified as we're iterating through it.
401
List<String JavaDoc> toDelete = new ArrayList<String JavaDoc>();
402                     for (String JavaDoc componentName : components.keySet()) {
403                         File JavaDoc file = new File JavaDoc(componentDirectory, componentName + ".jar");
404                         if (!file.exists()) {
405                             toDelete.add(componentName);
406                         }
407                     }
408                     for (String JavaDoc componentName : toDelete) {
409                         unloadComponent(componentName);
410                         System.gc();
411                         while (!deleteDir(new File JavaDoc(componentDirectory, componentName))) {
412                             manager.getLog().error("Error unloading component " + componentName + ". " +
413                                     "Will attempt again momentarily.");
414                             Thread.sleep(5000);
415                         }
416                     }
417                 }
418             }
419             catch (Exception JavaDoc e) {
420                 manager.getLog().error(e);
421             }
422         }
423
424         /**
425          * Unzips a component from a JAR file into a directory. If the JAR file
426          * isn't a component, this method will do nothing.
427          *
428          * @param componentName the name of the component.
429          * @param file the JAR file
430          * @param dir the directory to extract the component to.
431          */

432         private void unzipComponent(String JavaDoc componentName, File JavaDoc file, File JavaDoc dir) {
433             try {
434                 ZipFile JavaDoc zipFile = new JarFile JavaDoc(file);
435                 // Ensure that this JAR is a component.
436
if (zipFile.getEntry("component.xml") == null) {
437                     return;
438                 }
439                 dir.mkdir();
440                 manager.getLog().debug("Extracting component: " + componentName);
441                 for (Enumeration e=zipFile.entries(); e.hasMoreElements(); ) {
442                     JarEntry JavaDoc entry = (JarEntry JavaDoc)e.nextElement();
443                     File JavaDoc entryFile = new File JavaDoc(dir, entry.getName());
444                     // Ignore any manifest.mf entries.
445
if (entry.getName().toLowerCase().endsWith("manifest.mf")) {
446                         continue;
447                     }
448                     if (!entry.isDirectory()) {
449                         entryFile.getParentFile().mkdirs();
450                         FileOutputStream JavaDoc out = new FileOutputStream JavaDoc(entryFile);
451                         InputStream JavaDoc zin = zipFile.getInputStream(entry);
452                         byte [] b = new byte[512];
453                         int len = 0;
454                         while ( (len=zin.read(b))!= -1 ) {
455                             out.write(b,0,len);
456                         }
457                         out.flush();
458                         out.close();
459                         zin.close();
460                     }
461                 }
462                 zipFile.close();
463                 zipFile = null;
464             }
465             catch (Exception JavaDoc e) {
466                 manager.getLog().error(e);
467             }
468         }
469
470         /**
471          * Deletes a directory.
472          */

473          public boolean deleteDir(File JavaDoc dir) {
474             if (dir.isDirectory()) {
475                 String JavaDoc[] children = dir.list();
476                 for (int i=0; i<children.length; i++) {
477                     boolean success = deleteDir(new File JavaDoc(dir, children[i]));
478                     if (!success) {
479                         return false;
480                     }
481                 }
482             }
483             return dir.delete();
484         }
485     }
486 }
487
Popular Tags