/*
* 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.resin;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.logging.LogManager;
import javax.enterprise.context.spi.CreationalContext;
import com.caucho.cloud.topology.CloudCluster;
import com.caucho.cloud.topology.CloudServer;
import com.caucho.cloud.topology.CloudSystem;
import com.caucho.cloud.topology.TopologyService;
import com.caucho.config.ConfigException;
import com.caucho.config.program.ConfigProgram;
import com.caucho.config.types.RawString;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.loader.Environment;
import com.caucho.network.listen.StreamSocketLink;
import com.caucho.server.cluster.Server;
import com.caucho.server.host.Host;
import com.caucho.server.host.HostConfig;
import com.caucho.server.http.HttpRequest;
import com.caucho.server.resin.Resin;
import com.caucho.server.webapp.*;
import com.caucho.util.L10N;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.VfsStream;
import com.caucho.vfs.WriteStream;
/**
* Embeddable version of the Resin server.
*
* <code><pre>
* ResinEmbed resin = new ResinEmbed();
*
* HttpEmbed http = new HttpEmbed(8080);
* resin.addPort(http);
*
* WebAppEmbed webApp = new WebAppEmbed("/foo", "/home/ferg/ws/foo");
*
* resin.addWebApp(webApp);
*
* resin.start();
*
* resin.join();
* </pre></code>
*/
public class ResinEmbed
{
private static final L10N L = new L10N(ResinEmbed.class);
private static final String EMBED_CONF
= "classpath:com/caucho/resin/resin-embed.xml";
private final Resin _resin;
private String _configFile = EMBED_CONF;
private CloudCluster _cluster;
private Host _host;
private Server _server;
private String _serverHeader;
private final ArrayList<BeanEmbed> _beanList
= new ArrayList<BeanEmbed>();
private final ArrayList<WebAppEmbed> _webAppList
= new ArrayList<WebAppEmbed>();
private final ArrayList<PortEmbed> _portList
= new ArrayList<PortEmbed>();
private Lifecycle _lifecycle = new Lifecycle();
private boolean _isConfig;
private boolean _isDevelopmentMode;
/**
* Creates a new resin server.
*/
public ResinEmbed()
{
_resin = Resin.create("embed");
_resin.setEmbedded(true);
_resin.setRootDirectory(Vfs.lookup());
}
/**
* Creates a new resin server.
*/
public ResinEmbed(String configFile)
{
this();
setConfig(configFile);
}
//
// Configuration/Injection methods
//
/**
* Sets the root directory
*/
public void setRootDirectory(String rootUrl)
{
_resin.setRootDirectory(Vfs.lookup(rootUrl));
}
/**
* Sets the config file
*/
public void setConfig(String configFile)
{
_configFile = configFile;
}
/**
* Adds a port to the server, e.g. a HTTP port.
*
* @param port the embedded port to add to the server
*/
public void addPort(PortEmbed port)
{
_portList.add(port);
if (_server != null)
port.bindTo(_server);
/*
// server/1e00
if (_clusterServer == null)
initConfig(_configFile);
// XXX: port.bindTo(_clusterServer);
*/
}
/**
* Sets a list of ports.
*/
public void setPorts(PortEmbed []ports)
{
for (PortEmbed port : ports)
addPort(port);
}
/**
* Sets the server header
*/
public void setServerHeader(String serverName)
{
_serverHeader = serverName;
}
/**
* Adds a web-app to the server.
*/
public void addWebApp(WebAppEmbed webApplication)
{
if (webApplication == null) {
throw new NullPointerException();
}
_webAppList.add(webApplication);
// If the server has already been started, the web application will need to
// be explicitly deployed.
if (_lifecycle.isActive()) {
deployWebApplication(webApplication);
}
}
public void removeWebApp(WebAppEmbed webApplication)
{
if (webApplication == null) {
throw new NullPointerException();
}
_webAppList.remove(webApplication);
// If the server has already been started, the web application will need
// to be undeployed.
if (_lifecycle.isActive()) {
undeployWebApplication(webApplication);
}
}
/**
* Sets a list of webapps
*/
public void setWebApps(WebAppEmbed []webApps)
{
for (WebAppEmbed webApp : webApps)
addWebApp(webApp);
}
/**
* Adds a web bean.
*/
public void addBean(BeanEmbed bean)
{
_beanList.add(bean);
if (_lifecycle.isActive()) {
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_server.getClassLoader());
bean.configure();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw ConfigException.create(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
}
public void setDevelopmentMode(boolean isDevelopment)
{
_isDevelopmentMode = isDevelopment;
}
/**
* Initialize the Resin environment
*/
public void initializeEnvironment()
{
Environment.initializeEnvironment();
}
/**
* Set log handler
*/
public void resetLogManager()
{
LogManager.getLogManager().reset();
}
public void addScanRoot() {
_resin.getClassLoader().addScanRoot();
}
//
// Lifecycle
//
/**
* Starts the embedded server
*/
public void start()
{
if (! _lifecycle.toActive())
return;
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
Environment.initializeEnvironment();
_resin.preConfigureInit();
thread.setContextClassLoader(_resin.getClassLoader());
initConfig(_configFile);
_server = _resin.createServer();
thread.setContextClassLoader(_server.getClassLoader());
if (_serverHeader != null)
_server.setServerHeader(_serverHeader);
_server.setDevelopmentModeErrorPage(_isDevelopmentMode);
for (BeanEmbed beanEmbed : _beanList) {
beanEmbed.configure();
}
for (PortEmbed port : _portList) {
port.bindTo(_server);
}
_resin.start();
_host = _server.getHost("", 0);
if (_host == null) {
HostConfig hostConfig = new HostConfig();
_server.addHost(hostConfig);
_host = _server.getHost("", 0);
}
if (_host == null) {
throw new ConfigException(L.l("ResinEmbed requires a <host> to be configured in the resin.xml, because the webapps must belong to a host."));
}
for (WebAppEmbed webApplication : _webAppList) {
deployWebApplication(webApplication);
}
} catch (Exception e) {
throw ConfigException.create(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Stops the embedded server
*/
public void stop()
{
if (! _lifecycle.toStop())
return;
try {
_resin.stop();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw ConfigException.create(e);
}
}
/**
* Waits for the Resin process to exit.
*/
public void join()
{
while (! _resin.isClosed()) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
/**
* Destroys the embedded server
*/
public void destroy()
{
if (! _lifecycle.toDestroy())
return;
try {
_resin.close();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw ConfigException.create(e);
}
}
//
// Testing API
//
/**
* Sends a HTTP request to the embedded server for testing.
*
* @param is input stream containing the HTTP request
* @param os output stream to receive the request
*/
public void request(InputStream is, OutputStream os)
throws IOException
{
start();
TestConnection conn = createConnection();
conn.request(is, os);
}
/**
* Sends a HTTP request to the embedded server for testing.
*
* @param httpRequest HTTP request string, e.g. "GET /test.jsp"
* @param os output stream to receive the request
*/
public void request(String httpRequest, OutputStream os)
throws IOException
{
start();
TestConnection conn = createConnection();
conn.request(httpRequest, os);
}
/**
* Sends a HTTP request to the embedded server for testing.
*
* @param httpRequest HTTP request string, e.g. "GET /test.jsp"
*
* @return the HTTP result string
*/
public String request(String httpRequest)
throws IOException
{
start();
TestConnection conn = createConnection();
return conn.request(httpRequest);
}
/**
* Creates a test connection to the server
*/
private TestConnection createConnection()
{
TestConnection conn = new TestConnection();
return conn;
}
//
// utilities
//
private void initConfig(String configFile)
{
try {
if (_isConfig)
return;
_isConfig = true;
_resin.configureFile(Vfs.lookup(configFile));
} catch (Exception e) {
throw ConfigException.create(e);
}
CloudSystem cloudSystem = TopologyService.getCurrent().getSystem();
if (cloudSystem.getClusterList().length == 0)
throw new ConfigException(L.l("Resin needs at least one defined <cluster>"));
String clusterId = cloudSystem.getClusterList()[0].getId();
_cluster = cloudSystem.findCluster(clusterId);
if (_cluster.getPodList().length == 0)
throw new ConfigException(L.l("Resin needs at least one defined <server> or <cluster-pod>"));
if (_cluster.getPodList()[0].getServerList().length == 0)
throw new ConfigException(L.l("Resin needs at least one defined <server>"));
CloudServer cloudServer = _cluster.getPodList()[0].getServerList()[0];
// _clusterServer = cloudServer.getData(ClusterServer.class);
if (cloudServer != null)
_resin.setServerId(cloudServer.getId());
}
private void deployWebApplication(WebAppEmbed webApplication)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
// Web applications need to be loaded under the host class-loader.
thread.setContextClassLoader(_host.getClassLoader());
WebAppConfig configuration = new WebAppConfig();
configuration.setContextPath(webApplication.getContextPath());
configuration.setRootDirectory(new RawString(webApplication
.getRootDirectory()));
if (webApplication.getArchivePath() != null) {
configuration.setArchivePath(new RawString(webApplication
.getArchivePath()));
}
configuration.addBuilderProgram(new WebAppProgram(webApplication));
//_host.getWebAppContainer().addWebApp(configuration);
WebAppContainer container = _host.getWebAppContainer();
WebAppSingleDeployGenerator deployGenerator = container.createDeployGenerator(configuration);
webApplication.setDeployGenerator(deployGenerator);
container.addWebApp(deployGenerator);
} catch (Exception e) {
throw ConfigException.create(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
private void undeployWebApplication(WebAppEmbed webApplication)
{
//webApplication.getWebApp().getController().destroy();
//webApplication.getWebApp().destroy();
_host.getWebAppContainer().removeWebApp(webApplication.getDeployGenerator());
}
protected void finalize()
throws Throwable
{
super.finalize();
destroy();
}
/**
* Basic embedding server.
*/
public static void main(String []args)
throws Exception
{
ResinEmbed resin = new ResinEmbed();
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("--port=")) {
int port = Integer.parseInt(args[i].substring("--port=".length()));
HttpEmbed http = new HttpEmbed(port);
resin.addPort(http);
}
else if (args[i].startsWith("--config=")) {
String config = args[i].substring("--config=".length());
resin.setConfig(config);
}
else if (args[i].startsWith("--deploy:")) {
String valueString = args[i].substring("--deploy:".length());
String []values = valueString.split("[=,]");
String role = null;
for (int j = 0; j < values.length; j += 2) {
if (values[j].equals("role"))
role = values[j + 1];
}
WebAppLocalDeployEmbed webApp = new WebAppLocalDeployEmbed();
if (role != null)
webApp.setRole(role);
resin.addWebApp(webApp);
}
}
resin.resetLogManager();
resin.start();
resin.join();
}
/**
* Test HTTP connection
*/
private class TestConnection {
StreamSocketLink _conn;
HttpRequest _request;
VfsStream _vfsStream;
InetAddress _localAddress;
InetAddress _remoteAddress;
int _port = 6666;
char []_chars = new char[1024];
byte []_bytes = new byte[1024];
TestConnection()
{
_conn = new StreamSocketLink();
// _conn.setVirtualHost(_virtualHost);
_request = new HttpRequest(_resin.getServer(), _conn);
_request.init();
_vfsStream = new VfsStream(null, null);
// _conn.setSecure(_isSecure);
try {
_localAddress = InetAddress.getByName("127.0.0.1");
_remoteAddress = InetAddress.getByName("127.0.0.1");
} catch (IOException e) {
}
}
public String request(String input) throws IOException
{
OutputStream os = new ByteArrayOutputStream();
request(input, os);
return os.toString();
}
public void request(String input, OutputStream os)
throws IOException
{
ByteArrayInputStream is;
int len = input.length();
if (_chars.length < len) {
_chars = new char[len];
_bytes = new byte[len];
}
input.getChars(0, len, _chars, 0);
for (int i = 0; i < len; i++)
_bytes[i] = (byte) _chars[i];
is = new ByteArrayInputStream(_bytes, 0, len);
request(is, os);
}
public void request(InputStream is, OutputStream os)
throws IOException
{
Thread.yield();
WriteStream out = Vfs.openWrite(os);
out.setDisableClose(true);
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
try {
_vfsStream.init(is, os);
_conn.setStream(is, os);
_conn.setLocalAddress(_localAddress);
_conn.setLocalPort(_port);
_conn.setRemoteAddress(_remoteAddress);
_conn.setRemotePort(9666);
// _conn.setSecure(_isSecure);
try {
Thread.sleep(10);
} catch (Exception e) {
}
while (_request.handleRequest()) {
out.flush();
}
} catch (EOFException e) {
} finally {
out.flush();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
}
static class WebAppProgram extends ConfigProgram {
private final WebAppEmbed _config;
WebAppProgram(WebAppEmbed webAppConfig)
{
_config = webAppConfig;
}
/**
* Configures the object.
*/
@Override
public <T> void inject(T bean, CreationalContext<T> env)
throws ConfigException
{
_config.configure((WebApp) bean);
}
}
}