/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. * * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright 2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.core; import org.apache.catalina.*; import org.apache.catalina.deploy.NamingResources; import org.apache.catalina.util.LifecycleSupport; import javax.management.ObjectName; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.security.AccessControlException; import java.util.List; import java.util.Random; import java.util.ResourceBundle; import java.text.MessageFormat; import java.util.logging.Level; import java.util.logging.Logger; import org.glassfish.logging.annotation.LogMessageInfo; import org.glassfish.logging.annotation.LoggerInfo; import org.glassfish.logging.annotation.LogMessagesResourceBundle; /** * Standard implementation of the <b>Server</b> interface, available for use * (but not required) when deploying and starting Catalina. * * @author Craig R. McClanahan * @version $Revision: 1.5 $ $Date: 2007/02/20 20:16:56 $ */ public final class StandardServer implements Lifecycle, Server { @LogMessagesResourceBundle public static final String SHARED_LOG_MESSAGE_RESOURCE = "org.apache.catalina.core.LogMessages"; @LoggerInfo(subsystem = "WEB", description = "WEB Core Logger", publish = true) public static final String WEB_CORE_LOGGER = "javax.enterprise.web.core"; public static final Logger log = Logger.getLogger(WEB_CORE_LOGGER, SHARED_LOG_MESSAGE_RESOURCE); private static final ResourceBundle rb = log.getResourceBundle(); @LogMessageInfo( message = "LifecycleException occurred during service initialization: {0}", level = "SEVERE", cause = "This service was already initialized", action = "Verify if the service is not already initialized") public static final String LIFECYCLE_EXCEPTION_DURING_SERVICE_INIT = "AS-WEB-CORE-00243"; @LogMessageInfo( message = "Exception StandardServer.await: create[{0}]", level = "SEVERE", cause = "An I/O error occurred when opening the socket", action = "Verify the port number and try again") public static final String STANDARD_SERVER_AWAIT_CREATE_EXCEPTION = "AS-WEB-CORE-00244"; @LogMessageInfo( message = "StandardServer.accept security exception: {0}", level = "WARNING", cause = "Could not get connection", action = "Verify the connection settings and try again") public static final String STANDARD_SERVER_ACCEPT_SECURITY_EXCEPTION = "AS-WEB-CORE-00245"; @LogMessageInfo( message = "StandardServer.await: accept: {0}", level = "SEVERE", cause = "Could not get input stream", action = "Verify the input stream and try again") public static final String STANDARD_SERVER_AWAIT_ACCEPT_EXCEPTION = "AS-WEB-CORE-00246"; @LogMessageInfo( message = "StandardServer.await: read: {0}", level = "WARNING", cause = "Could not read from input stream", action = "Verify the input stream and try again") public static final String STANDARD_SERVER_AWAIT_READ_EXCEPTION = "AS-WEB-CORE-00247"; @LogMessageInfo( message = "StandardServer.await: Invalid command {0} received", level = "WARNING", cause = "Invalid command", action = "Verify the command") public static final String STANDARD_SERVER_AWAIT_INVALID_COMMAND_RECEIVED_EXCEPTION = "AS-WEB-CORE-00248"; @LogMessageInfo( message = "This service has already been initialized", level = "INFO") public static final String STANDARD_SERVER_INITIALIZE_INITIALIZED = "AS-WEB-CORE-00249"; @LogMessageInfo( message = "Error registering: {0}", level = "SEVERE", cause = "Could not register ObjectName: \"Catalina:type=Server\"", action = "Verify the configuration and try again") public static final String ERROR_REGISTERING = "AS-WEB-CORE-00250"; //-------------------------------------------------------------- // -------------------------------------------------------------- Constants /** * The set of class/property combinations that should <strong>NOT</strong> * be persisted because they are automatically calculated. */ private static String exceptions[][] = { { "org.apache.catalina.core.StandardEngine", "domain" }, { "org.apache.catalina.core.StandardHost", "domain" }, { "org.apache.catalina.core.StandardContext", "available" }, { "org.apache.catalina.core.StandardContext", "configFile" }, { "org.apache.catalina.core.StandardContext", "configured" }, { "org.apache.catalina.core.StandardContext", "distributable" }, { "org.apache.catalina.core.StandardContext", "domain" }, { "org.apache.catalina.core.StandardContext", "engineName" }, { "org.apache.catalina.core.StandardContext", "name" }, { "org.apache.catalina.core.StandardContext", "override" }, { "org.apache.catalina.core.StandardContext", "publicId" }, { "org.apache.catalina.core.StandardContext", "replaceWelcomeFiles" }, { "org.apache.catalina.core.StandardContext", "sessionTimeout" }, { "org.apache.catalina.core.StandardContext", "startupTime" }, { "org.apache.catalina.core.StandardContext", "tldScanTime" }, { "org.apache.catalina.core.StandardContext", "workDir" }, { "org.apache.catalina.session.StandardManager", "distributable" }, { "org.apache.catalina.session.StandardManager", "entropy" }, }; /** * The set of classes that represent persistable properties. */ private static Class persistables[] = { String.class, Integer.class, Integer.TYPE, Boolean.class, Boolean.TYPE, Byte.class, Byte.TYPE, Character.class, Character.TYPE, Double.class, Double.TYPE, Float.class, Float.TYPE, Long.class, Long.TYPE, Short.class, Short.TYPE, }; /** * The set of class names that should be skipped when persisting state, * because the corresponding listeners, valves, etc. are configured * automatically at startup time. */ private static String skippables[] = { "org.apache.catalina.authenticator.BasicAuthenticator", "org.apache.catalina.authenticator.DigestAuthenticator", "org.apache.catalina.authenticator.FormAuthenticator", "org.apache.catalina.authenticator.NonLoginAuthenticator", "org.apache.catalina.authenticator.SSLAuthenticator", "org.apache.catalina.core.NamingContextListener", "org.apache.catalina.core.StandardContextValve", "org.apache.catalina.core.StandardEngineValve", "org.apache.catalina.core.StandardHostValve", "org.apache.catalina.startup.ContextConfig", "org.apache.catalina.startup.EngineConfig", "org.apache.catalina.startup.HostConfig", "org.apache.catalina.valves.CertificatesValve", "org.apache.catalina.valves.ErrorReportValve", "org.apache.catalina.valves.RequestListenerValve", }; /** * The set of class names that are the standard implementations of * components, and hence should not be persisted. */ private static String standardImplementations[] = { "org.apache.catalina.core.StandardServer", "org.apache.catalina.core.StandardService", "org.apache.catalina.connector.CoyoteConnector", "org.apache.catalina.core.StandardEngine", "org.apache.catalina.core.StandardHost", "org.apache.catalina.core.StandardContext" }; // ------------------------------------------------------------ Constructor /** * Construct a default instance of this class. */ public StandardServer() { super(); ServerFactory.setServer(this); globalNamingResources = new NamingResources(); globalNamingResources.setContainer(this); if (isUseNaming()) { namingContextListener = new NamingContextListener(); namingContextListener.setDebug(getDebug()); addLifecycleListener(namingContextListener); } } // ----------------------------------------------------- Instance Variables /** * Debugging detail level. */ private int debug = 0; /** * Global naming resources context. */ private javax.naming.Context globalNamingContext = null; /** * Global naming resources. */ private NamingResources globalNamingResources = null; /** * Descriptive information about this Server implementation. */ private static final String info = "org.apache.catalina.core.StandardServer/1.0"; /** * The lifecycle event support for this component. */ private LifecycleSupport lifecycle = new LifecycleSupport(this); /** * The naming context listener for this web application. */ private NamingContextListener namingContextListener = null; /** * The port number on which we wait for shutdown commands. */ private int port = 8005; /** * A random number generator that is <strong>only</strong> used if * the shutdown command string is longer than 1024 characters. */ private Random random = null; /** * The set of Services associated with this Server. */ private Service services[] = new Service[0]; private final Object servicesMonitor = new Object(); /** * The shutdown command string we are looking for. */ private String shutdown = "SHUTDOWN"; /** * Has this component been started? */ private boolean started = false; /** * Has this component been initialized? */ private boolean initialized = false; /** * The property change support for this component. */ private PropertyChangeSupport support = new PropertyChangeSupport(this); // ------------------------------------------------------------- Properties /** * Return the debugging detail level. */ public int getDebug() { return (this.debug); } /** * Set the debugging detail level. * * @param debug The new debugging detail level */ public void setDebug(int debug) { this.debug = debug; } /** * Return the global naming resources context. */ public javax.naming.Context getGlobalNamingContext() { return (this.globalNamingContext); } /** * Set the global naming resources context. * * @param globalNamingContext The new global naming resource context */ public void setGlobalNamingContext (javax.naming.Context globalNamingContext) { this.globalNamingContext = globalNamingContext; } /** * Return the global naming resources. */ public NamingResources getGlobalNamingResources() { return (this.globalNamingResources); } /** * Set the global naming resources. * * @param globalNamingResources The new global naming resources */ public void setGlobalNamingResources (NamingResources globalNamingResources) { NamingResources oldGlobalNamingResources = this.globalNamingResources; this.globalNamingResources = globalNamingResources; this.globalNamingResources.setContainer(this); support.firePropertyChange("globalNamingResources", oldGlobalNamingResources, this.globalNamingResources); } /** * Return descriptive information about this Server implementation and * the corresponding version number, in the format * <code><description>/<version></code>. */ public String getInfo() { return (info); } /** * Return the port number we listen to for shutdown commands. */ public int getPort() { return (this.port); } /** * Set the port number we listen to for shutdown commands. * * @param port The new port number */ public void setPort(int port) { this.port = port; } /** * Return the shutdown command string we are waiting for. */ public String getShutdown() { return (this.shutdown); } /** * Set the shutdown command we are waiting for. * * @param shutdown The new shutdown command */ public void setShutdown(String shutdown) { this.shutdown = shutdown; } // --------------------------------------------------------- Server Methods /** * Add a new Service to the set of defined Services. * * @param service The Service to be added */ public void addService(Service service) { service.setServer(this); synchronized (servicesMonitor) { Service results[] = new Service[services.length + 1]; System.arraycopy(services, 0, results, 0, services.length); results[services.length] = service; services = results; if (initialized) { try { service.initialize(); } catch (LifecycleException e) { String msg = MessageFormat.format(rb.getString( LIFECYCLE_EXCEPTION_DURING_SERVICE_INIT), e.toString()); log.log(Level.SEVERE, msg, e); } } if (started && (service instanceof Lifecycle)) { try { ((Lifecycle) service).start(); } catch (LifecycleException e) { // Ignore } } // Report this property change to interested listeners support.firePropertyChange("service", null, service); } } /** * Wait until a proper shutdown command is received, then return. */ public void await() { // Set up a server socket to wait on ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { String msg = MessageFormat.format( rb.getString(STANDARD_SERVER_AWAIT_CREATE_EXCEPTION), port); log.log(Level.SEVERE, msg, e); System.exit(1); } // Loop waiting for a connection and a valid command while (true) { // Wait for the next connection Socket socket = null; InputStream stream = null; try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (AccessControlException ace) { String msg = MessageFormat.format( rb.getString(STANDARD_SERVER_ACCEPT_SECURITY_EXCEPTION), ace.getMessage()); log.log(Level.WARNING, msg, ace); continue; } catch (IOException e) { String msg = MessageFormat.format( rb.getString(STANDARD_SERVER_AWAIT_ACCEPT_EXCEPTION), e.toString()); log.log(Level.SEVERE, msg, e); System.exit(1); } // Read a set of characters from the socket StringBuilder command = new StringBuilder(); int expected = 1024; // Cut off to avoid DoS attack while (expected < shutdown.length()) { if (random == null) random = new Random(System.currentTimeMillis()); expected += random.nextInt(1024); } while (expected > 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { String msg = MessageFormat.format( rb.getString(STANDARD_SERVER_AWAIT_READ_EXCEPTION), e.toString()); log.log(Level.WARNING, msg, e); ch = -1; } if (ch < 32) // Control character or EOF terminates loop break; command.append((char) ch); expected--; } // Close the socket now that we are done with it try { socket.close(); } catch (IOException e) { // Ignore } // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { break; } else { log.log(Level.WARNING, STANDARD_SERVER_AWAIT_INVALID_COMMAND_RECEIVED_EXCEPTION, command.toString()); } } // Close the server socket and return try { serverSocket.close(); } catch (IOException e) { // Ignore } } /** * Return the specified Service (if it exists); otherwise return * <code>null</code>. * * @param name Name of the Service to be returned */ public Service findService(String name) { if (name == null) { return (null); } synchronized (servicesMonitor) { for (int i = 0; i < services.length; i++) { if (name.equals(services[i].getName())) { return (services[i]); } } } return (null); } /** * Return the set of Services defined within this Server. */ public Service[] findServices() { return (services); } /** * @return the object names of all registered Service instances */ public ObjectName[] getServiceNames() { ObjectName onames[]=new ObjectName[ services.length ]; for( int i=0; i<services.length; i++ ) { onames[i]=((StandardService)services[i]).getObjectName(); } return onames; } /** * Remove the specified Service from the set associated from this * Server. * * @param service The Service to be removed */ public void removeService(Service service) { synchronized (servicesMonitor) { int j = -1; for (int i = 0; i < services.length; i++) { if (service == services[i]) { j = i; break; } } if (j < 0) return; if (services[j] instanceof Lifecycle) { try { ((Lifecycle) services[j]).stop(); } catch (LifecycleException e) { // Ignore } } int k = 0; Service results[] = new Service[services.length - 1]; for (int i = 0; i < services.length; i++) { if (i != j) results[k++] = services[i]; } services = results; // Report this property change to interested listeners support.firePropertyChange("service", service, null); } } // --------------------------------------------------------- Public Methods /** * Add a property change listener to this component. * * @param listener The listener to add */ public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } /** * Remove a property change listener from this component. * * @param listener The listener to remove */ public void removePropertyChangeListener(PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } /** * Return a String representation of this component. */ public String toString() { StringBuilder sb = new StringBuilder("StandardServer["); sb.append(getPort()); sb.append("]"); return (sb.toString()); } // -------------------------------------------------------- Private Methods /** * Return true if naming should be used. */ private boolean isUseNaming() { boolean useNaming = true; // Reading the "catalina.useNaming" environment variable String useNamingProperty = System.getProperty("catalina.useNaming"); if ((useNamingProperty != null) && (useNamingProperty.equals("false"))) { useNaming = false; } return useNaming; } // ---------------------------------------------------- Lifecycle Methods /** * Add a LifecycleEvent listener to this component. * * @param listener The listener to add */ public void addLifecycleListener(LifecycleListener listener) { lifecycle.addLifecycleListener(listener); } /** * Gets the (possibly empty) list of lifecycle listeners * associated with this StandardServer. */ public List<LifecycleListener> findLifecycleListeners() { return lifecycle.findLifecycleListeners(); } /** * Remove a LifecycleEvent listener from this component. * * @param listener The listener to remove */ public void removeLifecycleListener(LifecycleListener listener) { lifecycle.removeLifecycleListener(listener); } /** * Prepare for the beginning of active use of the public methods of this * component. This method should be called before any of the public * methods of this component are utilized. It should also send a * LifecycleEvent of type START_EVENT to any registered listeners. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ public void start() throws LifecycleException { // Validate and update our current component state if (started) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "This server has already been started"); } return; } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Start our defined Services synchronized (servicesMonitor) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); } /** * Gracefully terminate the active use of the public methods of this * component. This method should be the last one called on a given * instance of this component. It should also send a LifecycleEvent * of type STOP_EVENT to any registered listeners. * * @exception LifecycleException if this component detects a fatal error * that needs to be reported */ public void stop() throws LifecycleException { // Validate and update our current component state if (!started) return; // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; // Stop our defined Services for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).stop(); } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null); } public void init() throws Exception { initialize(); } /** * Invoke a pre-startup initialization. This is used to allow connectors * to bind to restricted ports under Unix operating environments. */ public void initialize() throws LifecycleException { if (initialized) { log.log(Level.INFO, STANDARD_SERVER_INITIALIZE_INITIALIZED); return; } // START GlassFish 2439 lifecycle.fireLifecycleEvent(INIT_EVENT, null); // END GlassFish 2439 initialized = true; if( oname==null ) { try { oname=new ObjectName( "Catalina:type=Server"); } catch (Exception e) { String msg = MessageFormat.format(ERROR_REGISTERING, e.toString()); log.log(Level.SEVERE, msg, e); } } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].initialize(); } } private String type; private String domain; private String suffix; private ObjectName oname; public ObjectName getObjectName() { return oname; } public String getDomain() { return domain; } }