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.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.InetAddress;
27 import java.net.ServerSocket;
28 import java.net.Socket;
29 import java.security.AccessControlException;
30 import java.util.Random;
31
32 import javax.management.MBeanRegistration;
33 import javax.management.MBeanServer;
34 import javax.management.ObjectName;
35
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Lifecycle;
38 import org.apache.catalina.LifecycleException;
39 import org.apache.catalina.LifecycleListener;
40 import org.apache.catalina.Server;
41 import org.apache.catalina.ServerFactory;
42 import org.apache.catalina.Service;
43 import org.apache.catalina.deploy.NamingResources;
44 import org.apache.catalina.util.LifecycleSupport;
45 import org.apache.catalina.util.StringManager;
46 import org.apache.catalina.util.ServerInfo;
47 import org.apache.juli.logging.Log;
48 import org.apache.juli.logging.LogFactory;
49 import org.apache.tomcat.util.buf.StringCache;
50 import org.apache.tomcat.util.modeler.Registry;
51
52
53
54 /**
55 * Standard implementation of the <b>Server</b> interface, available for use
56 * (but not required) when deploying and starting Catalina.
57 *
58 * @author Craig R. McClanahan
59 * @version $Revision: 762911 $ $Date: 2009-04-07 21:15:16 +0200 (Tue, 07 Apr 2009) $
60 */
61 public final class StandardServer
62 implements Lifecycle, Server, MBeanRegistration
63 {
64 private static Log log = LogFactory.getLog(StandardServer.class);
65
66
67 // -------------------------------------------------------------- Constants
68
69
70 /**
71 * ServerLifecycleListener classname.
72 */
73 private static String SERVER_LISTENER_CLASS_NAME =
74 "org.apache.catalina.mbeans.ServerLifecycleListener";
75
76
77 // ------------------------------------------------------------ Constructor
78
79
80 /**
81 * Construct a default instance of this class.
82 */
83 public StandardServer() {
84
85 super();
86 ServerFactory.setServer(this);
87
88 globalNamingResources = new NamingResources();
89 globalNamingResources.setContainer(this);
90
91 if (isUseNaming()) {
92 if (namingContextListener == null) {
93 namingContextListener = new NamingContextListener();
94 addLifecycleListener(namingContextListener);
95 }
96 }
97
98 }
99
100
101 // ----------------------------------------------------- Instance Variables
102
103
104 /**
105 * Global naming resources context.
106 */
107 private javax.naming.Context globalNamingContext = null;
108
109
110 /**
111 * Global naming resources.
112 */
113 private NamingResources globalNamingResources = null;
114
115
116 /**
117 * Descriptive information about this Server implementation.
118 */
119 private static final String info =
120 "org.apache.catalina.core.StandardServer/1.0";
121
122
123 /**
124 * The lifecycle event support for this component.
125 */
126 private LifecycleSupport lifecycle = new LifecycleSupport(this);
127
128
129 /**
130 * The naming context listener for this web application.
131 */
132 private NamingContextListener namingContextListener = null;
133
134
135 /**
136 * The port number on which we wait for shutdown commands.
137 */
138 private int port = 8005;
139
140
141 /**
142 * A random number generator that is <strong>only</strong> used if
143 * the shutdown command string is longer than 1024 characters.
144 */
145 private Random random = null;
146
147
148 /**
149 * The set of Services associated with this Server.
150 */
151 private Service services[] = new Service[0];
152
153
154 /**
155 * The shutdown command string we are looking for.
156 */
157 private String shutdown = "SHUTDOWN";
158
159
160 /**
161 * The string manager for this package.
162 */
163 private static final StringManager sm =
164 StringManager.getManager(Constants.Package);
165
166
167 /**
168 * Has this component been started?
169 */
170 private boolean started = false;
171
172
173 /**
174 * Has this component been initialized?
175 */
176 private boolean initialized = false;
177
178
179 /**
180 * The property change support for this component.
181 */
182 protected PropertyChangeSupport support = new PropertyChangeSupport(this);
183
184 private boolean stopAwait = false;
185
186 // ------------------------------------------------------------- Properties
187
188
189 /**
190 * Return the global naming resources context.
191 */
192 public javax.naming.Context getGlobalNamingContext() {
193
194 return (this.globalNamingContext);
195
196 }
197
198
199 /**
200 * Set the global naming resources context.
201 *
202 * @param globalNamingContext The new global naming resource context
203 */
204 public void setGlobalNamingContext
205 (javax.naming.Context globalNamingContext) {
206
207 this.globalNamingContext = globalNamingContext;
208
209 }
210
211
212 /**
213 * Return the global naming resources.
214 */
215 public NamingResources getGlobalNamingResources() {
216
217 return (this.globalNamingResources);
218
219 }
220
221
222 /**
223 * Set the global naming resources.
224 *
225 * @param globalNamingResources The new global naming resources
226 */
227 public void setGlobalNamingResources
228 (NamingResources globalNamingResources) {
229
230 NamingResources oldGlobalNamingResources =
231 this.globalNamingResources;
232 this.globalNamingResources = globalNamingResources;
233 this.globalNamingResources.setContainer(this);
234 support.firePropertyChange("globalNamingResources",
235 oldGlobalNamingResources,
236 this.globalNamingResources);
237
238 }
239
240
241 /**
242 * Return descriptive information about this Server implementation and
243 * the corresponding version number, in the format
244 * <code><description>/<version></code>.
245 */
246 public String getInfo() {
247
248 return (info);
249
250 }
251
252 /**
253 * Report the current Tomcat Server Release number
254 * @return Tomcat release identifier
255 */
256 public String getServerInfo() {
257
258 return ServerInfo.getServerInfo();
259 }
260
261 /**
262 * Return the port number we listen to for shutdown commands.
263 */
264 public int getPort() {
265
266 return (this.port);
267
268 }
269
270
271 /**
272 * Set the port number we listen to for shutdown commands.
273 *
274 * @param port The new port number
275 */
276 public void setPort(int port) {
277
278 this.port = port;
279
280 }
281
282
283 /**
284 * Return the shutdown command string we are waiting for.
285 */
286 public String getShutdown() {
287
288 return (this.shutdown);
289
290 }
291
292
293 /**
294 * Set the shutdown command we are waiting for.
295 *
296 * @param shutdown The new shutdown command
297 */
298 public void setShutdown(String shutdown) {
299
300 this.shutdown = shutdown;
301
302 }
303
304
305 // --------------------------------------------------------- Server Methods
306
307
308 /**
309 * Add a new Service to the set of defined Services.
310 *
311 * @param service The Service to be added
312 */
313 public void addService(Service service) {
314
315 service.setServer(this);
316
317 synchronized (services) {
318 Service results[] = new Service[services.length + 1];
319 System.arraycopy(services, 0, results, 0, services.length);
320 results[services.length] = service;
321 services = results;
322
323 if (initialized) {
324 try {
325 service.initialize();
326 } catch (LifecycleException e) {
327 log.error(e);
328 }
329 }
330
331 if (started && (service instanceof Lifecycle)) {
332 try {
333 ((Lifecycle) service).start();
334 } catch (LifecycleException e) {
335 ;
336 }
337 }
338
339 // Report this property change to interested listeners
340 support.firePropertyChange("service", null, service);
341 }
342
343 }
344
345 public void stopAwait() {
346 stopAwait=true;
347 }
348
349 /**
350 * Wait until a proper shutdown command is received, then return.
351 * This keeps the main thread alive - the thread pool listening for http
352 * connections is daemon threads.
353 */
354 public void await() {
355 // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
356 if( port == -2 ) {
357 // undocumented yet - for embedding apps that are around, alive.
358 return;
359 }
360 if( port==-1 ) {
361 while( true ) {
362 try {
363 Thread.sleep( 10000 );
364 } catch( InterruptedException ex ) {
365 }
366 if( stopAwait ) return;
367 }
368 }
369
370 // Set up a server socket to wait on
371 ServerSocket serverSocket = null;
372 try {
373 serverSocket =
374 new ServerSocket(port, 1,
375 InetAddress.getByName("localhost"));
376 } catch (IOException e) {
377 log.error("StandardServer.await: create[" + port
378 + "]: ", e);
379 System.exit(1);
380 }
381
382 // Loop waiting for a connection and a valid command
383 while (true) {
384
385 // Wait for the next connection
386 Socket socket = null;
387 InputStream stream = null;
388 try {
389 socket = serverSocket.accept();
390 socket.setSoTimeout(10 * 1000); // Ten seconds
391 stream = socket.getInputStream();
392 } catch (AccessControlException ace) {
393 log.warn("StandardServer.accept security exception: "
394 + ace.getMessage(), ace);
395 continue;
396 } catch (IOException e) {
397 log.error("StandardServer.await: accept: ", e);
398 System.exit(1);
399 }
400
401 // Read a set of characters from the socket
402 StringBuffer command = new StringBuffer();
403 int expected = 1024; // Cut off to avoid DoS attack
404 while (expected < shutdown.length()) {
405 if (random == null)
406 random = new Random();
407 expected += (random.nextInt() % 1024);
408 }
409 while (expected > 0) {
410 int ch = -1;
411 try {
412 ch = stream.read();
413 } catch (IOException e) {
414 log.warn("StandardServer.await: read: ", e);
415 ch = -1;
416 }
417 if (ch < 32) // Control character or EOF terminates loop
418 break;
419 command.append((char) ch);
420 expected--;
421 }
422
423 // Close the socket now that we are done with it
424 try {
425 socket.close();
426 } catch (IOException e) {
427 ;
428 }
429
430 // Match against our command string
431 boolean match = command.toString().equals(shutdown);
432 if (match) {
433 break;
434 } else
435 log.warn("StandardServer.await: Invalid command '" +
436 command.toString() + "' received");
437
438 }
439
440 // Close the server socket and return
441 try {
442 serverSocket.close();
443 } catch (IOException e) {
444 ;
445 }
446
447 }
448
449
450 /**
451 * Return the specified Service (if it exists); otherwise return
452 * <code>null</code>.
453 *
454 * @param name Name of the Service to be returned
455 */
456 public Service findService(String name) {
457
458 if (name == null) {
459 return (null);
460 }
461 synchronized (services) {
462 for (int i = 0; i < services.length; i++) {
463 if (name.equals(services[i].getName())) {
464 return (services[i]);
465 }
466 }
467 }
468 return (null);
469
470 }
471
472
473 /**
474 * Return the set of Services defined within this Server.
475 */
476 public Service[] findServices() {
477
478 return (services);
479
480 }
481
482 /**
483 * Return the JMX service names.
484 */
485 public ObjectName[] getServiceNames() {
486 ObjectName onames[]=new ObjectName[ services.length ];
487 for( int i=0; i<services.length; i++ ) {
488 onames[i]=((StandardService)services[i]).getObjectName();
489 }
490 return onames;
491 }
492
493
494 /**
495 * Remove the specified Service from the set associated from this
496 * Server.
497 *
498 * @param service The Service to be removed
499 */
500 public void removeService(Service service) {
501
502 synchronized (services) {
503 int j = -1;
504 for (int i = 0; i < services.length; i++) {
505 if (service == services[i]) {
506 j = i;
507 break;
508 }
509 }
510 if (j < 0)
511 return;
512 if (services[j] instanceof Lifecycle) {
513 try {
514 ((Lifecycle) services[j]).stop();
515 } catch (LifecycleException e) {
516 ;
517 }
518 }
519 int k = 0;
520 Service results[] = new Service[services.length - 1];
521 for (int i = 0; i < services.length; i++) {
522 if (i != j)
523 results[k++] = services[i];
524 }
525 services = results;
526
527 // Report this property change to interested listeners
528 support.firePropertyChange("service", service, null);
529 }
530
531 }
532
533
534 // --------------------------------------------------------- Public Methods
535
536
537 /**
538 * Add a property change listener to this component.
539 *
540 * @param listener The listener to add
541 */
542 public void addPropertyChangeListener(PropertyChangeListener listener) {
543
544 support.addPropertyChangeListener(listener);
545
546 }
547
548
549 /**
550 * Remove a property change listener from this component.
551 *
552 * @param listener The listener to remove
553 */
554 public void removePropertyChangeListener(PropertyChangeListener listener) {
555
556 support.removePropertyChangeListener(listener);
557
558 }
559
560
561 /**
562 * Return a String representation of this component.
563 */
564 public String toString() {
565
566 StringBuffer sb = new StringBuffer("StandardServer[");
567 sb.append(getPort());
568 sb.append("]");
569 return (sb.toString());
570
571 }
572
573
574 /**
575 * Write the configuration information for this entire <code>Server</code>
576 * out to the server.xml configuration file.
577 *
578 * @exception javax.management.InstanceNotFoundException if the managed resource object
579 * cannot be found
580 * @exception javax.management.MBeanException if the initializer of the object throws
581 * an exception, or persistence is not supported
582 * @exception javax.management.RuntimeOperationsException if an exception is reported
583 * by the persistence mechanism
584 */
585 public synchronized void storeConfig() throws Exception {
586
587 ObjectName sname = null;
588 try {
589 sname = new ObjectName("Catalina:type=StoreConfig");
590 if(mserver.isRegistered(sname)) {
591 mserver.invoke(sname, "storeConfig", null, null);
592 } else
593 log.error("StoreConfig mbean not registered" + sname);
594 } catch (Throwable t) {
595 log.error(t);
596 }
597
598 }
599
600
601 /**
602 * Write the configuration information for <code>Context</code>
603 * out to the specified configuration file.
604 *
605 * @exception javax.management.InstanceNotFoundException if the managed resource object
606 * cannot be found
607 * @exception javax.management.MBeanException if the initializer of the object throws
608 * an exception, or persistence is not supported
609 * @exception javax.management.RuntimeOperationsException if an exception is reported
610 * by the persistence mechanism
611 */
612 public synchronized void storeContext(Context context) throws Exception {
613
614 ObjectName sname = null;
615 try {
616 sname = new ObjectName("Catalina:type=StoreConfig");
617 if(mserver.isRegistered(sname)) {
618 mserver.invoke(sname, "store",
619 new Object[] {context},
620 new String [] { "java.lang.String"});
621 } else
622 log.error("StoreConfig mbean not registered" + sname);
623 } catch (Throwable t) {
624 log.error(t);
625 }
626
627 }
628
629
630 /**
631 * Return true if naming should be used.
632 */
633 private boolean isUseNaming() {
634 boolean useNaming = true;
635 // Reading the "catalina.useNaming" environment variable
636 String useNamingProperty = System.getProperty("catalina.useNaming");
637 if ((useNamingProperty != null)
638 && (useNamingProperty.equals("false"))) {
639 useNaming = false;
640 }
641 return useNaming;
642 }
643
644
645 // ------------------------------------------------------ Lifecycle Methods
646
647
648 /**
649 * Add a LifecycleEvent listener to this component.
650 *
651 * @param listener The listener to add
652 */
653 public void addLifecycleListener(LifecycleListener listener) {
654
655 lifecycle.addLifecycleListener(listener);
656
657 }
658
659
660 /**
661 * Get the lifecycle listeners associated with this lifecycle. If this
662 * Lifecycle has no listeners registered, a zero-length array is returned.
663 */
664 public LifecycleListener[] findLifecycleListeners() {
665
666 return lifecycle.findLifecycleListeners();
667
668 }
669
670
671 /**
672 * Remove a LifecycleEvent listener from this component.
673 *
674 * @param listener The listener to remove
675 */
676 public void removeLifecycleListener(LifecycleListener listener) {
677
678 lifecycle.removeLifecycleListener(listener);
679
680 }
681
682
683 /**
684 * Prepare for the beginning of active use of the public methods of this
685 * component. This method should be called before any of the public
686 * methods of this component are utilized. It should also send a
687 * LifecycleEvent of type START_EVENT to any registered listeners.
688 *
689 * @exception LifecycleException if this component detects a fatal error
690 * that prevents this component from being used
691 */
692 public void start() throws LifecycleException {
693
694 // Validate and update our current component state
695 if (started) {
696 log.debug(sm.getString("standardServer.start.started"));
697 return;
698 }
699
700 // Notify our interested LifecycleListeners
701 lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
702
703 lifecycle.fireLifecycleEvent(START_EVENT, null);
704 started = true;
705
706 // Start our defined Services
707 synchronized (services) {
708 for (int i = 0; i < services.length; i++) {
709 if (services[i] instanceof Lifecycle)
710 ((Lifecycle) services[i]).start();
711 }
712 }
713
714 // Notify our interested LifecycleListeners
715 lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
716
717 }
718
719
720 /**
721 * Gracefully terminate the active use of the public methods of this
722 * component. This method should be the last one called on a given
723 * instance of this component. It should also send a LifecycleEvent
724 * of type STOP_EVENT to any registered listeners.
725 *
726 * @exception LifecycleException if this component detects a fatal error
727 * that needs to be reported
728 */
729 public void stop() throws LifecycleException {
730
731 // Validate and update our current component state
732 if (!started)
733 return;
734
735 // Notify our interested LifecycleListeners
736 lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
737
738 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
739 started = false;
740
741 // Stop our defined Services
742 for (int i = 0; i < services.length; i++) {
743 if (services[i] instanceof Lifecycle)
744 ((Lifecycle) services[i]).stop();
745 }
746
747 // Notify our interested LifecycleListeners
748 lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
749
750 if (port == -1)
751 stopAwait();
752
753 }
754
755 public void init() throws Exception {
756 initialize();
757 }
758
759 /**
760 * Invoke a pre-startup initialization. This is used to allow connectors
761 * to bind to restricted ports under Unix operating environments.
762 */
763 public void initialize()
764 throws LifecycleException
765 {
766 if (initialized) {
767 log.info(sm.getString("standardServer.initialize.initialized"));
768 return;
769 }
770 lifecycle.fireLifecycleEvent(INIT_EVENT, null);
771 initialized = true;
772
773 if( oname==null ) {
774 try {
775 oname=new ObjectName( "Catalina:type=Server");
776 Registry.getRegistry(null, null)
777 .registerComponent(this, oname, null );
778 } catch (Exception e) {
779 log.error("Error registering ",e);
780 }
781 }
782
783 // Register global String cache
784 try {
785 ObjectName oname2 =
786 new ObjectName(oname.getDomain() + ":type=StringCache");
787 Registry.getRegistry(null, null)
788 .registerComponent(new StringCache(), oname2, null );
789 } catch (Exception e) {
790 log.error("Error registering ",e);
791 }
792
793 // Initialize our defined Services
794 for (int i = 0; i < services.length; i++) {
795 services[i].initialize();
796 }
797 }
798
799 protected String type;
800 protected String domain;
801 protected String suffix;
802 protected ObjectName oname;
803 protected MBeanServer mserver;
804
805 public ObjectName getObjectName() {
806 return oname;
807 }
808
809 public String getDomain() {
810 return domain;
811 }
812
813 public ObjectName preRegister(MBeanServer server,
814 ObjectName name) throws Exception {
815 oname=name;
816 mserver=server;
817 domain=name.getDomain();
818 return name;
819 }
820
821 public void postRegister(Boolean registrationDone) {
822 }
823
824 public void preDeregister() throws Exception {
825 }
826
827 public void postDeregister() {
828 }
829
830 }