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/<engine>/<host> 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