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 package org.apache.catalina.core;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27 import java.util.concurrent.ExecutorService;
28 import java.util.regex.Pattern;
29
30 import javax.management.ObjectName;
31
32 import org.apache.catalina.Container;
33 import org.apache.catalina.Context;
34 import org.apache.catalina.Engine;
35 import org.apache.catalina.Globals;
36 import org.apache.catalina.Host;
37 import org.apache.catalina.JmxEnabled;
38 import org.apache.catalina.Lifecycle;
39 import org.apache.catalina.LifecycleEvent;
40 import org.apache.catalina.LifecycleException;
41 import org.apache.catalina.LifecycleListener;
42 import org.apache.catalina.Valve;
43 import org.apache.catalina.loader.WebappClassLoaderBase;
44 import org.apache.catalina.util.ContextName;
45 import org.apache.juli.logging.Log;
46 import org.apache.juli.logging.LogFactory;
47 import org.apache.tomcat.util.ExceptionUtils;
48
49 /**
50  * Standard implementation of the <b>Host</b> interface.  Each
51  * child container must be a Context implementation to process the
52  * requests directed to a particular web application.
53  *
54  * @author Craig R. McClanahan
55  * @author Remy Maucherat
56  */

57 public class StandardHost extends ContainerBase implements Host {
58
59     private static final Log log = LogFactory.getLog(StandardHost.class);
60
61     // ----------------------------------------------------------- Constructors
62
63
64     /**
65      * Create a new StandardHost component with the default basic Valve.
66      */

67     public StandardHost() {
68
69         super();
70         pipeline.setBasic(new StandardHostValve());
71
72     }
73
74
75     // ----------------------------------------------------- Instance Variables
76
77
78     /**
79      * The set of aliases for this Host.
80      */

81     private String[] aliases = new String[0];
82
83     private final Object aliasesLock = new Object();
84
85
86     /**
87      * The application root for this Host.
88      */

89     private String appBase = "webapps";
90     private volatile File appBaseFile = null;
91
92     /**
93      * The XML root for this Host.
94      */

95     private String xmlBase = null;
96
97     /**
98      * host's default config path
99      */

100     private volatile File hostConfigBase = null;
101
102     /**
103      * The auto deploy flag for this Host.
104      */

105     private boolean autoDeploy = true;
106
107
108     /**
109      * The Java class name of the default context configuration class
110      * for deployed web applications.
111      */

112     private String configClass =
113         "org.apache.catalina.startup.ContextConfig";
114
115
116     /**
117      * The Java class name of the default Context implementation class for
118      * deployed web applications.
119      */

120     private String contextClass =
121         "org.apache.catalina.core.StandardContext";
122
123
124     /**
125      * The deploy on startup flag for this Host.
126      */

127     private boolean deployOnStartup = true;
128
129
130     /**
131      * deploy Context XML config files property.
132      */

133     private boolean deployXML = !Globals.IS_SECURITY_ENABLED;
134
135
136     /**
137      * Should XML files be copied to
138      * $CATALINA_BASE/conf/&lt;engine&gt;/&lt;host&gt; by default when
139      * a web application is deployed?
140      */

141     private boolean copyXML = false;
142
143
144     /**
145      * The Java class name of the default error reporter implementation class
146      * for deployed web applications.
147      */

148     private String errorReportValveClass =
149         "org.apache.catalina.valves.ErrorReportValve";
150
151
152     /**
153      * Unpack WARs property.
154      */

155     private boolean unpackWARs = true;
156
157
158     /**
159      * Work Directory base for applications.
160      */

161     private String workDir = null;
162
163
164     /**
165      * Should we create directories upon startup for appBase and xmlBase
166      */

167     private boolean createDirs = true;
168
169
170     /**
171      * Track the class loaders for the child web applications so memory leaks
172      * can be detected.
173      */

174     private final Map<ClassLoader, String> childClassLoaders =
175             new WeakHashMap<>();
176
177
178     /**
179      * Any file or directory in {@link #appBase} that this pattern matches will
180      * be ignored by the automatic deployment process (both
181      * {@link #deployOnStartup} and {@link #autoDeploy}).
182      */

183     private Pattern deployIgnore = null;
184
185
186     private boolean undeployOldVersions = false;
187
188     private boolean failCtxIfServletStartFails = false;
189
190
191     // ------------------------------------------------------------- Properties
192
193     @Override
194     public boolean getUndeployOldVersions() {
195         return undeployOldVersions;
196     }
197
198
199     @Override
200     public void setUndeployOldVersions(boolean undeployOldVersions) {
201         this.undeployOldVersions = undeployOldVersions;
202     }
203
204
205     @Override
206     public ExecutorService getStartStopExecutor() {
207         return startStopExecutor;
208     }
209
210
211     /**
212      * Return the application root for this Host.  This can be an absolute
213      * pathname, a relative pathname, or a URL.
214      */

215     @Override
216     public String getAppBase() {
217         return this.appBase;
218     }
219
220
221     /**
222      * ({@inheritDoc}
223      */

224     @Override
225     public File getAppBaseFile() {
226
227         if (appBaseFile != null) {
228             return appBaseFile;
229         }
230
231         File file = new File(getAppBase());
232
233         // If not absolute, make it absolute
234         if (!file.isAbsolute()) {
235             file = new File(getCatalinaBase(), file.getPath());
236         }
237
238         // Make it canonical if possible
239         try {
240             file = file.getCanonicalFile();
241         } catch (IOException ioe) {
242             // Ignore
243         }
244
245         this.appBaseFile = file;
246         return file;
247     }
248
249
250     /**
251      * Set the application root for this Host.  This can be an absolute
252      * pathname, a relative pathname, or a URL.
253      *
254      * @param appBase The new application root
255      */

256     @Override
257     public void setAppBase(String appBase) {
258
259         if (appBase.trim().equals("")) {
260             log.warn(sm.getString("standardHost.problematicAppBase", getName()));
261         }
262         String oldAppBase = this.appBase;
263         this.appBase = appBase;
264         support.firePropertyChange("appBase", oldAppBase, this.appBase);
265         this.appBaseFile = null;
266     }
267
268
269     /**
270      * ({@inheritDoc}
271      */

272     @Override
273     public String getXmlBase() {
274         return this.xmlBase;
275     }
276
277
278     /**
279      * ({@inheritDoc}
280      */

281     @Override
282     public void setXmlBase(String xmlBase) {
283         String oldXmlBase = this.xmlBase;
284         this.xmlBase = xmlBase;
285         support.firePropertyChange("xmlBase", oldXmlBase, this.xmlBase);
286     }
287
288
289     /**
290      * ({@inheritDoc}
291      */

292     @Override
293     public File getConfigBaseFile() {
294         if (hostConfigBase != null) {
295             return hostConfigBase;
296         }
297         String path = null;
298         if (getXmlBase()!=null) {
299             path = getXmlBase();
300         } else {
301             StringBuilder xmlDir = new StringBuilder("conf");
302             Container parent = getParent();
303             if (parent instanceof Engine) {
304                 xmlDir.append('/');
305                 xmlDir.append(parent.getName());
306             }
307             xmlDir.append('/');
308             xmlDir.append(getName());
309             path = xmlDir.toString();
310         }
311         File file = new File(path);
312         if (!file.isAbsolute())
313             file = new File(getCatalinaBase(), path);
314         try {
315             file = file.getCanonicalFile();
316         } catch (IOException e) {// ignore
317         }
318         this.hostConfigBase = file;
319         return file;
320     }
321
322
323     /**
324      * @return <code>true</code> if the Host will attempt to create directories for appBase and xmlBase
325      * unless they already exist.
326      */

327     @Override
328     public boolean getCreateDirs() {
329         return createDirs;
330     }
331
332     /**
333      * Set to <code>true</code> if the Host should attempt to create directories for xmlBase and appBase upon startup
334      * @param createDirs the new flag value
335      */

336     @Override
337     public void setCreateDirs(boolean createDirs) {
338         this.createDirs = createDirs;
339     }
340
341     /**
342      * @return the value of the auto deploy flag.  If true, it indicates that
343      * this host's child webapps will be dynamically deployed.
344      */

345     @Override
346     public boolean getAutoDeploy() {
347         return this.autoDeploy;
348     }
349
350
351     /**
352      * Set the auto deploy flag value for this host.
353      *
354      * @param autoDeploy The new auto deploy flag
355      */

356     @Override
357     public void setAutoDeploy(boolean autoDeploy) {
358
359         boolean oldAutoDeploy = this.autoDeploy;
360         this.autoDeploy = autoDeploy;
361         support.firePropertyChange("autoDeploy", oldAutoDeploy,
362                                    this.autoDeploy);
363
364     }
365
366
367     /**
368      * @return the Java class name of the context configuration class
369      * for new web applications.
370      */

371     @Override
372     public String getConfigClass() {
373         return this.configClass;
374     }
375
376
377     /**
378      * Set the Java class name of the context configuration class
379      * for new web applications.
380      *
381      * @param configClass The new context configuration class
382      */

383     @Override
384     public void setConfigClass(String configClass) {
385
386         String oldConfigClass = this.configClass;
387         this.configClass = configClass;
388         support.firePropertyChange("configClass",
389                                    oldConfigClass, this.configClass);
390
391     }
392
393
394     /**
395      * @return the Java class name of the Context implementation class
396      * for new web applications.
397      */

398     public String getContextClass() {
399         return this.contextClass;
400     }
401
402
403     /**
404      * Set the Java class name of the Context implementation class
405      * for new web applications.
406      *
407      * @param contextClass The new context implementation class
408      */

409     public void setContextClass(String contextClass) {
410
411         String oldContextClass = this.contextClass;
412         this.contextClass = contextClass;
413         support.firePropertyChange("contextClass",
414                                    oldContextClass, this.contextClass);
415
416     }
417
418
419     /**
420      * @return the value of the deploy on startup flag.  If <code>true</code>, it indicates
421      * that this host's child webapps should be discovered and automatically
422      * deployed at startup time.
423      */

424     @Override
425     public boolean getDeployOnStartup() {
426         return this.deployOnStartup;
427     }
428
429
430     /**
431      * Set the deploy on startup flag value for this host.
432      *
433      * @param deployOnStartup The new deploy on startup flag
434      */

435     @Override
436     public void setDeployOnStartup(boolean deployOnStartup) {
437
438         boolean oldDeployOnStartup = this.deployOnStartup;
439         this.deployOnStartup = deployOnStartup;
440         support.firePropertyChange("deployOnStartup", oldDeployOnStartup,
441                                    this.deployOnStartup);
442
443     }
444
445
446     /**
447      * @return <code>true</code> if XML context descriptors should be deployed.
448      */

449     public boolean isDeployXML() {
450         return deployXML;
451     }
452
453
454     /**
455      * Deploy XML Context config files flag mutator.
456      *
457      * @param deployXML <code>true</code> if context descriptors should be deployed
458      */

459     public void setDeployXML(boolean deployXML) {
460         this.deployXML = deployXML;
461     }
462
463
464     /**
465      * @return the copy XML config file flag for this component.
466      */

467     public boolean isCopyXML() {
468         return this.copyXML;
469     }
470
471
472     /**
473      * Set the copy XML config file flag for this component.
474      *
475      * @param copyXML The new copy XML flag
476      */

477     public void setCopyXML(boolean copyXML) {
478         this.copyXML = copyXML;
479     }
480
481
482     /**
483      * @return the Java class name of the error report valve class
484      * for new web applications.
485      */

486     public String getErrorReportValveClass() {
487         return this.errorReportValveClass;
488     }
489
490
491     /**
492      * Set the Java class name of the error report valve class
493      * for new web applications.
494      *
495      * @param errorReportValveClass The new error report valve class
496      */

497     public void setErrorReportValveClass(String errorReportValveClass) {
498
499         String oldErrorReportValveClassClass = this.errorReportValveClass;
500         this.errorReportValveClass = errorReportValveClass;
501         support.firePropertyChange("errorReportValveClass",
502                                    oldErrorReportValveClassClass,
503                                    this.errorReportValveClass);
504
505     }
506
507
508     /**
509      * @return the canonical, fully qualified, name of the virtual host
510      * this Container represents.
511      */

512     @Override
513     public String getName() {
514         return name;
515     }
516
517
518     /**
519      * Set the canonical, fully qualified, name of the virtual host
520      * this Container represents.
521      *
522      * @param name Virtual host name
523      *
524      * @exception IllegalArgumentException if name is null
525      */

526     @Override
527     public void setName(String name) {
528
529         if (name == null)
530             throw new IllegalArgumentException
531                 (sm.getString("standardHost.nullName"));
532
533         name = name.toLowerCase(Locale.ENGLISH);      // Internally all names are lower case
534
535         String oldName = this.name;
536         this.name = name;
537         support.firePropertyChange("name", oldName, this.name);
538
539     }
540
541
542     /**
543      * @return <code>true</code> if WARs should be unpacked on deployment.
544      */

545     public boolean isUnpackWARs() {
546         return unpackWARs;
547     }
548
549
550     /**
551      * Unpack WARs flag mutator.
552      *
553      * @param unpackWARs <code>true</code> to unpack WARs on deployment
554      */

555     public void setUnpackWARs(boolean unpackWARs) {
556         this.unpackWARs = unpackWARs;
557     }
558
559
560     /**
561      * @return host work directory base.
562      */

563     public String getWorkDir() {
564         return workDir;
565     }
566
567
568     /**
569      * Set host work directory base.
570      *
571      * @param workDir the new base work folder for this host
572      */

573     public void setWorkDir(String workDir) {
574         this.workDir = workDir;
575     }
576
577
578     /**
579      * @return the regular expression that defines the files and directories in
580      * the host's {@link #getAppBase} that will be ignored by the automatic
581      * deployment process.
582      */

583     @Override
584     public String getDeployIgnore() {
585         if (deployIgnore == null) {
586             return null;
587         }
588         return this.deployIgnore.toString();
589     }
590
591
592     /**
593      * @return the compiled regular expression that defines the files and
594      * directories in the host's {@link #getAppBase} that will be ignored by the
595      * automatic deployment process.
596      */

597     @Override
598     public Pattern getDeployIgnorePattern() {
599         return this.deployIgnore;
600     }
601
602
603     /**
604      * Set the regular expression that defines the files and directories in
605      * the host's {@link #getAppBase} that will be ignored by the automatic
606      * deployment process.
607      *
608      * @param deployIgnore the regexp
609      */

610     @Override
611     public void setDeployIgnore(String deployIgnore) {
612         String oldDeployIgnore;
613         if (this.deployIgnore == null) {
614             oldDeployIgnore = null;
615         } else {
616             oldDeployIgnore = this.deployIgnore.toString();
617         }
618         if (deployIgnore == null) {
619             this.deployIgnore = null;
620         } else {
621             this.deployIgnore = Pattern.compile(deployIgnore);
622         }
623         support.firePropertyChange("deployIgnore",
624                                    oldDeployIgnore,
625                                    deployIgnore);
626     }
627
628
629     /**
630      * @return <code>true</code> if a webapp start should fail if a Servlet startup fails
631      */

632     public boolean isFailCtxIfServletStartFails() {
633         return failCtxIfServletStartFails;
634     }
635
636
637     /**
638      * Change the behavior of Servlet startup errors on web application starts.
639      * @param failCtxIfServletStartFails <code>false</code> to ignore errors on Servlets which
640      *    are stated when the web application starts
641      */

642     public void setFailCtxIfServletStartFails(
643             boolean failCtxIfServletStartFails) {
644         boolean oldFailCtxIfServletStartFails = this.failCtxIfServletStartFails;
645         this.failCtxIfServletStartFails = failCtxIfServletStartFails;
646         support.firePropertyChange("failCtxIfServletStartFails",
647                 oldFailCtxIfServletStartFails,
648                 failCtxIfServletStartFails);
649     }
650
651
652     // --------------------------------------------------------- Public Methods
653
654
655     /**
656      * Add an alias name that should be mapped to this same Host.
657      *
658      * @param alias The alias to be added
659      */

660     @Override
661     public void addAlias(String alias) {
662
663         alias = alias.toLowerCase(Locale.ENGLISH);
664
665         synchronized (aliasesLock) {
666             // Skip duplicate aliases
667             for (int i = 0; i < aliases.length; i++) {
668                 if (aliases[i].equals(alias))
669                     return;
670             }
671             // Add this alias to the list
672             String newAliases[] = Arrays.copyOf(aliases, aliases.length + 1);
673             newAliases[aliases.length] = alias;
674             aliases = newAliases;
675         }
676         // Inform interested listeners
677         fireContainerEvent(ADD_ALIAS_EVENT, alias);
678
679     }
680
681
682     /**
683      * Add a child Container, only if the proposed child is an implementation
684      * of Context.
685      *
686      * @param child Child container to be added
687      */

688     @Override
689     public void addChild(Container child) {
690
691         if (!(child instanceof Context))
692             throw new IllegalArgumentException
693                 (sm.getString("standardHost.notContext"));
694
695         child.addLifecycleListener(new MemoryLeakTrackingListener());
696
697         // Avoid NPE for case where Context is defined in server.xml with only a
698         // docBase
699         Context context = (Context) child;
700         if (context.getPath() == null) {
701             ContextName cn = new ContextName(context.getDocBase(), true);
702             context.setPath(cn.getPath());
703         }
704
705         super.addChild(child);
706
707     }
708
709
710     /**
711      * Used to ensure the regardless of {@link Context} implementation, a record
712      * is kept of the class loader used every time a context starts.
713      */

714     private class MemoryLeakTrackingListener implements LifecycleListener {
715         @Override
716         public void lifecycleEvent(LifecycleEvent event) {
717             if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
718                 if (event.getSource() instanceof Context) {
719                     Context context = ((Context) event.getSource());
720                     childClassLoaders.put(context.getLoader().getClassLoader(),
721                             context.getServletContext().getContextPath());
722                 }
723             }
724         }
725     }
726
727
728     /**
729      * Attempt to identify the contexts that have a class loader memory leak.
730      * This is usually triggered on context reload. Note: This method attempts
731      * to force a full garbage collection. This should be used with extreme
732      * caution on a production system.
733      *
734      * @return a list of possibly leaking contexts
735      */

