1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.loader;
20
21
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.beans.PropertyChangeSupport;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.FilePermission;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.Method;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.net.URLClassLoader;
36 import java.net.URLStreamHandlerFactory;
37 import java.util.ArrayList;
38 import java.util.jar.JarFile;
39
40 import javax.management.MBeanRegistration;
41 import javax.management.MBeanServer;
42 import javax.management.ObjectName;
43 import javax.naming.Binding;
44 import javax.naming.NameClassPair;
45 import javax.naming.NamingEnumeration;
46 import javax.naming.NamingException;
47 import javax.naming.directory.DirContext;
48 import javax.servlet.ServletContext;
49
50 import org.apache.catalina.Container;
51 import org.apache.catalina.Context;
52 import org.apache.catalina.Engine;
53 import org.apache.catalina.Globals;
54 import org.apache.catalina.Lifecycle;
55 import org.apache.catalina.LifecycleException;
56 import org.apache.catalina.LifecycleListener;
57 import org.apache.catalina.Loader;
58 import org.apache.catalina.core.StandardContext;
59 import org.apache.catalina.util.LifecycleSupport;
60 import org.apache.catalina.util.StringManager;
61 import org.apache.naming.resources.DirContextURLStreamHandler;
62 import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
63 import org.apache.naming.resources.Resource;
64 import org.apache.tomcat.util.modeler.Registry;
65
66
67 /**
68 * Classloader implementation which is specialized for handling web
69 * applications in the most efficient way, while being Catalina aware (all
70 * accesses to resources are made through the DirContext interface).
71 * This class loader supports detection of modified
72 * Java classes, which can be used to implement auto-reload support.
73 * <p>
74 * This class loader is configured by adding the pathnames of directories,
75 * JAR files, and ZIP files with the <code>addRepository()</code> method,
76 * prior to calling <code>start()</code>. When a new class is required,
77 * these repositories will be consulted first to locate the class. If it
78 * is not present, the system class loader will be used instead.
79 *
80 * @author Craig R. McClanahan
81 * @author Remy Maucherat
82 * @version $Revision: 505593 $ $Date: 2007-02-10 01:54:56 +0100 (sam., 10 févr. 2007) $
83 */
84
85 public class WebappLoader
86 implements Lifecycle, Loader, PropertyChangeListener, MBeanRegistration {
87
88 // ----------------------------------------------------------- Constructors
89
90
91 /**
92 * Construct a new WebappLoader with no defined parent class loader
93 * (so that the actual parent will be the system class loader).
94 */
95 public WebappLoader() {
96
97 this(null);
98
99 }
100
101
102 /**
103 * Construct a new WebappLoader with the specified class loader
104 * to be defined as the parent of the ClassLoader we ultimately create.
105 *
106 * @param parent The parent class loader
107 */
108 public WebappLoader(ClassLoader parent) {
109 super();
110 this.parentClassLoader = parent;
111 }
112
113
114 // ----------------------------------------------------- Instance Variables
115
116
117 /**
118 * First load of the class.
119 */
120 private static boolean first = true;
121
122
123 /**
124 * The class loader being managed by this Loader component.
125 */
126 private WebappClassLoader classLoader = null;
127
128
129 /**
130 * The Container with which this Loader has been associated.
131 */
132 private Container container = null;
133
134
135 /**
136 * The "follow standard delegation model" flag that will be used to
137 * configure our ClassLoader.
138 */
139 private boolean delegate = false;
140
141
142 /**
143 * The descriptive information about this Loader implementation.
144 */
145 private static final String info =
146 "org.apache.catalina.loader.WebappLoader/1.0";
147
148
149 /**
150 * The lifecycle event support for this component.
151 */
152 protected LifecycleSupport lifecycle = new LifecycleSupport(this);
153
154
155 /**
156 * The Java class name of the ClassLoader implementation to be used.
157 * This class should extend WebappClassLoader, otherwise, a different
158 * loader implementation must be used.
159 */
160 private String loaderClass =
161 "org.apache.catalina.loader.WebappClassLoader";
162
163
164 /**
165 * The parent class loader of the class loader we will create.
166 */
167 private ClassLoader parentClassLoader = null;
168
169
170 /**
171 * The reloadable flag for this Loader.
172 */
173 private boolean reloadable = false;
174
175
176 /**
177 * The set of repositories associated with this class loader.
178 */
179 private String repositories[] = new String[0];
180
181
182 /**
183 * The string manager for this package.
184 */
185 protected static final StringManager sm =
186 StringManager.getManager(Constants.Package);
187
188
189 /**
190 * Has this component been started?
191 */
192 private boolean started = false;
193
194
195 /**
196 * The property change support for this component.
197 */
198 protected PropertyChangeSupport support = new PropertyChangeSupport(this);
199
200
201 /**
202 * Classpath set in the loader.
203 */
204 private String classpath = null;
205
206
207 /**
208 * Repositories that are set in the loader, for JMX.
209 */
210 private ArrayList loaderRepositories = null;
211
212
213 // ------------------------------------------------------------- Properties
214
215
216 /**
217 * Return the Java class loader to be used by this Container.
218 */
219 public ClassLoader getClassLoader() {
220
221 return ((ClassLoader) classLoader);
222
223 }
224
225
226 /**
227 * Return the Container with which this Logger has been associated.
228 */
229 public Container getContainer() {
230
231 return (container);
232
233 }
234
235
236 /**
237 * Set the Container with which this Logger has been associated.
238 *
239 * @param container The associated Container
240 */
241 public void setContainer(Container container) {
242
243 // Deregister from the old Container (if any)
244 if ((this.container != null) && (this.container instanceof Context))
245 ((Context) this.container).removePropertyChangeListener(this);
246
247 // Process this property change
248 Container oldContainer = this.container;
249 this.container = container;
250 support.firePropertyChange("container", oldContainer, this.container);
251
252 // Register with the new Container (if any)
253 if ((this.container != null) && (this.container instanceof Context)) {
254 setReloadable( ((Context) this.container).getReloadable() );
255 ((Context) this.container).addPropertyChangeListener(this);
256 }
257
258 }
259
260
261 /**
262 * Return the "follow standard delegation model" flag used to configure
263 * our ClassLoader.
264 */
265 public boolean getDelegate() {
266
267 return (this.delegate);
268
269 }
270
271
272 /**
273 * Set the "follow standard delegation model" flag used to configure
274 * our ClassLoader.
275 *
276 * @param delegate The new flag
277 */
278 public void setDelegate(boolean delegate) {
279
280 boolean oldDelegate = this.delegate;
281 this.delegate = delegate;
282 support.firePropertyChange("delegate", new Boolean(oldDelegate),
283 new Boolean(this.delegate));
284
285 }
286
287
288 /**
289 * Return descriptive information about this Loader implementation and
290 * the corresponding version number, in the format
291 * <code><description>/<version></code>.
292 */
293 public String getInfo() {
294
295 return (info);
296
297 }
298
299
300 /**
301 * Return the ClassLoader class name.
302 */
303 public String getLoaderClass() {
304
305 return (this.loaderClass);
306
307 }
308
309
310 /**
311 * Set the ClassLoader class name.
312 *
313 * @param loaderClass The new ClassLoader class name
314 */
315 public void setLoaderClass(String loaderClass) {
316
317 this.loaderClass = loaderClass;
318
319 }
320
321
322 /**
323 * Return the reloadable flag for this Loader.
324 */
325 public boolean getReloadable() {
326
327 return (this.reloadable);
328
329 }
330
331
332 /**
333 * Set the reloadable flag for this Loader.
334 *
335 * @param reloadable The new reloadable flag
336 */
337 public void setReloadable(boolean reloadable) {
338
339 // Process this property change
340 boolean oldReloadable = this.reloadable;
341 this.reloadable = reloadable;
342 support.firePropertyChange("reloadable",
343 new Boolean(oldReloadable),
344 new Boolean(this.reloadable));
345
346 }
347
348
349 // --------------------------------------------------------- Public Methods
350
351
352 /**
353 * Add a property change listener to this component.
354 *
355 * @param listener The listener to add
356 */
357 public void addPropertyChangeListener(PropertyChangeListener listener) {
358
359 support.addPropertyChangeListener(listener);
360
361 }
362
363
364 /**
365 * Add a new repository to the set of repositories for this class loader.
366 *
367 * @param repository Repository to be added
368 */
369 public void addRepository(String repository) {
370
371 if (log.isDebugEnabled())
372 log.debug(sm.getString("webappLoader.addRepository", repository));
373
374 for (int i = 0; i < repositories.length; i++) {
375 if (repository.equals(repositories[i]))
376 return;
377 }
378 String results[] = new String[repositories.length + 1];
379 for (int i = 0; i < repositories.length; i++)
380 results[i] = repositories[i];
381 results[repositories.length] = repository;
382 repositories = results;
383
384 if (started && (classLoader != null)) {
385 classLoader.addRepository(repository);
386 if( loaderRepositories != null ) loaderRepositories.add(repository);
387 setClassPath();
388 }
389
390 }
391
392
393 /**
394 * Execute a periodic task, such as reloading, etc. This method will be
395 * invoked inside the classloading context of this container. Unexpected
396 * throwables will be caught and logged.
397 */
398 public void backgroundProcess() {
399 if (reloadable && modified()) {
400 try {
401 Thread.currentThread().setContextClassLoader
402 (WebappLoader.class.getClassLoader());
403 if (container instanceof StandardContext) {
404 ((StandardContext) container).reload();
405 }
406 } finally {
407 if (container.getLoader() != null) {
408 Thread.currentThread().setContextClassLoader
409 (container.getLoader().getClassLoader());
410 }
411 }
412 } else {
413 closeJARs(false);
414 }
415 }
416
417
418 /**
419 * Return the set of repositories defined for this class loader.
420 * If none are defined, a zero-length array is returned.
421 * For security reason, returns a clone of the Array (since
422 * String are immutable).
423 */
424 public String[] findRepositories() {
425
426 return ((String[])repositories.clone());
427
428 }
429
430 public String[] getRepositories() {
431 return ((String[])repositories.clone());
432 }
433
434 /** Extra repositories for this loader
435 */
436 public String getRepositoriesString() {
437 StringBuffer sb=new StringBuffer();
438 for( int i=0; i<repositories.length ; i++ ) {
439 sb.append( repositories[i]).append(":");
440 }
441 return sb.toString();
442 }
443
444 public String[] getLoaderRepositories() {
445 if( loaderRepositories==null ) return null;
446 String res[]=new String[ loaderRepositories.size()];
447 loaderRepositories.toArray(res);
448 return res;
449 }
450
451 public String getLoaderRepositoriesString() {
452 String repositories[]=getLoaderRepositories();
453 StringBuffer sb=new StringBuffer();
454 for( int i=0; i<repositories.length ; i++ ) {
455 sb.append( repositories[i]).append(":");
456 }
457 return sb.toString();
458 }
459
460
461 /**
462 * Classpath, as set in org.apache.catalina.jsp_classpath context
463 * property
464 *
465 * @return The classpath
466 */
467 public String getClasspath() {
468 return classpath;
469 }
470
471
472 /**
473 * Has the internal repository associated with this Loader been modified,
474 * such that the loaded classes should be reloaded?
475 */
476 public boolean modified() {
477
478 return (classLoader.modified());
479
480 }
481
482
483 /**
484 * Used to periodically signal to the classloader to release JAR resources.
485 */
486 public void closeJARs(boolean force) {
487 if (classLoader !=null){
488 classLoader.closeJARs(force);
489 }
490 }
491
492
493 /**
494 * Remove a property change listener from this component.
495 *
496 * @param listener The listener to remove
497 */
498 public void removePropertyChangeListener(PropertyChangeListener listener) {
499
500 support.removePropertyChangeListener(listener);
501
502 }
503
504
505 /**
506 * Return a String representation of this component.
507 */
508 public String toString() {
509
510 StringBuffer sb = new StringBuffer("WebappLoader[");
511 if (container != null)
512 sb.append(container.getName());
513 sb.append("]");
514 return (sb.toString());
515
516 }
517
518
519 // ------------------------------------------------------ Lifecycle Methods
520
521
522 /**
523 * Add a lifecycle event listener to this component.
524 *
525 * @param listener The listener to add
526 */
527 public void addLifecycleListener(LifecycleListener listener) {
528
529 lifecycle.addLifecycleListener(listener);
530
531 }
532
533
534 /**
535 * Get the lifecycle listeners associated with this lifecycle. If this
536 * Lifecycle has no listeners registered, a zero-length array is returned.
537 */
538 public LifecycleListener[] findLifecycleListeners() {
539
540 return lifecycle.findLifecycleListeners();
541
542 }
543
544
545 /**
546 * Remove a lifecycle event listener from this component.
547 *
548 * @param listener The listener to remove
549 */
550 public void removeLifecycleListener(LifecycleListener listener) {
551
552 lifecycle.removeLifecycleListener(listener);
553
554 }
555
556 private boolean initialized=false;
557
558 public void init() {
559 initialized=true;
560
561 if( oname==null ) {
562 // not registered yet - standalone or API
563 if( container instanceof StandardContext) {
564 // Register ourself. The container must be a webapp
565 try {
566 StandardContext ctx=(StandardContext)container;
567 Engine eng=(Engine)ctx.getParent().getParent();
568 String path = ctx.getPath();
569 if (path.equals("")) {
570 path = "/";
571 }
572 oname=new ObjectName(ctx.getEngineName() + ":type=Loader,path=" +
573 path + ",host=" + ctx.getParent().getName());
574 Registry.getRegistry(null, null).registerComponent(this, oname, null);
575 controller=oname;
576 } catch (Exception e) {
577 log.error("Error registering loader", e );
578 }
579 }
580 }
581
582 if( container == null ) {
583 // JMX created the loader
584 // TODO
585
586 }
587 }
588
589 public void destroy() {
590 if( controller==oname ) {
591 // Self-registration, undo it
592 Registry.getRegistry(null, null).unregisterComponent(oname);
593 oname = null;
594 }
595 initialized = false;
596
597 }
598
599 /**
600 * Start this component, initializing our associated class loader.
601 *
602 * @exception LifecycleException if a lifecycle error occurs
603 */
604 public void start() throws LifecycleException {
605 // Validate and update our current component state
606 if( ! initialized ) init();
607 if (started)
608 throw new LifecycleException
609 (sm.getString("webappLoader.alreadyStarted"));
610 if (log.isDebugEnabled())
611 log.debug(sm.getString("webappLoader.starting"));
612 lifecycle.fireLifecycleEvent(START_EVENT, null);
613 started = true;
614
615 if (container.getResources() == null) {
616 log.info("No resources for " + container);
617 return;
618 }
619 // Register a stream handler factory for the JNDI protocol
620 URLStreamHandlerFactory streamHandlerFactory =
621 new DirContextURLStreamHandlerFactory();
622 if (first) {
623 first = false;
624 try {
625 URL.setURLStreamHandlerFactory(streamHandlerFactory);
626 } catch (Exception e) {
627 // Log and continue anyway, this is not critical
628 log.error("Error registering jndi stream handler", e);
629 } catch (Throwable t) {
630 // This is likely a dual registration
631 log.info("Dual registration of jndi stream handler: "
632 + t.getMessage());
633 }
634 }
635
636 // Construct a class loader based on our current repositories list
637 try {
638
639 classLoader = createClassLoader();
640 classLoader.setResources(container.getResources());
641 classLoader.setDelegate(this.delegate);
642 if (container instanceof StandardContext)
643 classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());
644
645 for (int i = 0; i < repositories.length; i++) {
646 classLoader.addRepository(repositories[i]);
647 }
648
649 // Configure our repositories
650 setRepositories();
651 setClassPath();
652
653 setPermissions();
654
655 if (classLoader instanceof Lifecycle)
656 ((Lifecycle) classLoader).start();
657
658 // Binding the Webapp class loader to the directory context
659 DirContextURLStreamHandler.bind
660 ((ClassLoader) classLoader, this.container.getResources());
661
662 StandardContext ctx=(StandardContext)container;
663 Engine eng=(Engine)ctx.getParent().getParent();
664 String path = ctx.getPath();
665 if (path.equals("")) {
666 path = "/";
667 }
668 ObjectName cloname = new ObjectName
669 (ctx.getEngineName() + ":type=WebappClassLoader,path="
670 + path + ",host=" + ctx.getParent().getName());
671 Registry.getRegistry(null, null)
672 .registerComponent(classLoader, cloname, null);
673
674 } catch (Throwable t) {
675 log.error( "LifecycleException ", t );
676 throw new LifecycleException("start: ", t);
677 }
678
679 }
680
681
682 /**
683 * Stop this component, finalizing our associated class loader.
684 *
685 * @exception LifecycleException if a lifecycle error occurs
686 */
687 public void stop() throws LifecycleException {
688
689 // Validate and update our current component state
690 if (!started)
691 throw new LifecycleException
692 (sm.getString("webappLoader.notStarted"));
693 if (log.isDebugEnabled())
694 log.debug(sm.getString("webappLoader.stopping"));
695 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
696 started = false;
697
698 // Remove context attributes as appropriate
699 if (container instanceof Context) {
700 ServletContext servletContext =
701 ((Context) container).getServletContext();
702 servletContext.removeAttribute(Globals.CLASS_PATH_ATTR);
703 }
704
705 // Throw away our current class loader
706 if (classLoader instanceof Lifecycle)
707 ((Lifecycle) classLoader).stop();
708 DirContextURLStreamHandler.unbind((ClassLoader) classLoader);
709
710 try {
711 StandardContext ctx=(StandardContext)container;
712 Engine eng=(Engine)ctx.getParent().getParent();
713 String path = ctx.getPath();
714 if (path.equals("")) {
715 path = "/";
716 }
717 ObjectName cloname = new ObjectName
718 (ctx.getEngineName() + ":type=WebappClassLoader,path="
719 + path + ",host=" + ctx.getParent().getName());
720 Registry.getRegistry(null, null).unregisterComponent(cloname);
721 } catch (Throwable t) {
722 log.error( "LifecycleException ", t );
723 }
724
725 classLoader = null;
726
727 destroy();
728
729 }
730
731
732 // ----------------------------------------- PropertyChangeListener Methods
733
734
735 /**
736 * Process property change events from our associated Context.
737 *
738 * @param event The property change event that has occurred
739 */
740 public void propertyChange(PropertyChangeEvent event) {
741
742 // Validate the source of this event
743 if (!(event.getSource() instanceof Context))
744 return;
745 Context context = (Context) event.getSource();
746
747 // Process a relevant property change
748 if (event.getPropertyName().equals("reloadable")) {
749 try {
750 setReloadable
751 ( ((Boolean) event.getNewValue()).booleanValue() );
752 } catch (NumberFormatException e) {
753 log.error(sm.getString("webappLoader.reloadable",
754 event.getNewValue().toString()));
755 }
756 }
757
758 }
759
760
761 // ------------------------------------------------------- Private Methods
762
763
764 /**
765 * Create associated classLoader.
766 */
767 private WebappClassLoader createClassLoader()
768 throws Exception {
769
770 Class clazz = Class.forName(loaderClass);
771 WebappClassLoader classLoader = null;
772
773 if (parentClassLoader == null) {
774 parentClassLoader = container.getParentClassLoader();
775 }
776 Class[] argTypes = { ClassLoader.class };
777 Object[] args = { parentClassLoader };
778 Constructor constr = clazz.getConstructor(argTypes);
779 classLoader = (WebappClassLoader) constr.newInstance(args);
780
781 return classLoader;
782
783 }
784
785
786 /**
787 * Configure associated class loader permissions.
788 */
789 private void setPermissions() {
790
791 if (!Globals.IS_SECURITY_ENABLED)
792 return;
793 if (!(container instanceof Context))
794 return;
795
796 // Tell the class loader the root of the context
797 ServletContext servletContext =
798 ((Context) container).getServletContext();
799
800 // Assigning permissions for the work directory
801 File workDir =
802 (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
803 if (workDir != null) {
804 try {
805 String workDirPath = workDir.getCanonicalPath();
806 classLoader.addPermission
807 (new FilePermission(workDirPath, "read,write"));
808 classLoader.addPermission
809 (new FilePermission(workDirPath + File.separator + "-",
810 "read,write,delete"));
811 } catch (IOException e) {
812 // Ignore
813 }
814 }
815
816 try {
817
818 URL rootURL = servletContext.getResource("/");
819 classLoader.addPermission(rootURL);
820
821 String contextRoot = servletContext.getRealPath("/");
822 if (contextRoot != null) {
823 try {
824 contextRoot = (new File(contextRoot)).getCanonicalPath();
825 classLoader.addPermission(contextRoot);
826 } catch (IOException e) {
827 // Ignore
828 }
829 }
830
831 URL classesURL = servletContext.getResource("/WEB-INF/classes/");
832 classLoader.addPermission(classesURL);
833 URL libURL = servletContext.getResource("/WEB-INF/lib/");
834 classLoader.addPermission(libURL);
835
836 if (contextRoot != null) {
837
838 if (libURL != null) {
839 File rootDir = new File(contextRoot);
840 File libDir = new File(rootDir, "WEB-INF/lib/");
841 try {
842 String path = libDir.getCanonicalPath();
843 classLoader.addPermission(path);
844 } catch (IOException e) {
845 }
846 }
847
848 } else {
849
850 if (workDir != null) {
851 if (libURL != null) {
852 File libDir = new File(workDir, "WEB-INF/lib/");
853 try {
854 String path = libDir.getCanonicalPath();
855 classLoader.addPermission(path);
856 } catch (IOException e) {
857 }
858 }
859 if (classesURL != null) {
860 File classesDir = new File(workDir, "WEB-INF/classes/");
861 try {
862 String path = classesDir.getCanonicalPath();
863 classLoader.addPermission(path);
864 } catch (IOException e) {
865 }
866 }
867 }
868
869 }
870
871 } catch (MalformedURLException e) {
872 }
873
874 }
875
876
877 /**
878 * Configure the repositories for our class loader, based on the
879 * associated Context.
880 */
881 private void setRepositories() {
882
883 if (!(container instanceof Context))
884 return;
885 ServletContext servletContext =
886 ((Context) container).getServletContext();
887 if (servletContext == null)
888 return;
889
890 loaderRepositories=new ArrayList();
891 // Loading the work directory
892 File workDir =
893 (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
894 if (workDir == null) {
895 log.info("No work dir for " + servletContext);
896 }
897
898 if( log.isDebugEnabled())
899 log.debug(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));
900
901 classLoader.setWorkDir(workDir);
902
903 DirContext resources = container.getResources();
904
905 // Setting up the class repository (/WEB-INF/classes), if it exists
906
907 String classesPath = "/WEB-INF/classes";
908 DirContext classes = null;
909
910 try {
911 Object object = resources.lookup(classesPath);
912 if (object instanceof DirContext) {
913 classes = (DirContext) object;
914 }
915 } catch(NamingException e) {
916 // Silent catch: it's valid that no /WEB-INF/classes collection
917 // exists
918 }
919
920 if (classes != null) {
921
922 File classRepository = null;
923
924 String absoluteClassesPath =
925 servletContext.getRealPath(classesPath);
926
927 if (absoluteClassesPath != null) {
928
929 classRepository = new File(absoluteClassesPath);
930
931 } else {
932
933 classRepository = new File(workDir, classesPath);
934 classRepository.mkdirs();
935 copyDir(classes, classRepository);
936
937 }
938
939 if(log.isDebugEnabled())
940 log.debug(sm.getString("webappLoader.classDeploy", classesPath,
941 classRepository.getAbsolutePath()));
942
943
944 // Adding the repository to the class loader
945 classLoader.addRepository(classesPath + "/", classRepository);
946 loaderRepositories.add(classesPath + "/" );
947
948 }
949
950 // Setting up the JAR repository (/WEB-INF/lib), if it exists
951
952 String libPath = "/WEB-INF/lib";
953
954 classLoader.setJarPath(libPath);
955
956 DirContext libDir = null;
957 // Looking up directory /WEB-INF/lib in the context
958 try {
959 Object object = resources.lookup(libPath);
960 if (object instanceof DirContext)
961 libDir = (DirContext) object;
962 } catch(NamingException e) {
963 // Silent catch: it's valid that no /WEB-INF/lib collection
964 // exists
965 }
966
967 if (libDir != null) {
968
969 boolean copyJars = false;
970 String absoluteLibPath = servletContext.getRealPath(libPath);
971
972 File destDir = null;
973
974 if (absoluteLibPath != null) {
975 destDir = new File(absoluteLibPath);
976 } else {
977 copyJars = true;
978 destDir = new File(workDir, libPath);
979 destDir.mkdirs();
980 }
981
982 // Looking up directory /WEB-INF/lib in the context
983 try {
984 NamingEnumeration enumeration = resources.listBindings(libPath);
985 while (enumeration.hasMoreElements()) {
986
987 Binding binding = (Binding) enumeration.nextElement();
988 String filename = libPath + "/" + binding.getName();
989 if (!filename.endsWith(".jar"))
990 continue;
991
992 // Copy JAR in the work directory, always (the JAR file
993 // would get locked otherwise, which would make it
994 // impossible to update it or remove it at runtime)
995 File destFile = new File(destDir, binding.getName());
996
997 if( log.isDebugEnabled())
998 log.debug(sm.getString("webappLoader.jarDeploy", filename,
999 destFile.getAbsolutePath()));
1000
1001 Resource jarResource = (Resource) binding.getObject();
1002 if (copyJars) {
1003 if (!copy(jarResource.streamContent(),
1004 new FileOutputStream(destFile)))
1005 continue;
1006 }
1007
1008 try {
1009 JarFile jarFile = new JarFile(destFile);
1010 classLoader.addJar(filename, jarFile, destFile);
1011 } catch (Exception ex) {
1012 // Catch the exception if there is an empty jar file
1013 // Should ignore and continute loading other jar files
1014 // in the dir
1015 }
1016
1017 loaderRepositories.add( filename );
1018
1019 }
1020 } catch (NamingException e) {
1021 // Silent catch: it's valid that no /WEB-INF/lib directory
1022 // exists
1023 } catch (IOException e) {
1024 e.printStackTrace();
1025 }
1026
1027 }
1028
1029 }
1030
1031
1032 /**
1033 * Set the appropriate context attribute for our class path. This
1034 * is required only because Jasper depends on it.
1035 */
1036 private void setClassPath() {
1037
1038 // Validate our current state information
1039 if (!(container instanceof Context))
1040 return;
1041 ServletContext servletContext =
1042 ((Context) container).getServletContext();
1043 if (servletContext == null)
1044 return;
1045
1046 if (container instanceof StandardContext) {
1047 String baseClasspath =
1048 ((StandardContext) container).getCompilerClasspath();
1049 if (baseClasspath != null) {
1050 servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
1051 baseClasspath);
1052 return;
1053 }
1054 }
1055
1056 StringBuffer classpath = new StringBuffer();
1057
1058 // Assemble the class path information from our class loader chain
1059 ClassLoader loader = getClassLoader();
1060 int layers = 0;
1061 int n = 0;
1062 while (loader != null) {
1063 if (!(loader instanceof URLClassLoader)) {
1064 String cp=getClasspath( loader );
1065 if( cp==null ) {
1066 log.info( "Unknown loader " + loader + " " + loader.getClass());
1067 break;
1068 } else {
1069 if (n > 0)
1070 classpath.append(File.pathSeparator);
1071 classpath.append(cp);
1072 n++;
1073 }
1074 break;
1075 //continue;
1076 }
1077 URL repositories[] =
1078 ((URLClassLoader) loader).getURLs();
1079 for (int i = 0; i < repositories.length; i++) {
1080 String repository = repositories[i].toString();
1081 if (repository.startsWith("file://"))
1082 repository = repository.substring(7);
1083 else if (repository.startsWith("file:"))
1084 repository = repository.substring(5);
1085 else if (repository.startsWith("jndi:"))
1086 repository =
1087 servletContext.getRealPath(repository.substring(5));
1088 else
1089 continue;
1090 if (repository == null)
1091 continue;
1092 if (n > 0)
1093 classpath.append(File.pathSeparator);
1094 classpath.append(repository);
1095 n++;
1096 }
1097 loader = loader.getParent();
1098 layers++;
1099 }
1100
1101 this.classpath=classpath.toString();
1102
1103 // Store the assembled class path as a servlet context attribute
1104 servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
1105 classpath.toString());
1106
1107 }
1108
1109 // try to extract the classpath from a loader that is not URLClassLoader
1110 private String getClasspath( ClassLoader loader ) {
1111 try {
1112 Method m=loader.getClass().getMethod("getClasspath", new Class[] {});
1113 if( log.isTraceEnabled())
1114 log.trace("getClasspath " + m );
1115 if( m==null ) return null;
1116 Object o=m.invoke( loader, new Object[] {} );
1117 if( log.isDebugEnabled() )
1118 log.debug("gotClasspath " + o);
1119 if( o instanceof String )
1120 return (String)o;
1121 return null;
1122 } catch( Exception ex ) {
1123 if (log.isDebugEnabled())
1124 log.debug("getClasspath ", ex);
1125 }
1126 return null;
1127 }
1128
1129 /**
1130 * Copy directory.
1131 */
1132 private boolean copyDir(DirContext srcDir, File destDir) {
1133
1134 try {
1135
1136 NamingEnumeration enumeration = srcDir.list("");
1137 while (enumeration.hasMoreElements()) {
1138 NameClassPair ncPair =
1139 (NameClassPair) enumeration.nextElement();
1140 String name = ncPair.getName();
1141 Object object = srcDir.lookup(name);
1142 File currentFile = new File(destDir, name);
1143 if (object instanceof Resource) {
1144 InputStream is = ((Resource) object).streamContent();
1145 OutputStream os = new FileOutputStream(currentFile);
1146 if (!copy(is, os))
1147 return false;
1148 } else if (object instanceof InputStream) {
1149 OutputStream os = new FileOutputStream(currentFile);
1150 if (!copy((InputStream) object, os))
1151 return false;
1152 } else if (object instanceof DirContext) {
1153 currentFile.mkdir();
1154 copyDir((DirContext) object, currentFile);
1155 }
1156 }
1157
1158 } catch (NamingException e) {
1159 return false;
1160 } catch (IOException e) {
1161 return false;
1162 }
1163
1164 return true;
1165
1166 }
1167
1168
1169 /**
1170 * Copy a file to the specified temp directory. This is required only
1171 * because Jasper depends on it.
1172 */
1173 private boolean copy(InputStream is, OutputStream os) {
1174
1175 try {
1176 byte[] buf = new byte[4096];
1177 while (true) {
1178 int len = is.read(buf);
1179 if (len < 0)
1180 break;
1181 os.write(buf, 0, len);
1182 }
1183 is.close();
1184 os.close();
1185 } catch (IOException e) {
1186 return false;
1187 }
1188
1189 return true;
1190
1191 }
1192
1193
1194 private static org.apache.juli.logging.Log log=
1195 org.apache.juli.logging.LogFactory.getLog( WebappLoader.class );
1196
1197 private ObjectName oname;
1198 private MBeanServer mserver;
1199 private String domain;
1200 private ObjectName controller;
1201
1202 public ObjectName preRegister(MBeanServer server,
1203 ObjectName name) throws Exception {
1204 oname=name;
1205 mserver=server;
1206 domain=name.getDomain();
1207
1208 return name;
1209 }
1210
1211 public void postRegister(Boolean registrationDone) {
1212 }
1213
1214 public void preDeregister() throws Exception {
1215 }
1216
1217 public void postDeregister() {
1218 }
1219
1220 public ObjectName getController() {
1221 return controller;
1222 }
1223
1224 public void setController(ObjectName controller) {
1225 this.controller = controller;
1226 }
1227
1228 }