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.startup;
20
21
22 import java.io.BufferedOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.jar.JarEntry;
31 import java.util.jar.JarFile;
32
33 import javax.management.ObjectName;
34
35 import org.apache.catalina.Container;
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Engine;
38 import org.apache.catalina.Host;
39 import org.apache.catalina.Lifecycle;
40 import org.apache.catalina.LifecycleEvent;
41 import org.apache.catalina.LifecycleListener;
42 import org.apache.catalina.core.ContainerBase;
43 import org.apache.catalina.core.StandardHost;
44 import org.apache.catalina.util.StringManager;
45 import org.apache.tomcat.util.digester.Digester;
46 import org.apache.tomcat.util.modeler.Registry;
47
48
49 /**
50 * Startup event listener for a <b>Host</b> that configures the properties
51 * of that Host, and the associated defined contexts.
52 *
53 * @author Craig R. McClanahan
54 * @author Remy Maucherat
55 * @version $Revision: 613796 $ $Date: 2008-01-21 09:41:31 +0100 (lun., 21 janv. 2008) $
56 */
57 public class HostConfig
58 implements LifecycleListener {
59
60 protected static org.apache.juli.logging.Log log=
61 org.apache.juli.logging.LogFactory.getLog( HostConfig.class );
62
63 // ----------------------------------------------------- Instance Variables
64
65
66 /**
67 * App base.
68 */
69 protected File appBase = null;
70
71
72 /**
73 * Config base.
74 */
75 protected File configBase = null;
76
77
78 /**
79 * The Java class name of the Context configuration class we should use.
80 */
81 protected String configClass = "org.apache.catalina.startup.ContextConfig";
82
83
84 /**
85 * The Java class name of the Context implementation we should use.
86 */
87 protected String contextClass = "org.apache.catalina.core.StandardContext";
88
89
90 /**
91 * The Host we are associated with.
92 */
93 protected Host host = null;
94
95
96 /**
97 * The JMX ObjectName of this component.
98 */
99 protected ObjectName oname = null;
100
101
102 /**
103 * The string resources for this package.
104 */
105 protected static final StringManager sm =
106 StringManager.getManager(Constants.Package);
107
108
109 /**
110 * Should we deploy XML Context config files?
111 */
112 protected boolean deployXML = false;
113
114
115 /**
116 * Should we unpack WAR files when auto-deploying applications in the
117 * <code>appBase</code> directory?
118 */
119 protected boolean unpackWARs = false;
120
121
122 /**
123 * Map of deployed applications.
124 */
125 protected HashMap deployed = new HashMap();
126
127
128 /**
129 * List of applications which are being serviced, and shouldn't be
130 * deployed/undeployed/redeployed at the moment.
131 */
132 protected ArrayList serviced = new ArrayList();
133
134
135 /**
136 * Attribute value used to turn on/off XML validation
137 */
138 protected boolean xmlValidation = false;
139
140
141 /**
142 * Attribute value used to turn on/off XML namespace awarenes.
143 */
144 protected boolean xmlNamespaceAware = false;
145
146
147 /**
148 * The <code>Digester</code> instance used to parse context descriptors.
149 */
150 protected static Digester digester = createDigester();
151
152
153 // ------------------------------------------------------------- Properties
154
155
156 /**
157 * Return the Context configuration class name.
158 */
159 public String getConfigClass() {
160
161 return (this.configClass);
162
163 }
164
165
166 /**
167 * Set the Context configuration class name.
168 *
169 * @param configClass The new Context configuration class name.
170 */
171 public void setConfigClass(String configClass) {
172
173 this.configClass = configClass;
174
175 }
176
177
178 /**
179 * Return the Context implementation class name.
180 */
181 public String getContextClass() {
182
183 return (this.contextClass);
184
185 }
186
187
188 /**
189 * Set the Context implementation class name.
190 *
191 * @param contextClass The new Context implementation class name.
192 */
193 public void setContextClass(String contextClass) {
194
195 this.contextClass = contextClass;
196
197 }
198
199
200 /**
201 * Return the deploy XML config file flag for this component.
202 */
203 public boolean isDeployXML() {
204
205 return (this.deployXML);
206
207 }
208
209
210 /**
211 * Set the deploy XML config file flag for this component.
212 *
213 * @param deployXML The new deploy XML flag
214 */
215 public void setDeployXML(boolean deployXML) {
216
217 this.deployXML= deployXML;
218
219 }
220
221
222 /**
223 * Return the unpack WARs flag.
224 */
225 public boolean isUnpackWARs() {
226
227 return (this.unpackWARs);
228
229 }
230
231
232 /**
233 * Set the unpack WARs flag.
234 *
235 * @param unpackWARs The new unpack WARs flag
236 */
237 public void setUnpackWARs(boolean unpackWARs) {
238
239 this.unpackWARs = unpackWARs;
240
241 }
242
243
244 /**
245 * Set the validation feature of the XML parser used when
246 * parsing xml instances.
247 * @param xmlValidation true to enable xml instance validation
248 */
249 public void setXmlValidation(boolean xmlValidation){
250 this.xmlValidation = xmlValidation;
251 }
252
253 /**
254 * Get the server.xml <host> attribute's xmlValidation.
255 * @return true if validation is enabled.
256 *
257 */
258 public boolean getXmlValidation(){
259 return xmlValidation;
260 }
261
262 /**
263 * Get the server.xml <host> attribute's xmlNamespaceAware.
264 * @return true if namespace awarenes is enabled.
265 *
266 */
267 public boolean getXmlNamespaceAware(){
268 return xmlNamespaceAware;
269 }
270
271
272 /**
273 * Set the namespace aware feature of the XML parser used when
274 * parsing xml instances.
275 * @param xmlNamespaceAware true to enable namespace awareness
276 */
277 public void setXmlNamespaceAware(boolean xmlNamespaceAware){
278 this.xmlNamespaceAware=xmlNamespaceAware;
279 }
280
281
282 // --------------------------------------------------------- Public Methods
283
284
285 /**
286 * Process the START event for an associated Host.
287 *
288 * @param event The lifecycle event that has occurred
289 */
290 public void lifecycleEvent(LifecycleEvent event) {
291
292 if (event.getType().equals(Lifecycle.PERIODIC_EVENT))
293 check();
294
295 // Identify the host we are associated with
296 try {
297 host = (Host) event.getLifecycle();
298 if (host instanceof StandardHost) {
299 setDeployXML(((StandardHost) host).isDeployXML());
300 setUnpackWARs(((StandardHost) host).isUnpackWARs());
301 setXmlNamespaceAware(((StandardHost) host).getXmlNamespaceAware());
302 setXmlValidation(((StandardHost) host).getXmlValidation());
303 }
304 } catch (ClassCastException e) {
305 log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
306 return;
307 }
308
309 // Process the event that has occurred
310 if (event.getType().equals(Lifecycle.START_EVENT))
311 start();
312 else if (event.getType().equals(Lifecycle.STOP_EVENT))
313 stop();
314
315 }
316
317
318 /**
319 * Add a serviced application to the list.
320 */
321 public synchronized void addServiced(String name) {
322 serviced.add(name);
323 }
324
325
326 /**
327 * Is application serviced ?
328 * @return state of the application
329 */
330 public synchronized boolean isServiced(String name) {
331 return (serviced.contains(name));
332 }
333
334
335 /**
336 * Removed a serviced application from the list.
337 */
338 public synchronized void removeServiced(String name) {
339 serviced.remove(name);
340 }
341
342
343 /**
344 * Get the instant where an application was deployed.
345 * @return 0L if no application with that name is deployed, or the instant
346 * on which the application was deployed
347 */
348 public long getDeploymentTime(String name) {
349 DeployedApplication app = (DeployedApplication) deployed.get(name);
350 if (app == null) {
351 return 0L;
352 } else {
353 return app.timestamp;
354 }
355 }
356
357
358 /**
359 * Has the specified application been deployed? Note applications defined
360 * in server.xml will not have been deployed.
361 * @return <code>true</code> if the application has been deployed and
362 * <code>false</code> if the applciation has not been deployed or does not
363 * exist
364 */
365 public boolean isDeployed(String name) {
366 DeployedApplication app = (DeployedApplication) deployed.get(name);
367 if (app == null) {
368 return false;
369 } else {
370 return true;
371 }
372 }
373
374
375 // ------------------------------------------------------ Protected Methods
376
377
378 /**
379 * Create the digester which will be used to parse context config files.
380 */
381 protected static Digester createDigester() {
382 Digester digester = new Digester();
383 digester.setValidating(false);
384 // Add object creation rule
385 digester.addObjectCreate("Context", "org.apache.catalina.core.StandardContext",
386 "className");
387 // Set the properties on that object (it doesn't matter if extra
388 // properties are set)
389 digester.addSetProperties("Context");
390 return (digester);
391 }
392
393
394 /**
395 * Return a File object representing the "application root" directory
396 * for our associated Host.
397 */
398 protected File appBase() {
399
400 if (appBase != null) {
401 return appBase;
402 }
403
404 File file = new File(host.getAppBase());
405 if (!file.isAbsolute())
406 file = new File(System.getProperty("catalina.base"),
407 host.getAppBase());
408 try {
409 appBase = file.getCanonicalFile();
410 } catch (IOException e) {
411 appBase = file;
412 }
413 return (appBase);
414
415 }
416
417
418 /**
419 * Return a File object representing the "configuration root" directory
420 * for our associated Host.
421 */
422 protected File configBase() {
423
424 if (configBase != null) {
425 return configBase;
426 }
427
428 File file = new File(System.getProperty("catalina.base"), "conf");
429 Container parent = host.getParent();
430 if ((parent != null) && (parent instanceof Engine)) {
431 file = new File(file, parent.getName());
432 }
433 file = new File(file, host.getName());
434 try {
435 configBase = file.getCanonicalFile();
436 } catch (IOException e) {
437 configBase = file;
438 }
439 return (configBase);
440
441 }
442
443 /**
444 * Get the name of the configBase.
445 * For use with JMX management.
446 */
447 public String getConfigBaseName() {
448 return configBase().getAbsolutePath();
449 }
450
451 /**
452 * Given a context path, get the config file name.
453 */
454 protected String getConfigFile(String path) {
455 String basename = null;
456 if (path.equals("")) {
457 basename = "ROOT";
458 } else {
459 basename = path.substring(1).replace('/', '#');
460 }
461 return (basename);
462 }
463
464
465 /**
466 * Given a context path, get the config file name.
467 */
468 protected String getDocBase(String path) {
469 String basename = null;
470 if (path.equals("")) {
471 basename = "ROOT";
472 } else {
473 basename = path.substring(1);
474 }
475 return (basename);
476 }
477
478
479 /**
480 * Deploy applications for any directories or WAR files that are found
481 * in our "application root" directory.
482 */
483 protected void deployApps() {
484
485 File appBase = appBase();
486 File configBase = configBase();
487 // Deploy XML descriptors from configBase
488 deployDescriptors(configBase, configBase.list());
489 // Deploy WARs, and loop if additional descriptors are found
490 deployWARs(appBase, appBase.list());
491 // Deploy expanded folders
492 deployDirectories(appBase, appBase.list());
493
494 }
495
496
497 /**
498 * Deploy applications for any directories or WAR files that are found
499 * in our "application root" directory.
500 */
501 protected void deployApps(String name) {
502
503 File appBase = appBase();
504 File configBase = configBase();
505 String baseName = getConfigFile(name);
506 String docBase = getConfigFile(name);
507
508 // Deploy XML descriptors from configBase
509 File xml = new File(configBase, baseName + ".xml");
510 if (xml.exists())
511 deployDescriptor(name, xml, baseName + ".xml");
512 // Deploy WARs, and loop if additional descriptors are found
513 File war = new File(appBase, docBase + ".war");
514 if (war.exists())
515 deployWAR(name, war, docBase + ".war");
516 // Deploy expanded folders
517 File dir = new File(appBase, docBase);
518 if (dir.exists())
519 deployDirectory(name, dir, docBase);
520
521 }
522
523
524 /**
525 * Deploy XML context descriptors.
526 */
527 protected void deployDescriptors(File configBase, String[] files) {
528
529 if (files == null)
530 return;
531
532 for (int i = 0; i < files.length; i++) {
533
534 if (files[i].equalsIgnoreCase("META-INF"))
535 continue;
536 if (files[i].equalsIgnoreCase("WEB-INF"))
537 continue;
538 File contextXml = new File(configBase, files[i]);
539 if (files[i].toLowerCase().endsWith(".xml")) {
540
541 // Calculate the context path and make sure it is unique
542 String nameTmp = files[i].substring(0, files[i].length() - 4);
543 String contextPath = "/" + nameTmp.replace('#', '/');
544 if (nameTmp.equals("ROOT")) {
545 contextPath = "";
546 }
547
548 if (isServiced(contextPath))
549 continue;
550
551 String file = files[i];
552
553 deployDescriptor(contextPath, contextXml, file);
554
555 }
556
557 }
558
559 }
560
561
562 /**
563 * @param contextPath
564 * @param contextXml
565 * @param file
566 */
567 protected void deployDescriptor(String contextPath, File contextXml, String file) {
568 if (deploymentExists(contextPath)) {
569 return;
570 }
571
572 DeployedApplication deployedApp = new DeployedApplication(contextPath);
573
574 // Assume this is a configuration descriptor and deploy it
575 if(log.isDebugEnabled()) {
576 log.debug(sm.getString("hostConfig.deployDescriptor", file));
577 }
578
579 Context context = null;
580 try {
581 synchronized (digester) {
582 try {
583 context = (Context) digester.parse(contextXml);
584 if (context == null) {
585 log.error(sm.getString("hostConfig.deployDescriptor.error",
586 file));
587 return;
588 }
589 } finally {
590 digester.reset();
591 }
592 }
593 if (context instanceof Lifecycle) {
594 Class clazz = Class.forName(host.getConfigClass());
595 LifecycleListener listener =
596 (LifecycleListener) clazz.newInstance();
597 ((Lifecycle) context).addLifecycleListener(listener);
598 }
599 context.setConfigFile(contextXml.getAbsolutePath());
600 context.setPath(contextPath);
601 // Add the associated docBase to the redeployed list if it's a WAR
602 boolean isWar = false;
603 boolean isExternal = false;
604 if (context.getDocBase() != null) {
605 File docBase = new File(context.getDocBase());
606 if (!docBase.isAbsolute()) {
607 docBase = new File(appBase(), context.getDocBase());
608 }
609 // If external docBase, register .xml as redeploy first
610 if (!docBase.getCanonicalPath().startsWith(appBase().getAbsolutePath())) {
611 isExternal = true;
612 deployedApp.redeployResources.put
613 (contextXml.getAbsolutePath(), new Long(contextXml.lastModified()));
614 deployedApp.redeployResources.put(docBase.getAbsolutePath(),
615 new Long(docBase.lastModified()));
616 if (docBase.getAbsolutePath().toLowerCase().endsWith(".war")) {
617 isWar = true;
618 }
619 } else {
620 log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
621 docBase));
622 // Ignore specified docBase
623 context.setDocBase(null);
624 }
625 }
626 host.addChild(context);
627 // Get paths for WAR and expanded WAR in appBase
628 String name = null;
629 String path = context.getPath();
630 if (path.equals("")) {
631 name = "ROOT";
632 } else {
633 if (path.startsWith("/")) {
634 name = path.substring(1);
635 } else {
636 name = path;
637 }
638 }
639 File expandedDocBase = new File(appBase(), name);
640 if (context.getDocBase() != null) {
641 // first assume docBase is absolute
642 expandedDocBase = new File(context.getDocBase());
643 if (!expandedDocBase.isAbsolute()) {
644 // if docBase specified and relative, it must be relative to appBase
645 expandedDocBase = new File(appBase(), context.getDocBase());
646 }
647 }
648 // Add the eventual unpacked WAR and all the resources which will be
649 // watched inside it
650 if (isWar && unpackWARs) {
651 deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
652 new Long(expandedDocBase.lastModified()));
653 deployedApp.redeployResources.put
654 (contextXml.getAbsolutePath(), new Long(contextXml.lastModified()));
655 addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
656 } else {
657 // Find an existing matching war and expanded folder
658 File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war");
659 if (warDocBase.exists()) {
660 deployedApp.redeployResources.put(warDocBase.getAbsolutePath(),
661 new Long(warDocBase.lastModified()));
662 }
663 if (expandedDocBase.exists()) {
664 deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
665 new Long(expandedDocBase.lastModified()));
666 addWatchedResources(deployedApp,
667 expandedDocBase.getAbsolutePath(), context);
668 } else {
669 addWatchedResources(deployedApp, null, context);
670 }
671 // Add the context XML to the list of files which should trigger a redeployment
672 if (!isExternal) {
673 deployedApp.redeployResources.put
674 (contextXml.getAbsolutePath(), new Long(contextXml.lastModified()));
675 }
676 }
677 } catch (Throwable t) {
678 log.error(sm.getString("hostConfig.deployDescriptor.error",
679 file), t);
680 }
681
682 if (context != null && host.findChild(context.getName()) != null) {
683 deployed.put(contextPath, deployedApp);
684 }
685 }
686
687
688 /**
689 * Deploy WAR files.
690 */
691 protected void deployWARs(File appBase, String[] files) {
692
693 if (files == null)
694 return;
695
696 for (int i = 0; i < files.length; i++) {
697
698 if (files[i].equalsIgnoreCase("META-INF"))
699 continue;
700 if (files[i].equalsIgnoreCase("WEB-INF"))
701 continue;
702 File dir = new File(appBase, files[i]);
703 if (files[i].toLowerCase().endsWith(".war")) {
704
705 // Calculate the context path and make sure it is unique
706 String contextPath = "/" + files[i];
707 int period = contextPath.lastIndexOf(".");
708 if (period >= 0)
709 contextPath = contextPath.substring(0, period);
710 if (contextPath.equals("/ROOT"))
711 contextPath = "";
712
713 if (isServiced(contextPath))
714 continue;
715
716 String file = files[i];
717
718 deployWAR(contextPath, dir, file);
719
720 }
721
722 }
723
724 }
725
726
727 /**
728 * @param contextPath
729 * @param dir
730 * @param file
731 */
732 protected void deployWAR(String contextPath, File dir, String file) {
733
734 if (deploymentExists(contextPath))
735 return;
736
737 // Checking for a nested /META-INF/context.xml
738 JarFile jar = null;
739 JarEntry entry = null;
740 InputStream istream = null;
741 BufferedOutputStream ostream = null;
742 File xml = new File
743 (configBase, file.substring(0, file.lastIndexOf(".")) + ".xml");
744 if (deployXML && !xml.exists()) {
745 try {
746 jar = new JarFile(dir);
747 entry = jar.getJarEntry(Constants.ApplicationContextXml);
748 if (entry != null) {
749 istream = jar.getInputStream(entry);
750
751 configBase.mkdirs();
752
753 ostream =
754 new BufferedOutputStream
755 (new FileOutputStream(xml), 1024);
756 byte buffer[] = new byte[1024];
757 while (true) {
758 int n = istream.read(buffer);
759 if (n < 0) {
760 break;
761 }
762 ostream.write(buffer, 0, n);
763 }
764 ostream.flush();
765 ostream.close();
766 ostream = null;
767 istream.close();
768 istream = null;
769 entry = null;
770 jar.close();
771 jar = null;
772 }
773 } catch (Exception e) {
774 // Ignore and continue
775 if (ostream != null) {
776 try {
777 ostream.close();
778 } catch (Throwable t) {
779 ;
780 }
781 ostream = null;
782 }
783 if (istream != null) {
784 try {
785 istream.close();
786 } catch (Throwable t) {
787 ;
788 }
789 istream = null;
790 }
791 } finally {
792 entry = null;
793 if (jar != null) {
794 try {
795 jar.close();
796 } catch (Throwable t) {
797 ;
798 }
799 jar = null;
800 }
801 }
802 }
803
804 DeployedApplication deployedApp = new DeployedApplication(contextPath);
805
806 // Deploy the application in this WAR file
807 if(log.isInfoEnabled())
808 log.info(sm.getString("hostConfig.deployJar", file));
809
810 // Populate redeploy resources with the WAR file
811 deployedApp.redeployResources.put
812 (dir.getAbsolutePath(), new Long(dir.lastModified()));
813
814 try {
815 Context context = (Context) Class.forName(contextClass).newInstance();
816 if (context instanceof Lifecycle) {
817 Class clazz = Class.forName(host.getConfigClass());
818 LifecycleListener listener =
819 (LifecycleListener) clazz.newInstance();
820 ((Lifecycle) context).addLifecycleListener(listener);
821 }
822 context.setPath(contextPath);
823 context.setDocBase(file);
824 if (xml.exists()) {
825 context.setConfigFile(xml.getAbsolutePath());
826 deployedApp.redeployResources.put
827 (xml.getAbsolutePath(), new Long(xml.lastModified()));
828 }
829 host.addChild(context);
830 // If we're unpacking WARs, the docBase will be mutated after
831 // starting the context
832 if (unpackWARs && (context.getDocBase() != null)) {
833 String name = null;
834 String path = context.getPath();
835 if (path.equals("")) {
836 name = "ROOT";
837 } else {
838 if (path.startsWith("/")) {
839 name = path.substring(1);
840 } else {
841 name = path;
842 }
843 }
844 File docBase = new File(name);
845 if (!docBase.isAbsolute()) {
846 docBase = new File(appBase(), name);
847 }
848 deployedApp.redeployResources.put(docBase.getAbsolutePath(),
849 new Long(docBase.lastModified()));
850 addWatchedResources(deployedApp, docBase.getAbsolutePath(), context);
851 } else {
852 addWatchedResources(deployedApp, null, context);
853 }
854 } catch (Throwable t) {
855 log.error(sm.getString("hostConfig.deployJar.error", file), t);
856 }
857
858 deployed.put(contextPath, deployedApp);
859 }
860
861
862 /**
863 * Deploy directories.
864 */
865 protected void deployDirectories(File appBase, String[] files) {
866
867 if (files == null)
868 return;
869
870 for (int i = 0; i < files.length; i++) {
871
872 if (files[i].equalsIgnoreCase("META-INF"))
873 continue;
874 if (files[i].equalsIgnoreCase("WEB-INF"))
875 continue;
876 File dir = new File(appBase, files[i]);
877 if (dir.isDirectory()) {
878
879 // Calculate the context path and make sure it is unique
880 String contextPath = "/" + files[i];
881 if (files[i].equals("ROOT"))
882 contextPath = "";
883
884 if (isServiced(contextPath))
885 continue;
886
887 deployDirectory(contextPath, dir, files[i]);
888
889 }
890
891 }
892
893 }
894
895
896 /**
897 * @param contextPath
898 * @param dir
899 * @param file
900 */
901 protected void deployDirectory(String contextPath, File dir, String file) {
902 DeployedApplication deployedApp = new DeployedApplication(contextPath);
903
904 if (deploymentExists(contextPath))
905 return;
906
907 // Deploy the application in this directory
908 if( log.isDebugEnabled() )
909 log.debug(sm.getString("hostConfig.deployDir", file));
910 try {
911 Context context = (Context) Class.forName(contextClass).newInstance();
912 if (context instanceof Lifecycle) {
913 Class clazz = Class.forName(host.getConfigClass());
914 LifecycleListener listener =
915 (LifecycleListener) clazz.newInstance();
916 ((Lifecycle) context).addLifecycleListener(listener);
917 }
918 context.setPath(contextPath);
919 context.setDocBase(file);
920 File configFile = new File(dir, Constants.ApplicationContextXml);
921 if (deployXML) {
922 context.setConfigFile(configFile.getAbsolutePath());
923 }
924 host.addChild(context);
925 deployedApp.redeployResources.put(dir.getAbsolutePath(),
926 new Long(dir.lastModified()));
927 if (deployXML) {
928 deployedApp.redeployResources.put(configFile.getAbsolutePath(),
929 new Long(configFile.lastModified()));
930 }
931 addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
932 } catch (Throwable t) {
933 log.error(sm.getString("hostConfig.deployDir.error", file), t);
934 }
935
936 deployed.put(contextPath, deployedApp);
937 }
938
939
940 /**
941 * Check if a webapp is already deployed in this host.
942 *
943 * @param contextPath of the context which will be checked
944 */
945 protected boolean deploymentExists(String contextPath) {
946 return (deployed.containsKey(contextPath) || (host.findChild(contextPath) != null));
947 }
948
949
950 /**
951 * Add watched resources to the specified Context.
952 * @param app HostConfig deployed app
953 * @param docBase web app docBase
954 * @param context web application context
955 */
956 protected void addWatchedResources(DeployedApplication app, String docBase, Context context) {
957 // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*, WEB-INF/*.xml), where
958 // we would only check if at least one resource is newer than app.timestamp
959 File docBaseFile = null;
960 if (docBase != null) {
961 docBaseFile = new File(docBase);
962 if (!docBaseFile.isAbsolute()) {
963 docBaseFile = new File(appBase(), docBase);
964 }
965 }
966 String[] watchedResources = context.findWatchedResources();
967 for (int i = 0; i < watchedResources.length; i++) {
968 File resource = new File(watchedResources[i]);
969 if (!resource.isAbsolute()) {
970 if (docBase != null) {
971 resource = new File(docBaseFile, watchedResources[i]);
972 } else {
973 if(log.isDebugEnabled())
974 log.debug("Ignoring non-existent WatchedResource '"
975 + resource.getAbsolutePath() + "'");
976 continue;
977 }
978 }
979 if(log.isDebugEnabled())
980 log.debug("Watching WatchedResource '" + resource.getAbsolutePath() + "'");
981 app.reloadResources.put(resource.getAbsolutePath(),
982 new Long(resource.lastModified()));
983 }
984 }
985
986
987 /**
988 * Check resources for redeployment and reloading.
989 */
990 protected synchronized void checkResources(DeployedApplication app) {
991 String[] resources = (String[]) app.redeployResources.keySet().toArray(new String[0]);
992 for (int i = 0; i < resources.length; i++) {
993 File resource = new File(resources[i]);
994 if (log.isDebugEnabled())
995 log.debug("Checking context[" + app.name + "] redeploy resource " + resource);
996 if (resource.exists()) {
997 long lastModified = ((Long) app.redeployResources.get(resources[i])).longValue();
998 if ((!resource.isDirectory()) && resource.lastModified() > lastModified) {
999 // Undeploy application
1000 if (log.isInfoEnabled())
1001 log.info(sm.getString("hostConfig.undeploy", app.name));
1002 ContainerBase context = (ContainerBase) host.findChild(app.name);
1003 try {
1004 host.removeChild(context);
1005 } catch (Throwable t) {
1006 log.warn(sm.getString
1007 ("hostConfig.context.remove", app.name), t);
1008 }
1009 try {
1010 context.destroy();
1011 } catch (Throwable t) {
1012 log.warn(sm.getString
1013 ("hostConfig.context.destroy", app.name), t);
1014 }
1015 // Delete other redeploy resources
1016 for (int j = i + 1; j < resources.length; j++) {
1017 try {
1018 File current = new File(resources[j]);
1019 current = current.getCanonicalFile();
1020 if ((current.getAbsolutePath().startsWith(appBase().getAbsolutePath()))
1021 || (current.getAbsolutePath().startsWith(configBase().getAbsolutePath()))) {
1022 if (log.isDebugEnabled())
1023 log.debug("Delete " + current);
1024 ExpandWar.delete(current);
1025 }
1026 } catch (IOException e) {
1027 log.warn(sm.getString
1028 ("hostConfig.canonicalizing", app.name), e);
1029 }
1030 }
1031 deployed.remove(app.name);
1032 return;
1033 }
1034 } else {
1035 long lastModified = ((Long) app.redeployResources.get(resources[i])).longValue();
1036 if (lastModified == 0L) {
1037 continue;
1038 }
1039 // Undeploy application
1040 if (log.isInfoEnabled())
1041 log.info(sm.getString("hostConfig.undeploy", app.name));
1042 ContainerBase context = (ContainerBase) host.findChild(app.name);
1043 try {
1044 host.removeChild(context);
1045 } catch (Throwable t) {
1046 log.warn(sm.getString
1047 ("hostConfig.context.remove", app.name), t);
1048 }
1049 try {
1050 context.destroy();
1051 } catch (Throwable t) {
1052 log.warn(sm.getString
1053 ("hostConfig.context.destroy", app.name), t);
1054 }
1055 // Delete all redeploy resources
1056 for (int j = i + 1; j < resources.length; j++) {
1057 try {
1058 File current = new File(resources[j]);
1059 current = current.getCanonicalFile();
1060 if ((current.getAbsolutePath().startsWith(appBase().getAbsolutePath()))
1061 || (current.getAbsolutePath().startsWith(configBase().getAbsolutePath()))) {
1062 if (log.isDebugEnabled())
1063 log.debug("Delete " + current);
1064 ExpandWar.delete(current);
1065 }
1066 } catch (IOException e) {
1067 log.warn(sm.getString
1068 ("hostConfig.canonicalizing", app.name), e);
1069 }
1070 }
1071 // Delete reload resources as well (to remove any remaining .xml descriptor)
1072 String[] resources2 = (String[]) app.reloadResources.keySet().toArray(new String[0]);
1073 for (int j = 0; j < resources2.length; j++) {
1074 try {
1075 File current = new File(resources2[j]);
1076 current = current.getCanonicalFile();
1077 if ((current.getAbsolutePath().startsWith(appBase().getAbsolutePath()))
1078 || ((current.getAbsolutePath().startsWith(configBase().getAbsolutePath())
1079 && (current.getAbsolutePath().endsWith(".xml"))))) {
1080 if (log.isDebugEnabled())
1081 log.debug("Delete " + current);
1082 ExpandWar.delete(current);
1083 }
1084 } catch (IOException e) {
1085 log.warn(sm.getString
1086 ("hostConfig.canonicalizing", app.name), e);
1087 }
1088 }
1089 deployed.remove(app.name);
1090 return;
1091 }
1092 }
1093 resources = (String[]) app.reloadResources.keySet().toArray(new String[0]);
1094 for (int i = 0; i < resources.length; i++) {
1095 File resource = new File(resources[i]);
1096 if (log.isDebugEnabled())
1097 log.debug("Checking context[" + app.name + "] reload resource " + resource);
1098 long lastModified = ((Long) app.reloadResources.get(resources[i])).longValue();
1099 if ((!resource.exists() && lastModified != 0L)
1100 || (resource.lastModified() != lastModified)) {
1101 // Reload application
1102 if(log.isInfoEnabled())
1103 log.info(sm.getString("hostConfig.reload", app.name));
1104 Container context = host.findChild(app.name);
1105 try {
1106 ((Lifecycle) context).stop();
1107 } catch (Exception e) {
1108 log.warn(sm.getString
1109 ("hostConfig.context.restart", app.name), e);
1110 }
1111 // If the context was not started (for example an error
1112 // in web.xml) we'll still get to try to start
1113 try {
1114 ((Lifecycle) context).start();
1115 } catch (Exception e) {
1116 log.warn(sm.getString
1117 ("hostConfig.context.restart", app.name), e);
1118 }
1119 // Update times
1120 app.reloadResources.put(resources[i], new Long(resource.lastModified()));
1121 app.timestamp = System.currentTimeMillis();
1122 return;
1123 }
1124 }
1125 }
1126
1127
1128 /**
1129 * Process a "start" event for this Host.
1130 */
1131 public void start() {
1132
1133 if (log.isDebugEnabled())
1134 log.debug(sm.getString("hostConfig.start"));
1135
1136 try {
1137 ObjectName hostON = new ObjectName(host.getObjectName());
1138 oname = new ObjectName
1139 (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
1140 Registry.getRegistry(null, null).registerComponent
1141 (this, oname, this.getClass().getName());
1142 } catch (Exception e) {
1143 log.error(sm.getString("hostConfig.jmx.register", oname), e);
1144 }
1145
1146 if (host.getDeployOnStartup())
1147 deployApps();
1148
1149 }
1150
1151
1152 /**
1153 * Process a "stop" event for this Host.
1154 */
1155 public void stop() {
1156
1157 if (log.isDebugEnabled())
1158 log.debug(sm.getString("hostConfig.stop"));
1159
1160 undeployApps();
1161
1162 if (oname != null) {
1163 try {
1164 Registry.getRegistry(null, null).unregisterComponent(oname);
1165 } catch (Exception e) {
1166 log.error(sm.getString("hostConfig.jmx.unregister", oname), e);
1167 }
1168 }
1169 oname = null;
1170 appBase = null;
1171 configBase = null;
1172
1173 }
1174
1175
1176 /**
1177 * Undeploy all deployed applications.
1178 */
1179 protected void undeployApps() {
1180
1181 if (log.isDebugEnabled())
1182 log.debug(sm.getString("hostConfig.undeploying"));
1183
1184 // Soft undeploy all contexts we have deployed
1185 DeployedApplication[] apps =
1186 (DeployedApplication[]) deployed.values().toArray(new DeployedApplication[0]);
1187 for (int i = 0; i < apps.length; i++) {
1188 try {
1189 host.removeChild(host.findChild(apps[i].name));
1190 } catch (Throwable t) {
1191 log.warn(sm.getString
1192 ("hostConfig.context.remove", apps[i].name), t);
1193 }
1194 }
1195
1196 deployed.clear();
1197
1198 }
1199
1200
1201 /**
1202 * Check status of all webapps.
1203 */
1204 protected void check() {
1205
1206 if (host.getAutoDeploy()) {
1207 // Check for resources modification to trigger redeployment
1208 DeployedApplication[] apps =
1209 (DeployedApplication[]) deployed.values().toArray(new DeployedApplication[0]);
1210 for (int i = 0; i < apps.length; i++) {
1211 if (!isServiced(apps[i].name))
1212 checkResources(apps[i]);
1213 }
1214 // Hotdeploy applications
1215 deployApps();
1216 }
1217
1218 }
1219
1220
1221 /**
1222 * Check status of a specific webapp, for use with stuff like management webapps.
1223 */
1224 public void check(String name) {
1225 DeployedApplication app = (DeployedApplication) deployed.get(name);
1226 if (app != null) {
1227 checkResources(app);
1228 } else {
1229 deployApps(name);
1230 }
1231 }
1232
1233 /**
1234 * Add a new Context to be managed by us.
1235 * Entry point for the admin webapp, and other JMX Context controlers.
1236 */
1237 public void manageApp(Context context) {
1238
1239 String contextPath = context.getPath();
1240
1241 if (deployed.containsKey(contextPath))
1242 return;
1243
1244 DeployedApplication deployedApp = new DeployedApplication(contextPath);
1245
1246 // Add the associated docBase to the redeployed list if it's a WAR
1247 boolean isWar = false;
1248 if (context.getDocBase() != null) {
1249 File docBase = new File(context.getDocBase());
1250 if (!docBase.isAbsolute()) {
1251 docBase = new File(appBase(), context.getDocBase());
1252 }
1253 deployedApp.redeployResources.put(docBase.getAbsolutePath(),
1254 new Long(docBase.lastModified()));
1255 if (docBase.getAbsolutePath().toLowerCase().endsWith(".war")) {
1256 isWar = true;
1257 }
1258 }
1259 host.addChild(context);
1260 // Add the eventual unpacked WAR and all the resources which will be
1261 // watched inside it
1262 if (isWar && unpackWARs) {
1263 String name = null;
1264 String path = context.getPath();
1265 if (path.equals("")) {
1266 name = "ROOT";
1267 } else {
1268 if (path.startsWith("/")) {
1269 name = path.substring(1);
1270 } else {
1271 name = path;
1272 }
1273 }
1274 File docBase = new File(name);
1275 if (!docBase.isAbsolute()) {
1276 docBase = new File(appBase(), name);
1277 }
1278 deployedApp.redeployResources.put(docBase.getAbsolutePath(),
1279 new Long(docBase.lastModified()));
1280 addWatchedResources(deployedApp, docBase.getAbsolutePath(), context);
1281 } else {
1282 addWatchedResources(deployedApp, null, context);
1283 }
1284 deployed.put(contextPath, deployedApp);
1285 }
1286
1287 /**
1288 * Remove a webapp from our control.
1289 * Entry point for the admin webapp, and other JMX Context controlers.
1290 */
1291 public void unmanageApp(String contextPath) {
1292 if(isServiced(contextPath)) {
1293 deployed.remove(contextPath);
1294 host.removeChild(host.findChild(contextPath));
1295 }
1296 }
1297
1298 // ----------------------------------------------------- Instance Variables
1299
1300
1301 /**
1302 * This class represents the state of a deployed application, as well as
1303 * the monitored resources.
1304 */
1305 protected class DeployedApplication {
1306 public DeployedApplication(String name) {
1307 this.name = name;
1308 }
1309
1310 /**
1311 * Application context path. The assertion is that
1312 * (host.getChild(name) != null).
1313 */
1314 public String name;
1315
1316 /**
1317 * Any modification of the specified (static) resources will cause a
1318 * redeployment of the application. If any of the specified resources is
1319 * removed, the application will be undeployed. Typically, this will
1320 * contain resources like the context.xml file, a compressed WAR path.
1321 * The value is the last modification time.
1322 */
1323 public LinkedHashMap redeployResources = new LinkedHashMap();
1324
1325 /**
1326 * Any modification of the specified (static) resources will cause a
1327 * reload of the application. This will typically contain resources
1328 * such as the web.xml of a webapp, but can be configured to contain
1329 * additional descriptors.
1330 * The value is the last modification time.
1331 */
1332 public HashMap reloadResources = new HashMap();
1333
1334 /**
1335 * Instant where the application was last put in service.
1336 */
1337 public long timestamp = System.currentTimeMillis();
1338 }
1339
1340 }