736     public String[] findReloadedContextMemoryLeaks() {
737
738         System.gc();
739
740         List<String> result = new ArrayList<>();
741
742         for (Map.Entry<ClassLoader, String> entry :
743                 childClassLoaders.entrySet()) {
744             ClassLoader cl = entry.getKey();
745             if (cl instanceof WebappClassLoaderBase) {
746                 if (!((WebappClassLoaderBase) cl).getState().isAvailable()) {
747                     result.add(entry.getValue());
748                 }
749             }
750         }
751
752         return result.toArray(new String[result.size()]);
753     }
754
755     /**
756      * @return the set of alias names for this Host.  If none are defined,
757      * a zero length array is returned.
758      */

759     @Override
760     public String[] findAliases() {
761         synchronized (aliasesLock) {
762             return this.aliases;
763         }
764     }
765
766
767     /**
768      * Remove the specified alias name from the aliases for this Host.
769      *
770      * @param alias Alias name to be removed
771      */

772     @Override
773     public void removeAlias(String alias) {
774
775         alias = alias.toLowerCase(Locale.ENGLISH);
776
777         synchronized (aliasesLock) {
778
779             // Make sure this alias is currently present
780             int n = -1;
781             for (int i = 0; i < aliases.length; i++) {
782                 if (aliases[i].equals(alias)) {
783                     n = i;
784                     break;
785                 }
786             }
787             if (n < 0)
788                 return;
789
790             // Remove the specified alias
791             int j = 0;
792             String results[] = new String[aliases.length - 1];
793             for (int i = 0; i < aliases.length; i++) {
794                 if (i != n)
795                     results[j++] = aliases[i];
796             }
797             aliases = results;
798
799         }
800
801         // Inform interested listeners
802         fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
803
804     }
805
806
807     /**
808      * Start this component and implement the requirements
809      * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
810      *
811      * @exception LifecycleException if this component detects a fatal error
812      *  that prevents this component from being used
813      */

