/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.catalina.AccessLog;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerEvent;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.realm.NullRealm;
import org.apache.catalina.util.ServerInfo;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Standard implementation of the <b>Engine</b> interface. Each
* child container must be a Host implementation to process the specific
* fully qualified host name of that virtual host. <br>
* You can set the jvmRoute direct or with the System.property <b>jvmRoute</b>.
*
* @author Craig R. McClanahan
*/
public class StandardEngine extends ContainerBase implements Engine {
private static final Log log = LogFactory.getLog(StandardEngine.class);
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
// ----------------------------------------------------- Instance Variables
/**
* Host name to use when no server host, or an unknown host,
* is specified in the request.
*/
private String defaultHost = null;
/**
* The <code>Service</code> that owns this Engine, if any.
*/
private Service service = null;
/**
* The JVM Route ID for this Tomcat instance. All Route ID's must be unique
* across the cluster.
*/
private String jvmRouteId;
/**
* Default access log to use for request/response pairs where we can't ID
* the intended host and context.
*/
private final AtomicReference<AccessLog> defaultAccessLog =
new AtomicReference<>();
// ------------------------------------------------------------- Properties
/**
* Obtain the configured Realm and provide a default Realm implementation
* when no explicit configuration is set.
*
* @return configured realm, or a {@link NullRealm} by default
*/
@Override
public Realm getRealm() {
Realm configured = super.getRealm();
// If no set realm has been called - default to NullRealm
// This can be overridden at engine, context and host level
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
/**
* Return the default host.
*/
@Override
public String getDefaultHost() {
return defaultHost;
}
/**
* Set the default host.
*
* @param host The new default host
*/
@Override
public void setDefaultHost(String host) {
String oldDefaultHost = this.defaultHost;
if (host == null) {
this.defaultHost = null;
} else {
this.defaultHost = host.toLowerCase(Locale.ENGLISH);
}
support.firePropertyChange("defaultHost", oldDefaultHost,
this.defaultHost);
}
/**
* Set the cluster-wide unique identifier for this Engine.
* This value is only useful in a load-balancing scenario.
* <p>
* This property should not be changed once it is set.
*/
@Override
public void setJvmRoute(String routeId) {
jvmRouteId = routeId;
}
/**
* Retrieve the cluster-wide unique identifier for this Engine.
* This value is only useful in a load-balancing scenario.
*/
@Override
public String getJvmRoute() {
return jvmRouteId;
}
/**
* Return the <code>Service</code> with which we are associated (if any).
*/
@Override
public Service getService() {
return this.service;
}
/**
* Set the <code>Service</code> with which we are associated (if any).
*
* @param service The service that owns this Engine
*/
@Override
public void setService(Service service) {
this.service = service;
}
// --------------------------------------------------------- Public Methods
/**
* Add a child Container, only if the proposed child is an implementation
* of Host.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
/**
* Disallow any attempt to set a parent for this Container, since an
* Engine is supposed to be at the top of the Container hierarchy.
*
* @param container Proposed parent Container
*/
@Override
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
@Override
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
/**
* Override the default implementation. If no access log is defined for the
* Engine, look for one in the Engine's default host and then the default
* host's ROOT context. If still none is found, return the default NoOp
* access log.
*/
@Override
public void logAccess(Request request, Response response, long time,
boolean useDefault) {
boolean logged = false;
if (getAccessLog() != null) {
accessLog.log(request, response, time);
logged = true;
}
if (!logged && useDefault) {
AccessLog newDefaultAccessLog = defaultAccessLog.get();
if (newDefaultAccessLog == null) {
// If we reached this point, this Engine can't have an AccessLog
// Look in the defaultHost
Host host = (Host) findChild(getDefaultHost());
Context context = null;
if (host != null && host.getState().isAvailable()) {
newDefaultAccessLog = host.getAccessLog();
if (newDefaultAccessLog != null) {
if (defaultAccessLog.compareAndSet(null,
newDefaultAccessLog)) {
AccessLogListener l = new AccessLogListener(this,
host, null);
l.install();
}
} else {
// Try the ROOT context of default host
context = (Context) host.findChild("");
if (context != null &&
context.getState().isAvailable()) {
newDefaultAccessLog = context.getAccessLog();
if (newDefaultAccessLog != null) {
if (defaultAccessLog.compareAndSet(null,
newDefaultAccessLog)) {
AccessLogListener l = new AccessLogListener(
this, null, context);
l.install();
}
}
}
}
}
if (newDefaultAccessLog == null) {
newDefaultAccessLog = new NoopAccessLog();
if (defaultAccessLog.compareAndSet(null,
newDefaultAccessLog)) {
AccessLogListener l = new AccessLogListener(this, host,
context);
l.install();
}
}
}
newDefaultAccessLog.log(request, response, time);
}
}
/**
* Return the parent class loader for this component.
*/
@Override
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return parentClassLoader;
if (service != null) {
return service.getParentClassLoader();
}
return ClassLoader.getSystemClassLoader();
}
@Override
public File getCatalinaBase() {
if (service != null) {
Server s = service.getServer();
if (s != null) {
File base = s.getCatalinaBase();
if (base != null) {
return base;
}
}
}
// Fall-back
return super.getCatalinaBase();
}
@Override
public File getCatalinaHome() {
if (service != null) {
Server s = service.getServer();
if (s != null) {
File base = s.getCatalinaHome();
if (base != null) {
return base;
}
}
}
// Fall-back
return super.getCatalinaHome();
}
// -------------------- JMX registration --------------------
@Override
protected String getObjectNameKeyProperties() {
return "type=Engine";
}
@Override
protected String getDomainInternal() {
return getName();
}
// ----------------------------------------------------------- Inner classes
protected static final class NoopAccessLog implements AccessLog {
@Override
public void log(Request request, Response response, long time) {
// NOOP
}
@Override
public void setRequestAttributesEnabled(
boolean requestAttributesEnabled) {
// NOOP
}
@Override
public boolean getRequestAttributesEnabled() {
// NOOP
return false;
}
}
protected static final class AccessLogListener
implements PropertyChangeListener, LifecycleListener,
ContainerListener {
private final StandardEngine engine;
private final Host host;
private final Context context;
private volatile boolean disabled = false;
public AccessLogListener(StandardEngine engine, Host host,
Context context) {
this.engine = engine;
this.host = host;
this.context = context;
}
public void install() {
engine.addPropertyChangeListener(this);
if (host != null) {
host.addContainerListener(this);
host.addLifecycleListener(this);
}
if (context != null) {
context.addLifecycleListener(this);
}
}
private void uninstall() {
disabled = true;
if (context != null) {
context.removeLifecycleListener(this);
}
if (host != null) {
host.removeLifecycleListener(this);
host.removeContainerListener(this);
}
engine.removePropertyChangeListener(this);
}
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (disabled) return;
String type = event.getType();
if (Lifecycle.AFTER_START_EVENT.equals(type) ||
Lifecycle.BEFORE_STOP_EVENT.equals(type) ||
Lifecycle.BEFORE_DESTROY_EVENT.equals(type)) {
// Container is being started/stopped/removed
// Force re-calculation and disable listener since it won't
// be re-used
engine.defaultAccessLog.set(null);
uninstall();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (disabled) return;
if ("defaultHost".equals(evt.getPropertyName())) {
// Force re-calculation and disable listener since it won't
// be re-used
engine.defaultAccessLog.set(null);
uninstall();
}
}
@Override
public void containerEvent(ContainerEvent event) {
// Only useful for hosts
if (disabled) return;
if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
Context context = (Context) event.getData();
if ("".equals(context.getPath())) {
// Force re-calculation and disable listener since it won't
// be re-used
engine.defaultAccessLog.set(null);
uninstall();
}
}
}
}
}