/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.server.host;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import com.caucho.bam.broker.Broker;
import com.caucho.bam.broker.ManagedBroker;
import com.caucho.cloud.network.NetworkListenSystem;
import com.caucho.cloud.topology.CloudCluster;
import com.caucho.config.ConfigException;
import com.caucho.config.Configurable;
import com.caucho.config.SchemaBean;
import com.caucho.config.inject.InjectManager;
import com.caucho.env.deploy.EnvironmentDeployInstance;
import com.caucho.env.service.ResinSystem;
import com.caucho.hemp.broker.HempBroker;
import com.caucho.hemp.broker.HempBrokerManager;
import com.caucho.http.log.AccessLog;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.lifecycle.LifecycleState;
import com.caucho.loader.EnvironmentBean;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.make.AlwaysModified;
import com.caucho.management.server.HostMXBean;
import com.caucho.network.listen.TcpSocketLinkListener;
import com.caucho.rewrite.DispatchRule;
import com.caucho.rewrite.RewriteFilter;
import com.caucho.server.cluster.Server;
import com.caucho.server.dispatch.ExceptionFilterChain;
import com.caucho.server.dispatch.Invocation;
import com.caucho.server.dispatch.InvocationBuilder;
import com.caucho.server.e_app.EarConfig;
import com.caucho.server.e_app.EarDeployGenerator;
import com.caucho.server.resin.Resin;
import com.caucho.server.rewrite.RewriteDispatch;
import com.caucho.server.webapp.ErrorPage;
import com.caucho.server.webapp.ErrorPageManager;
import com.caucho.server.webapp.WebAppConfig;
import com.caucho.server.webapp.WebAppContainer;
import com.caucho.server.webapp.WebAppExpandDeployGenerator;
import com.caucho.util.L10N;
import com.caucho.vfs.Dependency;
import com.caucho.vfs.Path;
/**
* Resin's virtual host implementation.
*/
public class Host
implements EnvironmentBean, Dependency, SchemaBean,
EnvironmentDeployInstance, InvocationBuilder
{
private static final L10N L = new L10N(Host.class);
private static final Logger log = Logger.getLogger(Host.class.getName());
private static EnvironmentLocal<Host> _hostLocal
= new EnvironmentLocal<Host>("caucho.host");
private final Server _servletContainer;
private final HostContainer _parent;
// The Host entry
private final HostController _controller;
private final Path _rootDirectory;
private EnvironmentClassLoader _classLoader;
// The canonical host name. The host name may include the port.
private String _hostName = "";
// The canonical URL
private String _url;
private String _serverName = "";
private int _serverPort = 0;
private final WebAppContainer _webAppContainer;
private ErrorPageManager _errorPageManager;
// The secure host
private String _secureHostName;
private Boolean _isSecure;
private boolean _isDefaultHost;
// Alises
private ArrayList<String> _aliasList = new ArrayList<String>();
private HempBroker _bamBroker;
private Throwable _configException;
private String _configETag = null;
private final Lifecycle _lifecycle;
/**
* Creates the webApp with its environment loader.
*/
public Host(HostContainer parent,
HostController controller,
String hostName)
{
_servletContainer = parent.getServer();
if (_servletContainer == null)
throw new IllegalStateException(L.l("Host requires an active Servlet container"));
_classLoader = EnvironmentClassLoader.create("host:" + controller.getName());
_parent = parent;
_controller = controller;
_rootDirectory = controller.getRootDirectory();
if (controller.getId().startsWith("error/"))
_lifecycle = new Lifecycle(log, "Host[" + controller.getId() + "]", Level.FINEST);
else
_lifecycle = new Lifecycle(log, "Host[" + controller.getId() + "]", Level.INFO);
InjectManager.create(_classLoader);
_webAppContainer = new WebAppContainer(_servletContainer,
this,
_rootDirectory,
getClassLoader(),
_lifecycle);
try {
setHostName(hostName);
_hostLocal.set(this, getClassLoader());
} catch (Exception e) {
_configException = e;
}
}
/**
* Returns the local host.
*/
public static Host getLocal()
{
return _hostLocal.get();
}
/**
* Sets the canonical host name.
*/
private void setHostName(String name)
throws ConfigException
{
_hostName = name;
if (name.equals(""))
_isDefaultHost = true;
addHostAlias(name);
int p = name.indexOf("://");
if (p >= 0)
name = name.substring(p + 3);
_serverName = name;
p = name.lastIndexOf(':');
if (p > 0) {
_serverName = name.substring(0, p);
boolean isPort = true;
int port = 0;
for (p++; p < name.length(); p++) {
char ch = name.charAt(p);
if ('0' <= ch && ch <= '9')
port = 10 * port + ch - '0';
else
isPort = false;
}
if (isPort)
_serverPort = port;
}
}
/**
* Returns the entry name
*/
public String getName()
{
return _controller.getName();
}
/**
* Returns the canonical host name. The canonical host name may include
* the port.
*/
public String getHostName()
{
return _hostName;
}
/**
* Returns the secure host name. Used for redirects.
*/
public String getSecureHostName()
{
return _secureHostName;
}
/**
* Sets the secure host name. Used for redirects.
*/
public void setSecureHostName(String secureHostName)
{
_secureHostName = secureHostName;
}
public void setSetRequestSecure(boolean isSecure)
{
_isSecure = isSecure;
}
public Boolean isRequestSecure()
{
return _isSecure;
}
/**
* Returns the bam broker.
*/
public Broker getBamBroker()
{
return _bamBroker;
}
/**
* Returns the relax schema.
*/
@Override
public String getSchema()
{
return "com/caucho/server/host/host.rnc";
}
/**
* Returns id for the host
*/
public String getId()
{
return _controller.getId();
}
public String getIdTail()
{
String id = _controller.getId();
int p = id.indexOf("/host/");
return id.substring(p + 6);
}
/**
* Returns the URL for the container.
*/
public String getURL()
{
if (_url != null && ! "".equals(_url))
return _url;
else if (_hostName == null
|| _hostName.equals("")
|| _hostName.equals("default")) {
Server server = getServer();
if (server == null)
return "http://localhost";
ResinSystem resinSystem = server.getResinSystem();
NetworkListenSystem listenService
= resinSystem.getService(NetworkListenSystem.class);
for (TcpSocketLinkListener port : listenService.getListeners()) {
if ("http".equals(port.getProtocolName())) {
String address = port.getAddress();
if (address == null || address.equals(""))
address = "localhost";
return "http://" + address + ":" + port.getPort();
}
}
for (TcpSocketLinkListener port : listenService.getListeners()) {
if ("https".equals(port.getProtocolName())) {
String address = port.getAddress();
if (address == null || address.equals(""))
address = "localhost";
return "https://" + address + ":" + port.getPort();
}
}
return "http://localhost";
}
else if (_hostName.startsWith("http:")
|| _hostName.startsWith("https:"))
return _hostName;
else if (_hostName.equals("") || _hostName.equals("default"))
return "http://localhost";
else
return "http://" + _hostName;
}
/**
* Adds an alias.
*/
public void addHostAlias(String name)
{
name = name.toLowerCase(Locale.ENGLISH);
if (! _aliasList.contains(name))
_aliasList.add(name);
if (name.equals("") || name.equals("*"))
_isDefaultHost = true;
_controller.addExtHostAlias(name);
}
public LifecycleState getState()
{
return _lifecycle.getState();
}
/**
* Gets the alias list.
*/
public ArrayList<String> getAliasList()
{
return _aliasList;
}
/**
* Adds an alias.
*/
@Configurable
public void addHostAliasRegexp(String name)
{
name = name.trim();
Pattern pattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
_controller.addExtHostAliasRegexp(pattern);
}
/**
* Returns true if matches the default host.
*/
public boolean isDefaultHost()
{
return _isDefaultHost;
}
/**
* Gets the environment class loader.
*/
@Override
public EnvironmentClassLoader getClassLoader()
{
return _classLoader;
}
public Path getRootDirectory()
{
return _rootDirectory;
}
@Configurable
public void setAccessLog(AccessLog log)
{
_webAppContainer.setAccessLog(log);
}
/**
* Sets the doc dir.
*/
public void setDocumentDirectory(Path docDir)
{
_webAppContainer.setDocumentDirectory(docDir);
}
public WebAppContainer getWebAppContainer()
{
return _webAppContainer;
}
@Configurable
public void addErrorPage(ErrorPage errorPage)
{
getErrorPageManager().addErrorPage(errorPage);
}
public ErrorPageManager getErrorPageManager()
{
if (_errorPageManager == null) {
_errorPageManager = new ErrorPageManager(getServer(), this, null);
}
return _errorPageManager;
}
@Configurable
public void addWebApp(WebAppConfig config)
{
getWebAppContainer().addWebApp(config);
}
@Configurable
public void addWebAppDefault(WebAppConfig config)
{
getWebAppContainer().addWebAppDefault(config);
}
/**
* Sets the war-expansion
*/
@Configurable
public WebAppExpandDeployGenerator createWarDeploy()
{
return getWebAppContainer().createWebAppDeploy();
}
/**
* Sets the war-expansion
*/
@Configurable
public void addWarDeploy(WebAppExpandDeployGenerator webAppDeploy)
throws ConfigException
{
getWebAppContainer().addWarDeploy(webAppDeploy);
}
/**
* Sets the war-expansion
*/
@Configurable
public WebAppExpandDeployGenerator createWebAppDeploy()
{
return getWebAppContainer().createWebAppDeploy();
}
/**
* Sets the war-expansion
*/
public void addWebAppDeploy(WebAppExpandDeployGenerator deploy)
throws ConfigException
{
getWebAppContainer().addWebAppDeploy(deploy);
}
/**
* Sets the ear-expansion
*/
@Configurable
public EarDeployGenerator createEarDeploy()
throws Exception
{
return getWebAppContainer().createEarDeploy();
}
/**
* Adds the ear-expansion
*/
@Configurable
public void addEarDeploy(EarDeployGenerator earDeploy)
throws Exception
{
getWebAppContainer().addEarDeploy(earDeploy);
}
@Configurable
public void addEarDefault(EarConfig config)
{
getWebAppContainer().addEarDefault(config);
}
/**
* Sets the war-dir for backwards compatibility.
*/
@Configurable
public void setWarDir(Path warDir)
throws ConfigException
{
getWebAppContainer().setWarDir(warDir);
}
/**
* Sets the war-expand-dir.
*/
public void setWarExpandDir(Path warDir)
{
getWebAppContainer().setWarExpandDir(warDir);
}
/**
* Adds a rewrite dispatch rule
*/
public void add(DispatchRule dispatchRule)
{
_webAppContainer.add(dispatchRule);
}
/**
* Adds a rewrite dispatch rule
*/
public void add(RewriteFilter dispatchAction)
{
_webAppContainer.add(dispatchAction);
}
/**
* Adds rewrite-dispatch (backward compat).
*/
public RewriteDispatch createRewriteDispatch()
{
return _webAppContainer.createRewriteDispatch();
}
/**
* Sets the config exception.
*/
@Override
public void setConfigException(Throwable e)
{
if (e != null) {
_configException = e;
_classLoader.addDependency(AlwaysModified.create());
if (log.isLoggable(Level.FINE))
log.log(Level.FINE, e.toString(), e);
}
}
/**
* Gets the config exception.
*/
@Override
public Throwable getConfigException()
{
return _configException;
}
/**
* Returns the owning server.
*/
public Server getServer()
{
return _parent.getServer();
}
/**
* Returns the current cluster.
*/
public CloudCluster getCluster()
{
Server server = getServer();
if (server != null)
return server.getCluster();
else
return null;
}
/**
* Returns the config etag.
*/
public String getConfigETag()
{
return _configETag;
}
/**
* Returns the config etag.
*/
public void setConfigETag(String etag)
{
_configETag = etag;
}
/**
* Returns the admin.
*/
public HostMXBean getAdmin()
{
return _controller.getAdmin();
}
/**
* Initialization before configuration
*/
public void preConfigInit()
{
}
@Override
public void init()
{
}
/**
* Starts the host.
*/
@Override
public void start()
{
if (! _lifecycle.toStarting())
return;
if (getURL().equals("") && _parent != null) {
_url = _parent.getURL();
}
EnvironmentClassLoader loader = _classLoader;
// server/1al2
// loader.setId("host:" + getURL());
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(loader);
initBam();
// ioc/04010
// loader needs to start first, so Host managed beans will be
// initialized before the webappd
loader.start();
getWebAppContainer().start();
if (_parent != null)
_parent.clearCache();
} finally {
thread.setContextClassLoader(oldLoader);
}
_lifecycle.toActive();
}
private void initBam()
{
if (Resin.getCurrent() == null)
return;
String hostName = _hostName;
if ("".equals(hostName)) {
try {
hostName = InetAddress.getLocalHost().getCanonicalHostName();
} catch (Exception e) {
throw ConfigException.create(e);
}
}
HempBrokerManager brokerManager = HempBrokerManager.getCurrent();
_bamBroker = new HempBroker(brokerManager, hostName);
if (brokerManager != null)
brokerManager.addBroker(hostName, _bamBroker);
for (String alias : _aliasList) {
_bamBroker.addAlias(alias);
if (brokerManager != null)
brokerManager.addBroker(alias, _bamBroker);
}
InjectManager cdiManager = InjectManager.getCurrent();
cdiManager.addBean(cdiManager.createBeanFactory(ManagedBroker.class)
.name("bamBroker").singleton(_bamBroker));
// webBeans.addExtension(_bamBroker);
// XXX: webBeans.addRegistrationListener(new BamRegisterListener());
}
/**
* Clears the cache
*/
public void clearCache()
{
_parent.clearCache();
_webAppContainer.clearCache();
setConfigETag(null);
}
/**
* Builds the invocation for the host.
*/
@Override
public Invocation buildInvocation(Invocation invocation)
throws ConfigException
{
invocation.setHostName(_serverName);
invocation.setPort(_serverPort);
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(getClassLoader());
if (_configException == null)
return _webAppContainer.buildInvocation(invocation);
else {
invocation.setFilterChain(new ExceptionFilterChain(_configException));
invocation.setDependency(AlwaysModified.create());
return invocation;
}
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns true if the host is modified.
*/
@Override
public boolean isModified()
{
return (_lifecycle.isDestroyed() || _classLoader.isModified());
}
/**
* Returns true if the host is modified.
*/
public boolean isModifiedNow()
{
return (_lifecycle.isDestroyed() || _classLoader.isModifiedNow());
}
/**
* Log the reason for modification.
*/
@Override
public boolean logModified(Logger log)
{
if (_lifecycle.isDestroyed()) {
log.finer(this + " is destroyed");
return true;
}
else
return _classLoader.logModified(log);
}
/**
* Returns true if the host deploy was an error
*/
public boolean isDeployError()
{
return _configException != null;
}
/**
* Returns true if the host is idle
*/
@Override
public boolean isDeployIdle()
{
return false;
}
/**
* Stops the host.
*/
public boolean stop()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
EnvironmentClassLoader envLoader = _classLoader;
thread.setContextClassLoader(envLoader);
if (! _lifecycle.toStopping())
return false;
_webAppContainer.stop();
if (_bamBroker != null)
_bamBroker.close();
envLoader.stop();
return true;
} finally {
_lifecycle.toStop();
thread.setContextClassLoader(oldLoader);
}
}
/**
* Closes the host.
*/
@Override
public void destroy()
{
stop();
if (! _lifecycle.toDestroy())
return;
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
EnvironmentClassLoader classLoader = _classLoader;
thread.setContextClassLoader(classLoader);
try {
_webAppContainer.destroy();
} finally {
thread.setContextClassLoader(oldLoader);
classLoader.destroy();
}
}
/**
* @param shortHost
* @return
*/
public static String calculateCanonicalIPv6(String host)
{
try {
InetAddress addr = InetAddress.getByName(host);
return "[" + addr.getHostAddress() + "]";
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
return host;
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + getId() + "]";
}
}