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