814     @Override
815     protected synchronized void startInternal() throws LifecycleException {
816
817         // Set error report valve
818         String errorValve = getErrorReportValveClass();
819         if ((errorValve != null) && (!errorValve.equals(""))) {
820             try {
821                 boolean found = false;
822                 Valve[] valves = getPipeline().getValves();
823                 for (Valve valve : valves) {
824                     if (errorValve.equals(valve.getClass().getName())) {
825                         found = true;
826                         break;
827                     }
828                 }
829                 if(!found) {
830                     Valve valve =
831                         (Valve) Class.forName(errorValve).getConstructor().newInstance();
832                     getPipeline().addValve(valve);
833                 }
834             } catch (Throwable t) {
835                 ExceptionUtils.handleThrowable(t);
836                 log.error(sm.getString(
837                         "standardHost.invalidErrorReportValveClass",
838                         errorValve), t);
839             }
840         }
841         super.startInternal();
842     }
843
844
845     // -------------------- JMX  --------------------
846     /**
847      * @return the MBean Names of the Valves associated with this Host
848      *
849      * @exception Exception if an MBean cannot be created or registered
850      */

851     public String[] getValveNames() throws Exception {
852         Valve [] valves = this.getPipeline().getValves();
853         String [] mbeanNames = new String[valves.length];
854         for (int i = 0; i < valves.length; i++) {
855             if (valves[i] instanceof JmxEnabled) {
856                 ObjectName oname = ((JmxEnabled) valves[i]).getObjectName();
857                 if (oname != null) {
858                     mbeanNames[i] = oname.toString();
859                 }
860             }
861         }
862
863         return mbeanNames;
864     }
865
866     public String[] getAliases() {
867         synchronized (aliasesLock) {
868             return aliases;
869         }
870     }
871
872     @Override
873     protected String getObjectNameKeyProperties() {
874
875         StringBuilder keyProperties = new StringBuilder("type=Host");
876         keyProperties.append(getMBeanKeyProperties());
877
878         return keyProperties.toString();
879     }
880
881 }
882