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.core;
20
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.WeakHashMap;
26
27 import javax.management.MBeanServer;
28 import javax.management.ObjectName;
29
30 import org.apache.catalina.Container;
31 import org.apache.catalina.Context;
32 import org.apache.catalina.Host;
33 import org.apache.catalina.Lifecycle;
34 import org.apache.catalina.LifecycleEvent;
35 import org.apache.catalina.LifecycleException;
36 import org.apache.catalina.LifecycleListener;
37 import org.apache.catalina.Valve;
38 import org.apache.catalina.loader.WebappClassLoader;
39 import org.apache.catalina.startup.HostConfig;
40 import org.apache.catalina.valves.ValveBase;
41 import org.apache.tomcat.util.modeler.Registry;
42
43
44 /**
45 * Standard implementation of the <b>Host</b> interface. Each
46 * child container must be a Context implementation to process the
47 * requests directed to a particular web application.
48 *
49 * @author Craig R. McClanahan
50 * @author Remy Maucherat
51 * @version $Revision: 915603 $ $Date: 2010-02-24 01:07:06 +0100 (Wed, 24 Feb 2010) $
52 */
53
54 public class StandardHost
55 extends ContainerBase
56 implements Host
57 {
58 /* Why do we implement deployer and delegate to deployer ??? */
59
60 private static org.apache.juli.logging.Log log=
61 org.apache.juli.logging.LogFactory.getLog( StandardHost.class );
62
63 // ----------------------------------------------------------- Constructors
64
65
66 /**
67 * Create a new StandardHost component with the default basic Valve.
68 */
69 public StandardHost() {
70
71 super();
72 pipeline.setBasic(new StandardHostValve());
73
74 }
75
76
77 // ----------------------------------------------------- Instance Variables
78
79
80 /**
81 * The set of aliases for this Host.
82 */
83 private String[] aliases = new String[0];
84
85 private final Object aliasesLock = new Object();
86
87
88 /**
89 * The application root for this Host.
90 */
91 private String appBase = "webapps";
92
93
94 /**
95 * The auto deploy flag for this Host.
96 */
97 private boolean autoDeploy = true;
98
99
100 /**
101 * The Java class name of the default context configuration class
102 * for deployed web applications.
103 */
104 private String configClass =
105 "org.apache.catalina.startup.ContextConfig";
106
107
108 /**
109 * The Java class name of the default Context implementation class for
110 * deployed web applications.
111 */
112 private String contextClass =
113 "org.apache.catalina.core.StandardContext";
114
115
116 /**
117 * The deploy on startup flag for this Host.
118 */
119 private boolean deployOnStartup = true;
120
121
122 /**
123 * deploy Context XML config files property.
124 */
125 private boolean deployXML = true;
126
127
128 /**
129 * The Java class name of the default error reporter implementation class
130 * for deployed web applications.
131 */
132 private String errorReportValveClass =
133 "org.apache.catalina.valves.ErrorReportValve";
134
135 /**
136 * The object name for the errorReportValve.
137 */
138 private ObjectName errorReportValveObjectName = null;
139
140 /**
141 * The descriptive information string for this implementation.
142 */
143 private static final String info =
144 "org.apache.catalina.core.StandardHost/1.0";
145
146
147 /**
148 * The live deploy flag for this Host.
149 */
150 private boolean liveDeploy = true;
151
152
153 /**
154 * Unpack WARs property.
155 */
156 private boolean unpackWARs = true;
157
158
159 /**
160 * Work Directory base for applications.
161 */
162 private String workDir = null;
163
164
165 /**
166 * Attribute value used to turn on/off XML validation
167 */
168 private boolean xmlValidation = false;
169
170
171 /**
172 * Attribute value used to turn on/off XML namespace awarenes.
173 */
174 private boolean xmlNamespaceAware = false;
175
176 /**
177 * Track the class loaders for the child web applications so memory leaks
178 * can be detected.
179 */
180 private Map<ClassLoader, String> childClassLoaders =
181 new WeakHashMap<ClassLoader, String>();
182
183 // ------------------------------------------------------------- Properties
184
185
186 /**
187 * Return the application root for this Host. This can be an absolute
188 * pathname, a relative pathname, or a URL.
189 */
190 public String getAppBase() {
191
192 return (this.appBase);
193
194 }
195
196
197 /**
198 * Set the application root for this Host. This can be an absolute
199 * pathname, a relative pathname, or a URL.
200 *
201 * @param appBase The new application root
202 */
203 public void setAppBase(String appBase) {
204
205 String oldAppBase = this.appBase;
206 this.appBase = appBase;
207 support.firePropertyChange("appBase", oldAppBase, this.appBase);
208
209 }
210
211
212 /**
213 * Return the value of the auto deploy flag. If true, it indicates that
214 * this host's child webapps will be dynamically deployed.
215 */
216 public boolean getAutoDeploy() {
217
218 return (this.autoDeploy);
219
220 }
221
222
223 /**
224 * Set the auto deploy flag value for this host.
225 *
226 * @param autoDeploy The new auto deploy flag
227 */
228 public void setAutoDeploy(boolean autoDeploy) {
229
230 boolean oldAutoDeploy = this.autoDeploy;
231 this.autoDeploy = autoDeploy;
232 support.firePropertyChange("autoDeploy", oldAutoDeploy,
233 this.autoDeploy);
234
235 }
236
237
238 /**
239 * Return the Java class name of the context configuration class
240 * for new web applications.
241 */
242 public String getConfigClass() {
243
244 return (this.configClass);
245
246 }
247
248
249 /**
250 * Set the Java class name of the context configuration class
251 * for new web applications.
252 *
253 * @param configClass The new context configuration class
254 */
255 public void setConfigClass(String configClass) {
256
257 String oldConfigClass = this.configClass;
258 this.configClass = configClass;
259 support.firePropertyChange("configClass",
260 oldConfigClass, this.configClass);
261
262 }
263
264
265 /**
266 * Return the Java class name of the Context implementation class
267 * for new web applications.
268 */
269 public String getContextClass() {
270
271 return (this.contextClass);
272
273 }
274
275
276 /**
277 * Set the Java class name of the Context implementation class
278 * for new web applications.
279 *
280 * @param contextClass The new context implementation class
281 */
282 public void setContextClass(String contextClass) {
283
284 String oldContextClass = this.contextClass;
285 this.contextClass = contextClass;
286 support.firePropertyChange("contextClass",
287 oldContextClass, this.contextClass);
288
289 }
290
291
292 /**
293 * Return the value of the deploy on startup flag. If true, it indicates
294 * that this host's child webapps should be discovred and automatically
295 * deployed at startup time.
296 */
297 public boolean getDeployOnStartup() {
298
299 return (this.deployOnStartup);
300
301 }
302
303
304 /**
305 * Set the deploy on startup flag value for this host.
306 *
307 * @param deployOnStartup The new deploy on startup flag
308 */
309 public void setDeployOnStartup(boolean deployOnStartup) {
310
311 boolean oldDeployOnStartup = this.deployOnStartup;
312 this.deployOnStartup = deployOnStartup;
313 support.firePropertyChange("deployOnStartup", oldDeployOnStartup,
314 this.deployOnStartup);
315
316 }
317
318
319 /**
320 * Deploy XML Context config files flag accessor.
321 */
322 public boolean isDeployXML() {
323
324 return (deployXML);
325
326 }
327
328
329 /**
330 * Deploy XML Context config files flag mutator.
331 */
332 public void setDeployXML(boolean deployXML) {
333
334 this.deployXML = deployXML;
335
336 }
337
338
339 /**
340 * Return the value of the live deploy flag. If true, it indicates that
341 * a background thread should be started that looks for web application
342 * context files, WAR files, or unpacked directories being dropped in to
343 * the <code>appBase</code> directory, and deploys new ones as they are
344 * encountered.
345 */
346 public boolean getLiveDeploy() {
347 return (this.autoDeploy);
348 }
349
350
351 /**
352 * Set the live deploy flag value for this host.
353 *
354 * @param liveDeploy The new live deploy flag
355 */
356 public void setLiveDeploy(boolean liveDeploy) {
357 setAutoDeploy(liveDeploy);
358 }
359
360
361 /**
362 * Return the Java class name of the error report valve class
363 * for new web applications.
364 */
365 public String getErrorReportValveClass() {
366
367 return (this.errorReportValveClass);
368
369 }
370
371
372 /**
373 * Set the Java class name of the error report valve class
374 * for new web applications.
375 *
376 * @param errorReportValveClass The new error report valve class
377 */
378 public void setErrorReportValveClass(String errorReportValveClass) {
379
380 String oldErrorReportValveClassClass = this.errorReportValveClass;
381 this.errorReportValveClass = errorReportValveClass;
382 support.firePropertyChange("errorReportValveClass",
383 oldErrorReportValveClassClass,
384 this.errorReportValveClass);
385
386 }
387
388
389 /**
390 * Return the canonical, fully qualified, name of the virtual host
391 * this Container represents.
392 */
393 public String getName() {
394
395 return (name);
396
397 }
398
399
400 /**
401 * Set the canonical, fully qualified, name of the virtual host
402 * this Container represents.
403 *
404 * @param name Virtual host name
405 *
406 * @exception IllegalArgumentException if name is null
407 */
408 public void setName(String name) {
409
410 if (name == null)
411 throw new IllegalArgumentException
412 (sm.getString("standardHost.nullName"));
413
414 name = name.toLowerCase(); // Internally all names are lower case
415
416 String oldName = this.name;
417 this.name = name;
418 support.firePropertyChange("name", oldName, this.name);
419
420 }
421
422
423 /**
424 * Unpack WARs flag accessor.
425 */
426 public boolean isUnpackWARs() {
427
428 return (unpackWARs);
429
430 }
431
432
433 /**
434 * Unpack WARs flag mutator.
435 */
436 public void setUnpackWARs(boolean unpackWARs) {
437
438 this.unpackWARs = unpackWARs;
439
440 }
441
442 /**
443 * Set the validation feature of the XML parser used when
444 * parsing xml instances.
445 * @param xmlValidation true to enable xml instance validation
446 */
447 public void setXmlValidation(boolean xmlValidation){
448
449 this.xmlValidation = xmlValidation;
450
451 }
452
453 /**
454 * Get the server.xml <host> attribute's xmlValidation.
455 * @return true if validation is enabled.
456 *
457 */
458 public boolean getXmlValidation(){
459 return xmlValidation;
460 }
461
462 /**
463 * Get the server.xml <host> attribute's xmlNamespaceAware.
464 * @return true if namespace awarenes is enabled.
465 *
466 */
467 public boolean getXmlNamespaceAware(){
468 return xmlNamespaceAware;
469 }
470
471
472 /**
473 * Set the namespace aware feature of the XML parser used when
474 * parsing xml instances.
475 * @param xmlNamespaceAware true to enable namespace awareness
476 */
477 public void setXmlNamespaceAware(boolean xmlNamespaceAware){
478 this.xmlNamespaceAware=xmlNamespaceAware;
479 }
480
481 /**
482 * Host work directory base.
483 */
484 public String getWorkDir() {
485
486 return (workDir);
487 }
488
489
490 /**
491 * Host work directory base.
492 */
493 public void setWorkDir(String workDir) {
494
495 this.workDir = workDir;
496 }
497
498
499 // --------------------------------------------------------- Public Methods
500
501
502 /**
503 * Add an alias name that should be mapped to this same Host.
504 *
505 * @param alias The alias to be added
506 */
507 public void addAlias(String alias) {
508
509 alias = alias.toLowerCase();
510
511 synchronized (aliasesLock) {
512 // Skip duplicate aliases
513 for (int i = 0; i < aliases.length; i++) {
514 if (aliases[i].equals(alias))
515 return;
516 }
517 // Add this alias to the list
518 String newAliases[] = new String[aliases.length + 1];
519 for (int i = 0; i < aliases.length; i++)
520 newAliases[i] = aliases[i];
521 newAliases[aliases.length] = alias;
522 aliases = newAliases;
523 }
524 // Inform interested listeners
525 fireContainerEvent(ADD_ALIAS_EVENT, alias);
526
527 }
528
529
530 /**
531 * Add a child Container, only if the proposed child is an implementation
532 * of Context.
533 *
534 * @param child Child container to be added
535 */
536 public void addChild(Container child) {
537
538 if (child instanceof Lifecycle) {
539 ((Lifecycle) child).addLifecycleListener(
540 new MemoryLeakTrackingListener());
541 }
542
543 if (!(child instanceof Context))
544 throw new IllegalArgumentException
545 (sm.getString("standardHost.notContext"));
546 super.addChild(child);
547
548 }
549
550
551 /**
552 * Used to ensure the regardless of {@link Context} implementation, a record
553 * is kept of the class loader used every time a context starts.
554 */
555 private class MemoryLeakTrackingListener implements LifecycleListener {
556
557 public void lifecycleEvent(LifecycleEvent event) {
558 if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
559 if (event.getSource() instanceof Context) {
560 Context context = ((Context) event.getSource());
561 childClassLoaders.put(context.getLoader().getClassLoader(),
562 context.getServletContext().getContextPath());
563 }
564 }
565 }
566 }
567
568
569 /**
570 * Attempt to identify the contexts that have a class loader memory leak.
571 * This is usually triggered on context reload. Note: This method attempts
572 * to force a full garbage collection. This should be used with extreme
573 * caution on a production system.
574 */
575 public String[] findReloadedContextMemoryLeaks() {
576
577 System.gc();
578
579 List<String> result = new ArrayList<String>();
580
581 for (Map.Entry<ClassLoader, String> entry :
582 childClassLoaders.entrySet()) {
583 ClassLoader cl = entry.getKey();
584 if (cl instanceof WebappClassLoader) {
585 if (!((WebappClassLoader) cl).isStarted()) {
586 result.add(entry.getValue());
587 }
588 }
589 }
590
591 return result.toArray(new String[result.size()]);
592 }
593
594 /**
595 * Return the set of alias names for this Host. If none are defined,
596 * a zero length array is returned.
597 */
598 public String[] findAliases() {
599
600 synchronized (aliasesLock) {
601 return (this.aliases);
602 }
603
604 }
605
606
607 /**
608 * Return descriptive information about this Container implementation and
609 * the corresponding version number, in the format
610 * <code><description>/<version></code>.
611 */
612 public String getInfo() {
613
614 return (info);
615
616 }
617
618
619 /**
620 * Return the Context that would be used to process the specified
621 * host-relative request URI, if any; otherwise return <code>null</code>.
622 *
623 * @param uri Request URI to be mapped
624 */
625 public Context map(String uri) {
626
627 if (log.isDebugEnabled())
628 log.debug("Mapping request URI '" + uri + "'");
629 if (uri == null)
630 return (null);
631
632 // Match on the longest possible context path prefix
633 if (log.isTraceEnabled())
634 log.trace(" Trying the longest context path prefix");
635 Context context = null;
636 String mapuri = uri;
637 while (true) {
638 context = (Context) findChild(mapuri);
639 if (context != null)
640 break;
641 int slash = mapuri.lastIndexOf('/');
642 if (slash < 0)
643 break;
644 mapuri = mapuri.substring(0, slash);
645 }
646
647 // If no Context matches, select the default Context
648 if (context == null) {
649 if (log.isTraceEnabled())
650 log.trace(" Trying the default context");
651 context = (Context) findChild("");
652 }
653
654 // Complain if no Context has been selected
655 if (context == null) {
656 log.error(sm.getString("standardHost.mappingError", uri));
657 return (null);
658 }
659
660 // Return the mapped Context (if any)
661 if (log.isDebugEnabled())
662 log.debug(" Mapped to context '" + context.getPath() + "'");
663 return (context);
664
665 }
666
667
668 /**
669 * Remove the specified alias name from the aliases for this Host.
670 *
671 * @param alias Alias name to be removed
672 */
673 public void removeAlias(String alias) {
674
675 alias = alias.toLowerCase();
676
677 synchronized (aliasesLock) {
678
679 // Make sure this alias is currently present
680 int n = -1;
681 for (int i = 0; i < aliases.length; i++) {
682 if (aliases[i].equals(alias)) {
683 n = i;
684 break;
685 }
686 }
687 if (n < 0)
688 return;
689
690 // Remove the specified alias
691 int j = 0;
692 String results[] = new String[aliases.length - 1];
693 for (int i = 0; i < aliases.length; i++) {
694 if (i != n)
695 results[j++] = aliases[i];
696 }
697 aliases = results;
698
699 }
700
701 // Inform interested listeners
702 fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
703
704 }
705
706
707 /**
708 * Return a String representation of this component.
709 */
710 public String toString() {
711
712 StringBuffer sb = new StringBuffer();
713 if (getParent() != null) {
714 sb.append(getParent().toString());
715 sb.append(".");
716 }
717 sb.append("StandardHost[");
718 sb.append(getName());
719 sb.append("]");
720 return (sb.toString());
721
722 }
723
724
725 /**
726 * Start this host.
727 *
728 * @exception LifecycleException if this component detects a fatal error
729 * that prevents it from being started
730 */
731 public synchronized void start() throws LifecycleException {
732 if( started ) {
733 return;
734 }
735 if( ! initialized )
736 init();
737
738 // Look for a realm - that may have been configured earlier.
739 // If the realm is added after context - it'll set itself.
740 if( realm == null ) {
741 ObjectName realmName=null;
742 try {
743 realmName=new ObjectName( domain + ":type=Realm,host=" + getName());
744 if( mserver.isRegistered(realmName ) ) {
745 mserver.invoke(realmName, "init",
746 new Object[] {},
747 new String[] {}
748 );
749 }
750 } catch( Throwable t ) {
751 log.debug("No realm for this host " + realmName);
752 }
753 }
754
755 // Set error report valve
756 if ((errorReportValveClass != null)
757 && (!errorReportValveClass.equals(""))) {
758 try {
759 boolean found = false;
760 if(errorReportValveObjectName != null) {
761 ObjectName[] names =
762 ((StandardPipeline)pipeline).getValveObjectNames();
763 for (int i=0; !found && i<names.length; i++)
764 if(errorReportValveObjectName.equals(names[i]))
765 found = true ;
766 }
767 if(!found) {
768 Valve valve = (Valve) Class.forName(errorReportValveClass)
769 .newInstance();
770 addValve(valve);
771 errorReportValveObjectName = ((ValveBase)valve).getObjectName() ;
772 }
773 } catch (Throwable t) {
774 log.error(sm.getString
775 ("standardHost.invalidErrorReportValveClass",
776 errorReportValveClass), t);
777 }
778 }
779 if(log.isDebugEnabled()) {
780 if (xmlValidation)
781 log.debug(sm.getString("standardHost.validationEnabled"));
782 else
783 log.debug(sm.getString("standardHost.validationDisabled"));
784 }
785 super.start();
786
787 }
788
789
790 // -------------------- JMX --------------------
791 /**
792 * Return the MBean Names of the Valves assoicated with this Host
793 *
794 * @exception Exception if an MBean cannot be created or registered
795 */
796 public String [] getValveNames()
797 throws Exception
798 {
799 Valve [] valves = this.getValves();
800 String [] mbeanNames = new String[valves.length];
801 for (int i = 0; i < valves.length; i++) {
802 if( valves[i] == null ) continue;
803 if( ((ValveBase)valves[i]).getObjectName() == null ) continue;
804 mbeanNames[i] = ((ValveBase)valves[i]).getObjectName().toString();
805 }
806
807 return mbeanNames;
808
809 }
810
811 public String[] getAliases() {
812 synchronized (aliasesLock) {
813 return aliases;
814 }
815 }
816
817 private boolean initialized=false;
818
819 public void init() {
820 if( initialized ) return;
821 initialized=true;
822
823 // already registered.
824 if( getParent() == null ) {
825 try {
826 // Register with the Engine
827 ObjectName serviceName=new ObjectName(domain +
828 ":type=Engine");
829
830 HostConfig deployer = new HostConfig();
831 addLifecycleListener(deployer);
832 if( mserver.isRegistered( serviceName )) {
833 if(log.isDebugEnabled())
834 log.debug("Registering "+ serviceName +" with the Engine");
835 mserver.invoke( serviceName, "addChild",
836 new Object[] { this },
837 new String[] { "org.apache.catalina.Container" } );
838 }
839 } catch( Exception ex ) {
840 log.error("Host registering failed!",ex);
841 }
842 }
843
844 if( oname==null ) {
845 // not registered in JMX yet - standalone mode
846 try {
847 StandardEngine engine=(StandardEngine)parent;
848 domain=engine.getName();
849 if(log.isDebugEnabled())
850 log.debug( "Register host " + getName() + " with domain "+ domain );
851 oname=new ObjectName(domain + ":type=Host,host=" +
852 this.getName());
853 controller = oname;
854 Registry.getRegistry(null, null)
855 .registerComponent(this, oname, null);
856 } catch( Throwable t ) {
857 log.error("Host registering failed!", t );
858 }
859 }
860 }
861
862 public void destroy() throws Exception {
863 // destroy our child containers, if any
864 Container children[] = findChildren();
865 super.destroy();
866 for (int i = 0; i < children.length; i++) {
867 if(children[i] instanceof StandardContext)
868 ((StandardContext)children[i]).destroy();
869 }
870
871 }
872
873 public ObjectName preRegister(MBeanServer server, ObjectName oname )
874 throws Exception
875 {
876 ObjectName res=super.preRegister(server, oname);
877 String name=oname.getKeyProperty("host");
878 if( name != null )
879 setName( name );
880 return res;
881 }
882
883 public ObjectName createObjectName(String domain, ObjectName parent)
884 throws Exception
885 {
886 if( log.isDebugEnabled())
887 log.debug("Create ObjectName " + domain + " " + parent );
888 return new ObjectName( domain + ":type=Host,host=" + getName());
889 }
890
891 }