/*
* Copyright (c) 1998-2010 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 com.caucho.config.Config;
import com.caucho.config.ConfigException;
import com.caucho.config.SchemaBean;
import com.caucho.config.cfg.BeansConfig;
import com.caucho.config.inject.BeanFactory;
import com.caucho.config.inject.InjectManager;
import com.caucho.ejb.EJBServer;
import com.caucho.env.jpa.ListenerPersistenceEnvironment;
import com.caucho.java.WorkDir;
import com.caucho.loader.CompilingLoader;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentBean;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.server.webbeans.ResinWebBeansProducer;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Set;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.context.spi.Context;
import javax.enterprise.inject.spi.Bean;
/**
* Embeddable Resin context for unit testing of
* application modules in the correct environment but
* without the overhead of the Resin server.
*
* <code><pre>
* static void main(String []args)
* {
* ResinBeanContainer beans = new ResinBeanContainer();
*
* // optional resin configuration file
* // beans.addBeansXml("resin-context.xml");
*
* beans.addModule("test.jar");
* beans.start();
*
* RequestContext req = beans.beginRequest();
* try {
* MyMain main = beans.getInstance(MyMain.class);
*
* main.main(args);
* } finally {
* req.close();
* }
*
* beans.close();
* }
* </pre></code>
*
* <h2>Configuration File</h2>
*
* The optional configuration file for the ResinContext allows the same
* environment and bean configuration as the resin-web.xml, but without the
* servlet-specific configuration.
*
* <pre><code>
* <beans xmlns="http://caucho.com/ns/resin"
* xmlns:resin="urn:java:com.caucho.resin">
*
* <resin:import path="${__DIR__}/my-include.xml"/>
*
* <database name="my-database">
* <driver ...>
* ...
* </driver>
* </database>
*
* <mypkg:MyBean xmlns:mypkg="urn:java:com.mycom.mypkg">
* <my-property>my-data</my-property>
* </mypkg:MyBean>
* </beans>
* </code></pre>
*/
public class ResinBeanContainer
{
private static final L10N L = new L10N(ResinBeanContainer.class);
private static final String SCHEMA = "com/caucho/resin/resin-context.rnc";
private EnvironmentClassLoader _classLoader;
private InjectManager _injectManager;
private ThreadLocal<RequestContext> _localContext
= new ThreadLocal<RequestContext>();
/**
* Creates a new ResinContext.
*/
public ResinBeanContainer()
{
_classLoader = EnvironmentClassLoader.create("resin-context");
_injectManager = InjectManager.create(_classLoader);
_injectManager.addContext(new RequestScope());
_injectManager.addManagedBean(_injectManager.createManagedBean(ResinWebBeansProducer.class));
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Environment.init();
EJBServer ejbServer = new EJBServer();
ejbServer.init();
Environment.addChildLoaderListener(new ListenerPersistenceEnvironment());
_classLoader.scanRoot();
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Adds a new module (jar or classes directory)
*/
public void addModule(String modulePath)
{
Path path = Vfs.lookup(modulePath);
if (modulePath.endsWith(".jar")) {
_classLoader.addJar(path);
}
else {
CompilingLoader loader = new CompilingLoader(_classLoader);
loader.setPath(path);
loader.init();
}
}
/**
* Adds a Resin beans configuration file, allowing creation of
* databases, or bean configuration.
*
* @param pathName URL/path to the configuration file
*/
public void addBeansXml(String pathName)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Path path = Vfs.lookup(pathName);
ContextConfig context = new ContextConfig(_injectManager, path);
Config config = new Config();
config.configure(context, path, SCHEMA);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Sets the work directory for Resin to use when generating temporary
* files.
*/
public void setWorkDirectory(String path)
{
WorkDir.setLocalWorkDir(Vfs.lookup(path));
}
/**
* Initializes the context.
*/
public void start()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
_classLoader.start();
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns a new instance of the given type with optional bindings. If
* the type is a managed bean, it will be injected before returning.
*
* @param className the className of the bean to instantiate
* @param qualifier optional @Qualifier annotations to select the bean
*/
public Object getInstance(String className, Annotation ...qualifiers)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Class<?> cl = Class.forName(className, false, _classLoader);
return getInstance(cl, qualifiers);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns a new instance of the given type with optional qualifiers.
*/
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> type, Annotation ...qualifiers)
{
if (type == null)
throw new NullPointerException();
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Set<Bean<?>> beans = _injectManager.getBeans(type, qualifiers);
if (beans.size() > 0) {
Bean<?> bean = _injectManager.resolve(beans);
return (T) _injectManager.getReference(bean);
}
return type.newInstance();
} catch (InstantiationException e) {
// XXX: proper exception
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// XXX: proper exception
throw new RuntimeException(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns an instance of the bean with the given name.
* If the type is a managed bean, it will be injected before returning.
*
* @param name the @Named of the bean to instantiate
*/
public Object getBeanByName(String name)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Set<Bean<?>> beans = _injectManager.getBeans(name);
if (beans.size() > 0) {
Bean<?> bean = _injectManager.resolve(beans);
return _injectManager.getReference(bean);
}
return null;
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Enters the Resin context and begins a new request on the thread. The
* the returned context must be passed to the completeRequest. To ensure
* the request is properly closed, use the following pattern:
*
* <code><pre>
* ResinContext resinContext = ...;
*
* RequestContext cxt = resinContext.beginRequest();
*
* try {
* // ... actions inside the Resin request context
* } finally {
* resinContext.completeRequest(cxt);
* }
* </pre></code>
*
* @return the RequestContext which must be passed to
* <code>completeContext</code>
*/
public RequestContext beginRequest()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
RequestContext oldContext = _localContext.get();
RequestContext context = new RequestContext(this, oldLoader, oldContext);
thread.setContextClassLoader(_classLoader);
_localContext.set(context);
return context;
}
/**
* Completes the thread's request and exits the Resin context.
*/
void completeRequest(RequestContext context)
{
Thread thread = Thread.currentThread();
thread.setContextClassLoader(context.getOldClassLoader());
_localContext.set(context.getOldContext());
}
/**
* Shuts the context down.
*/
public void close()
{
EnvironmentClassLoader loader = _classLoader;
_classLoader = null;
if (loader != null) {
loader.destroy();
}
}
public String toString()
{
return getClass().getName() + "[]";
}
class ContextConfig extends BeansConfig implements EnvironmentBean {
ContextConfig(InjectManager manager, Path root)
{
super(manager, root);
}
public ClassLoader getClassLoader()
{
return _classLoader;
}
public SystemContext createSystem()
{
return new SystemContext();
}
}
public class SystemContext implements EnvironmentBean {
public ClassLoader getClassLoader()
{
return ClassLoader.getSystemClassLoader();
}
}
class RequestScope implements Context {
@Override
public <T> T get(Contextual<T> bean)
{
RequestContext cxt = _localContext.get();
if (cxt == null)
throw new IllegalStateException(L.l("No RequestScope is active"));
return cxt.get(bean);
}
@Override
public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
{
RequestContext cxt = _localContext.get();
if (cxt == null)
throw new IllegalStateException(L.l("No RequestScope is active"));
return cxt.get(bean, creationalContext, _injectManager);
}
@Override
public Class<? extends Annotation> getScope()
{
return RequestScoped.class;
}
@Override
public boolean isActive()
{
return _localContext.get() != null;
}
}
}