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