001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.core;
019:
020: import java.beans.PropertyChangeListener;
021: import java.beans.PropertyChangeSupport;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.net.InetAddress;
025: import java.net.ServerSocket;
026: import java.net.Socket;
027: import java.security.AccessControlException;
028: import java.util.Random;
029:
030: import javax.management.MBeanRegistration;
031: import javax.management.MBeanServer;
032: import javax.management.ObjectName;
033:
034: import org.apache.catalina.Context;
035: import org.apache.catalina.Lifecycle;
036: import org.apache.catalina.LifecycleException;
037: import org.apache.catalina.LifecycleListener;
038: import org.apache.catalina.Server;
039: import org.apache.catalina.ServerFactory;
040: import org.apache.catalina.Service;
041: import org.apache.catalina.deploy.NamingResources;
042: import org.apache.catalina.util.LifecycleSupport;
043: import org.apache.catalina.util.StringManager;
044: import org.apache.catalina.util.ServerInfo;
045: import org.apache.juli.logging.Log;
046: import org.apache.juli.logging.LogFactory;
047: import org.apache.tomcat.util.buf.StringCache;
048: import org.apache.tomcat.util.modeler.Registry;
049:
050: /**
051: * Standard implementation of the <b>Server</b> interface, available for use
052: * (but not required) when deploying and starting Catalina.
053: *
054: * @author Craig R. McClanahan
055: * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
056: */
057: public final class StandardServer implements Lifecycle, Server,
058: MBeanRegistration {
059: private static Log log = LogFactory.getLog(StandardServer.class);
060:
061: // -------------------------------------------------------------- Constants
062:
063: /**
064: * ServerLifecycleListener classname.
065: */
066: private static String SERVER_LISTENER_CLASS_NAME = "org.apache.catalina.mbeans.ServerLifecycleListener";
067:
068: // ------------------------------------------------------------ Constructor
069:
070: /**
071: * Construct a default instance of this class.
072: */
073: public StandardServer() {
074:
075: super ();
076: ServerFactory.setServer(this );
077:
078: globalNamingResources = new NamingResources();
079: globalNamingResources.setContainer(this );
080:
081: if (isUseNaming()) {
082: if (namingContextListener == null) {
083: namingContextListener = new NamingContextListener();
084: addLifecycleListener(namingContextListener);
085: }
086: }
087:
088: }
089:
090: // ----------------------------------------------------- Instance Variables
091:
092: /**
093: * Global naming resources context.
094: */
095: private javax.naming.Context globalNamingContext = null;
096:
097: /**
098: * Global naming resources.
099: */
100: private NamingResources globalNamingResources = null;
101:
102: /**
103: * Descriptive information about this Server implementation.
104: */
105: private static final String info = "org.apache.catalina.core.StandardServer/1.0";
106:
107: /**
108: * The lifecycle event support for this component.
109: */
110: private LifecycleSupport lifecycle = new LifecycleSupport(this );
111:
112: /**
113: * The naming context listener for this web application.
114: */
115: private NamingContextListener namingContextListener = null;
116:
117: /**
118: * The port number on which we wait for shutdown commands.
119: */
120: private int port = 8005;
121:
122: /**
123: * A random number generator that is <strong>only</strong> used if
124: * the shutdown command string is longer than 1024 characters.
125: */
126: private Random random = null;
127:
128: /**
129: * The set of Services associated with this Server.
130: */
131: private Service services[] = new Service[0];
132:
133: /**
134: * The shutdown command string we are looking for.
135: */
136: private String shutdown = "SHUTDOWN";
137:
138: /**
139: * The string manager for this package.
140: */
141: private static final StringManager sm = StringManager
142: .getManager(Constants.Package);
143:
144: /**
145: * Has this component been started?
146: */
147: private boolean started = false;
148:
149: /**
150: * Has this component been initialized?
151: */
152: private boolean initialized = false;
153:
154: /**
155: * The property change support for this component.
156: */
157: protected PropertyChangeSupport support = new PropertyChangeSupport(
158: this );
159:
160: private boolean stopAwait = false;
161:
162: // ------------------------------------------------------------- Properties
163:
164: /**
165: * Return the global naming resources context.
166: */
167: public javax.naming.Context getGlobalNamingContext() {
168:
169: return (this .globalNamingContext);
170:
171: }
172:
173: /**
174: * Set the global naming resources context.
175: *
176: * @param globalNamingContext The new global naming resource context
177: */
178: public void setGlobalNamingContext(
179: javax.naming.Context globalNamingContext) {
180:
181: this .globalNamingContext = globalNamingContext;
182:
183: }
184:
185: /**
186: * Return the global naming resources.
187: */
188: public NamingResources getGlobalNamingResources() {
189:
190: return (this .globalNamingResources);
191:
192: }
193:
194: /**
195: * Set the global naming resources.
196: *
197: * @param globalNamingResources The new global naming resources
198: */
199: public void setGlobalNamingResources(
200: NamingResources globalNamingResources) {
201:
202: NamingResources oldGlobalNamingResources = this .globalNamingResources;
203: this .globalNamingResources = globalNamingResources;
204: this .globalNamingResources.setContainer(this );
205: support.firePropertyChange("globalNamingResources",
206: oldGlobalNamingResources, this .globalNamingResources);
207:
208: }
209:
210: /**
211: * Return descriptive information about this Server implementation and
212: * the corresponding version number, in the format
213: * <code><description>/<version></code>.
214: */
215: public String getInfo() {
216:
217: return (info);
218:
219: }
220:
221: /**
222: * Report the current Tomcat Server Release number
223: * @return Tomcat release identifier
224: */
225: public String getServerInfo() {
226:
227: return ServerInfo.getServerInfo();
228: }
229:
230: /**
231: * Return the port number we listen to for shutdown commands.
232: */
233: public int getPort() {
234:
235: return (this .port);
236:
237: }
238:
239: /**
240: * Set the port number we listen to for shutdown commands.
241: *
242: * @param port The new port number
243: */
244: public void setPort(int port) {
245:
246: this .port = port;
247:
248: }
249:
250: /**
251: * Return the shutdown command string we are waiting for.
252: */
253: public String getShutdown() {
254:
255: return (this .shutdown);
256:
257: }
258:
259: /**
260: * Set the shutdown command we are waiting for.
261: *
262: * @param shutdown The new shutdown command
263: */
264: public void setShutdown(String shutdown) {
265:
266: this .shutdown = shutdown;
267:
268: }
269:
270: // --------------------------------------------------------- Server Methods
271:
272: /**
273: * Add a new Service to the set of defined Services.
274: *
275: * @param service The Service to be added
276: */
277: public void addService(Service service) {
278:
279: service.setServer(this );
280:
281: synchronized (services) {
282: Service results[] = new Service[services.length + 1];
283: System.arraycopy(services, 0, results, 0, services.length);
284: results[services.length] = service;
285: services = results;
286:
287: if (initialized) {
288: try {
289: service.initialize();
290: } catch (LifecycleException e) {
291: log.error(e);
292: }
293: }
294:
295: if (started && (service instanceof Lifecycle)) {
296: try {
297: ((Lifecycle) service).start();
298: } catch (LifecycleException e) {
299: ;
300: }
301: }
302:
303: // Report this property change to interested listeners
304: support.firePropertyChange("service", null, service);
305: }
306:
307: }
308:
309: public void stopAwait() {
310: stopAwait = true;
311: }
312:
313: /**
314: * Wait until a proper shutdown command is received, then return.
315: * This keeps the main thread alive - the thread pool listening for http
316: * connections is daemon threads.
317: */
318: public void await() {
319: // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
320: if (port == -2) {
321: // undocumented yet - for embedding apps that are around, alive.
322: return;
323: }
324: if (port == -1) {
325: while (true) {
326: try {
327: Thread.sleep(100000);
328: } catch (InterruptedException ex) {
329: }
330: if (stopAwait)
331: return;
332: }
333: }
334:
335: // Set up a server socket to wait on
336: ServerSocket serverSocket = null;
337: try {
338: serverSocket = new ServerSocket(port, 1, InetAddress
339: .getByName("127.0.0.1"));
340: } catch (IOException e) {
341: log
342: .error("StandardServer.await: create[" + port
343: + "]: ", e);
344: System.exit(1);
345: }
346:
347: // Loop waiting for a connection and a valid command
348: while (true) {
349:
350: // Wait for the next connection
351: Socket socket = null;
352: InputStream stream = null;
353: try {
354: socket = serverSocket.accept();
355: socket.setSoTimeout(10 * 1000); // Ten seconds
356: stream = socket.getInputStream();
357: } catch (AccessControlException ace) {
358: log.warn("StandardServer.accept security exception: "
359: + ace.getMessage(), ace);
360: continue;
361: } catch (IOException e) {
362: log.error("StandardServer.await: accept: ", e);
363: System.exit(1);
364: }
365:
366: // Read a set of characters from the socket
367: StringBuffer command = new StringBuffer();
368: int expected = 1024; // Cut off to avoid DoS attack
369: while (expected < shutdown.length()) {
370: if (random == null)
371: random = new Random(System.currentTimeMillis());
372: expected += (random.nextInt() % 1024);
373: }
374: while (expected > 0) {
375: int ch = -1;
376: try {
377: ch = stream.read();
378: } catch (IOException e) {
379: log.warn("StandardServer.await: read: ", e);
380: ch = -1;
381: }
382: if (ch < 32) // Control character or EOF terminates loop
383: break;
384: command.append((char) ch);
385: expected--;
386: }
387:
388: // Close the socket now that we are done with it
389: try {
390: socket.close();
391: } catch (IOException e) {
392: ;
393: }
394:
395: // Match against our command string
396: boolean match = command.toString().equals(shutdown);
397: if (match) {
398: break;
399: } else
400: log.warn("StandardServer.await: Invalid command '"
401: + command.toString() + "' received");
402:
403: }
404:
405: // Close the server socket and return
406: try {
407: serverSocket.close();
408: } catch (IOException e) {
409: ;
410: }
411:
412: }
413:
414: /**
415: * Return the specified Service (if it exists); otherwise return
416: * <code>null</code>.
417: *
418: * @param name Name of the Service to be returned
419: */
420: public Service findService(String name) {
421:
422: if (name == null) {
423: return (null);
424: }
425: synchronized (services) {
426: for (int i = 0; i < services.length; i++) {
427: if (name.equals(services[i].getName())) {
428: return (services[i]);
429: }
430: }
431: }
432: return (null);
433:
434: }
435:
436: /**
437: * Return the set of Services defined within this Server.
438: */
439: public Service[] findServices() {
440:
441: return (services);
442:
443: }
444:
445: /**
446: * Return the JMX service names.
447: */
448: public ObjectName[] getServiceNames() {
449: ObjectName onames[] = new ObjectName[services.length];
450: for (int i = 0; i < services.length; i++) {
451: onames[i] = ((StandardService) services[i]).getObjectName();
452: }
453: return onames;
454: }
455:
456: /**
457: * Remove the specified Service from the set associated from this
458: * Server.
459: *
460: * @param service The Service to be removed
461: */
462: public void removeService(Service service) {
463:
464: synchronized (services) {
465: int j = -1;
466: for (int i = 0; i < services.length; i++) {
467: if (service == services[i]) {
468: j = i;
469: break;
470: }
471: }
472: if (j < 0)
473: return;
474: if (services[j] instanceof Lifecycle) {
475: try {
476: ((Lifecycle) services[j]).stop();
477: } catch (LifecycleException e) {
478: ;
479: }
480: }
481: int k = 0;
482: Service results[] = new Service[services.length - 1];
483: for (int i = 0; i < services.length; i++) {
484: if (i != j)
485: results[k++] = services[i];
486: }
487: services = results;
488:
489: // Report this property change to interested listeners
490: support.firePropertyChange("service", service, null);
491: }
492:
493: }
494:
495: // --------------------------------------------------------- Public Methods
496:
497: /**
498: * Add a property change listener to this component.
499: *
500: * @param listener The listener to add
501: */
502: public void addPropertyChangeListener(
503: PropertyChangeListener listener) {
504:
505: support.addPropertyChangeListener(listener);
506:
507: }
508:
509: /**
510: * Remove a property change listener from this component.
511: *
512: * @param listener The listener to remove
513: */
514: public void removePropertyChangeListener(
515: PropertyChangeListener listener) {
516:
517: support.removePropertyChangeListener(listener);
518:
519: }
520:
521: /**
522: * Return a String representation of this component.
523: */
524: public String toString() {
525:
526: StringBuffer sb = new StringBuffer("StandardServer[");
527: sb.append(getPort());
528: sb.append("]");
529: return (sb.toString());
530:
531: }
532:
533: /**
534: * Write the configuration information for this entire <code>Server</code>
535: * out to the server.xml configuration file.
536: *
537: * @exception javax.management.InstanceNotFoundException if the managed resource object
538: * cannot be found
539: * @exception javax.management.MBeanException if the initializer of the object throws
540: * an exception, or persistence is not supported
541: * @exception javax.management.RuntimeOperationsException if an exception is reported
542: * by the persistence mechanism
543: */
544: public synchronized void storeConfig() throws Exception {
545:
546: ObjectName sname = null;
547: try {
548: sname = new ObjectName("Catalina:type=StoreConfig");
549: if (mserver.isRegistered(sname)) {
550: mserver.invoke(sname, "storeConfig", null, null);
551: } else
552: log.error("StoreConfig mbean not registered" + sname);
553: } catch (Throwable t) {
554: log.error(t);
555: }
556:
557: }
558:
559: /**
560: * Write the configuration information for <code>Context</code>
561: * out to the specified configuration file.
562: *
563: * @exception javax.management.InstanceNotFoundException if the managed resource object
564: * cannot be found
565: * @exception javax.management.MBeanException if the initializer of the object throws
566: * an exception, or persistence is not supported
567: * @exception javax.management.RuntimeOperationsException if an exception is reported
568: * by the persistence mechanism
569: */
570: public synchronized void storeContext(Context context)
571: throws Exception {
572:
573: ObjectName sname = null;
574: try {
575: sname = new ObjectName("Catalina:type=StoreConfig");
576: if (mserver.isRegistered(sname)) {
577: mserver.invoke(sname, "store",
578: new Object[] { context },
579: new String[] { "java.lang.String" });
580: } else
581: log.error("StoreConfig mbean not registered" + sname);
582: } catch (Throwable t) {
583: log.error(t);
584: }
585:
586: }
587:
588: /**
589: * Return true if naming should be used.
590: */
591: private boolean isUseNaming() {
592: boolean useNaming = true;
593: // Reading the "catalina.useNaming" environment variable
594: String useNamingProperty = System
595: .getProperty("catalina.useNaming");
596: if ((useNamingProperty != null)
597: && (useNamingProperty.equals("false"))) {
598: useNaming = false;
599: }
600: return useNaming;
601: }
602:
603: // ------------------------------------------------------ Lifecycle Methods
604:
605: /**
606: * Add a LifecycleEvent listener to this component.
607: *
608: * @param listener The listener to add
609: */
610: public void addLifecycleListener(LifecycleListener listener) {
611:
612: lifecycle.addLifecycleListener(listener);
613:
614: }
615:
616: /**
617: * Get the lifecycle listeners associated with this lifecycle. If this
618: * Lifecycle has no listeners registered, a zero-length array is returned.
619: */
620: public LifecycleListener[] findLifecycleListeners() {
621:
622: return lifecycle.findLifecycleListeners();
623:
624: }
625:
626: /**
627: * Remove a LifecycleEvent listener from this component.
628: *
629: * @param listener The listener to remove
630: */
631: public void removeLifecycleListener(LifecycleListener listener) {
632:
633: lifecycle.removeLifecycleListener(listener);
634:
635: }
636:
637: /**
638: * Prepare for the beginning of active use of the public methods of this
639: * component. This method should be called before any of the public
640: * methods of this component are utilized. It should also send a
641: * LifecycleEvent of type START_EVENT to any registered listeners.
642: *
643: * @exception LifecycleException if this component detects a fatal error
644: * that prevents this component from being used
645: */
646: public void start() throws LifecycleException {
647:
648: // Validate and update our current component state
649: if (started) {
650: log.debug(sm.getString("standardServer.start.started"));
651: return;
652: }
653:
654: // Notify our interested LifecycleListeners
655: lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
656:
657: lifecycle.fireLifecycleEvent(START_EVENT, null);
658: started = true;
659:
660: // Start our defined Services
661: synchronized (services) {
662: for (int i = 0; i < services.length; i++) {
663: if (services[i] instanceof Lifecycle)
664: ((Lifecycle) services[i]).start();
665: }
666: }
667:
668: // Notify our interested LifecycleListeners
669: lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
670:
671: }
672:
673: /**
674: * Gracefully terminate the active use of the public methods of this
675: * component. This method should be the last one called on a given
676: * instance of this component. It should also send a LifecycleEvent
677: * of type STOP_EVENT to any registered listeners.
678: *
679: * @exception LifecycleException if this component detects a fatal error
680: * that needs to be reported
681: */
682: public void stop() throws LifecycleException {
683:
684: // Validate and update our current component state
685: if (!started)
686: return;
687:
688: // Notify our interested LifecycleListeners
689: lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
690:
691: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
692: started = false;
693:
694: // Stop our defined Services
695: for (int i = 0; i < services.length; i++) {
696: if (services[i] instanceof Lifecycle)
697: ((Lifecycle) services[i]).stop();
698: }
699:
700: // Notify our interested LifecycleListeners
701: lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
702:
703: }
704:
705: public void init() throws Exception {
706: initialize();
707: }
708:
709: /**
710: * Invoke a pre-startup initialization. This is used to allow connectors
711: * to bind to restricted ports under Unix operating environments.
712: */
713: public void initialize() throws LifecycleException {
714: if (initialized) {
715: log
716: .info(sm
717: .getString("standardServer.initialize.initialized"));
718: return;
719: }
720: lifecycle.fireLifecycleEvent(INIT_EVENT, null);
721: initialized = true;
722:
723: if (oname == null) {
724: try {
725: oname = new ObjectName("Catalina:type=Server");
726: Registry.getRegistry(null, null).registerComponent(
727: this , oname, null);
728: } catch (Exception e) {
729: log.error("Error registering ", e);
730: }
731: }
732:
733: // Register global String cache
734: try {
735: ObjectName oname2 = new ObjectName(oname.getDomain()
736: + ":type=StringCache");
737: Registry.getRegistry(null, null).registerComponent(
738: new StringCache(), oname2, null);
739: } catch (Exception e) {
740: log.error("Error registering ", e);
741: }
742:
743: // Initialize our defined Services
744: for (int i = 0; i < services.length; i++) {
745: services[i].initialize();
746: }
747: }
748:
749: protected String type;
750: protected String domain;
751: protected String suffix;
752: protected ObjectName oname;
753: protected MBeanServer mserver;
754:
755: public ObjectName getObjectName() {
756: return oname;
757: }
758:
759: public String getDomain() {
760: return domain;
761: }
762:
763: public ObjectName preRegister(MBeanServer server, ObjectName name)
764: throws Exception {
765: oname = name;
766: mserver = server;
767: domain = name.getDomain();
768: return name;
769: }
770:
771: public void postRegister(Boolean registrationDone) {
772: }
773:
774: public void preDeregister() throws Exception {
775: }
776:
777: public void postDeregister() {
778: }
779:
780: }
|