blob: 3fdaebabc96d1a5c20ac6e1ac2db31601b604fd9 [file] [log] [blame]
/*
* 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.openejb.junit;
import org.apache.openejb.AppContext;
import org.apache.openejb.BeanContext;
import org.apache.openejb.InjectionProcessor;
import org.apache.openejb.assembler.classic.AppInfo;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.cdi.OWBInjector;
import org.apache.openejb.config.AppModule;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.ConnectorModule;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.config.PersistenceModule;
import org.apache.openejb.core.Operation;
import org.apache.openejb.core.ThreadContext;
import org.apache.openejb.core.ivm.naming.InitContextFactory;
import org.apache.openejb.jee.Application;
import org.apache.openejb.jee.Beans;
import org.apache.openejb.jee.Connector;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.EnterpriseBean;
import org.apache.openejb.jee.ManagedBean;
import org.apache.openejb.jee.NamedModule;
import org.apache.openejb.jee.TransactionType;
import org.apache.openejb.jee.jpa.unit.Persistence;
import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
import org.apache.openejb.jee.oejb3.EjbDeployment;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.Join;
import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.junit.rules.MethodRule;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import static org.apache.openejb.config.DeploymentsResolver.DEPLOYMENTS_CLASSPATH_PROPERTY;
/**
* @version $Rev$ $Date$
*/
public class ApplicationComposer extends BlockJUnit4ClassRunner {
private final TestClass testClass;
public ApplicationComposer(Class<?> klass) throws InitializationError {
super(klass);
testClass = new TestClass(klass);
validate();
}
private void validate() throws InitializationError {
List<Throwable> errors = new ArrayList<Throwable>();
final List<FrameworkMethod> configs = testClass.getAnnotatedMethods(Configuration.class);
if (configs.size() > 1) {
final String gripe = "Test class should have no more than one @Configuration method";
errors.add(new Exception(gripe));
}
for (FrameworkMethod method : configs) {
final Class<?> type = method.getMethod().getReturnType();
if (!Properties.class.isAssignableFrom(type)) {
final String gripe = "@Configuration method must return " + Properties.class.getName();
errors.add(new Exception(gripe));
}
}
int appModules = 0;
int modules = 0;
Class[] moduleTypes = {EjbJar.class, EnterpriseBean.class, Persistence.class, PersistenceUnit.class, Connector.class, Beans.class, Application.class, Class[].class};
for (FrameworkMethod method : testClass.getAnnotatedMethods(Module.class)) {
modules++;
final Class<?> type = method.getMethod().getReturnType();
if (Application.class.isAssignableFrom(type)) {
appModules++;
} else if (!isValidModuleType(type, moduleTypes)) {
final String gripe = "@Module method must return " + Join.join(" or ", moduleTypes).replaceAll("(class|interface) ", "");
errors.add(new Exception(gripe));
}
}
if (appModules > 1) {
final String gripe = "Test class should have no more than one @Module method that returns " + Application.class.getName();
errors.add(new Exception(gripe));
}
if (modules < 1) {
final String gripe = "Test class should have at least one @Module method";
errors.add(new Exception(gripe));
}
if (!errors.isEmpty()) {
throw new InitializationError(errors);
}
}
private boolean isValidModuleType(Class<?> type, Class[] moduleTypes) {
for (Class moduleType : moduleTypes) {
if (moduleType.isAssignableFrom(type)) return true;
}
return false;
}
@Override
protected List<MethodRule> rules(Object test) {
final List<MethodRule> rules = super.rules(test);
rules.add(new MethodRule(){
public Statement apply(Statement base, FrameworkMethod method, Object target) {
return new DeployApplication(target, base);
}
});
return rules;
}
public class DeployApplication extends Statement {
// The TestCase instance
private final Object testInstance;
private final Statement next;
public DeployApplication(Object testInstance, Statement next) {
this.testInstance = testInstance;
this.next = next;
}
@Override
public void evaluate() throws Throwable {
final Class<?> javaClass = testClass.getJavaClass();
final ClassLoader loader = javaClass.getClassLoader();
AppModule appModule = new AppModule(loader, javaClass.getSimpleName());
// Add the test case as an @ManagedBean
{
final EjbJar ejbJar = new EjbJar();
final OpenejbJar openejbJar = new OpenejbJar();
final ManagedBean bean = ejbJar.addEnterpriseBean(new ManagedBean(javaClass.getSimpleName(), javaClass.getName(), true));
bean.setTransactionType(TransactionType.BEAN);
final EjbDeployment ejbDeployment = openejbJar.addEjbDeployment(bean);
ejbDeployment.setDeploymentId(javaClass.getName());
appModule.getEjbModules().add(new EjbModule(ejbJar, openejbJar));
}
Application application = null;
// Invoke the @Module producer methods to build out the AppModule
for (FrameworkMethod method : testClass.getAnnotatedMethods(Module.class)) {
final Object obj = method.invokeExplosively(testInstance);
if (obj instanceof EjbJar) {
final EjbJar ejbJar = (EjbJar) obj;
setId(ejbJar, method);
appModule.getEjbModules().add(new EjbModule(ejbJar));
} else if (obj instanceof EnterpriseBean) {
final EnterpriseBean bean = (EnterpriseBean) obj;
final EjbJar ejbJar = new EjbJar(method.getName());
ejbJar.addEnterpriseBean(bean);
EjbModule ejbModule = new EjbModule(ejbJar);
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(bean.getEjbClass());
ejbModule.setFinder(new AnnotationFinder(new ClassesArchive(clazz)).link());
appModule.getEjbModules().add(ejbModule);
} else if (obj instanceof Application) {
application = (Application) obj;
setId(application, method);
} else if (obj instanceof Connector) {
final Connector connector = (Connector) obj;
setId(connector, method);
appModule.getConnectorModules().add(new ConnectorModule(connector));
} else if (obj instanceof Persistence) {
final Persistence persistence = (Persistence) obj;
appModule.getPersistenceModules().add(new PersistenceModule("", persistence));
} else if (obj instanceof PersistenceUnit) {
final PersistenceUnit unit = (PersistenceUnit) obj;
appModule.getPersistenceModules().add(new PersistenceModule("", new Persistence(unit)));
} else if (obj instanceof Beans) {
final Beans beans = (Beans) obj;
final EjbModule ejbModule = new EjbModule(new EjbJar(method.getName()));
ejbModule.setBeans(beans);
appModule.getEjbModules().add(ejbModule);
} else if (obj instanceof Class[]) {
final Class[] beans = (Class[]) obj;
final EjbModule ejbModule = new EjbModule(new EjbJar(method.getName()));
ejbModule.setFinder(new AnnotationFinder(new ClassesArchive(beans)).link());
ejbModule.setBeans(new Beans());
appModule.getEjbModules().add(ejbModule);
} else if (obj instanceof Class) {
final Class bean = (Class) obj;
final EjbModule ejbModule = new EjbModule(new EjbJar(method.getName()));
ejbModule.setFinder(new AnnotationFinder(new ClassesArchive(bean)).link());
ejbModule.setBeans(new Beans());
appModule.getEjbModules().add(ejbModule);
}
}
// Application is final in AppModule, which is fine, so we'll create a new one and move everything
if (application != null) {
final AppModule newModule = new AppModule(appModule.getClassLoader(), appModule.getModuleId(), application, false);
newModule.getClientModules().addAll(appModule.getClientModules());
newModule.getPersistenceModules().addAll(appModule.getPersistenceModules());
newModule.getEjbModules().addAll(appModule.getEjbModules());
newModule.getConnectorModules().addAll(appModule.getConnectorModules());
appModule = newModule;
}
// For the moment we just take the first @Configuration method
// maybe later we can add something fancy to allow multiple configurations using a qualifier
// as a sort of altDD/altConfig concept. Say for example the altDD prefix might be "foo",
// we can then imagine something like this:
// @Foo @Configuration public Properties alternateConfig(){...}
// @Foo @Module public Properties alternateModule(){...}
// anyway, one thing at a time ....
final Properties configuration = new Properties();
configuration.put(DEPLOYMENTS_CLASSPATH_PROPERTY, "false");
final List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Configuration.class);
for (FrameworkMethod method : methods) {
final Object o = method.invokeExplosively(testInstance);
if (o instanceof Properties) {
Properties properties = (Properties) o;
configuration.putAll(properties);
}
break;
}
if (SystemInstance.isInitialized()) SystemInstance.reset();
SystemInstance.init(configuration);
try {
ConfigurationFactory config = new ConfigurationFactory();
config.init(SystemInstance.get().getProperties());
Assembler assembler = new Assembler();
assembler.buildContainerSystem(config.getOpenEjbConfiguration());
final AppInfo appInfo = config.configureApplication(appModule);
final AppContext appContext = assembler.createApplication(appInfo);
try {
final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
final BeanContext context = containerSystem.getBeanContext(javaClass.getName());
ThreadContext callContext = new ThreadContext(context, null, Operation.INJECTION);
ThreadContext oldContext = ThreadContext.enter(callContext);
try {
final InjectionProcessor processor = new InjectionProcessor(testInstance, context.getInjections(), context.getJndiContext());
processor.createInstance();
try {
OWBInjector beanInjector = new OWBInjector(appContext.getWebBeansContext());
beanInjector.inject(testInstance);
} catch (Throwable t) {
// TODO handle this differently
// this is temporary till the injector can be rewritten
}
} finally {
ThreadContext.exit(oldContext);
}
System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, InitContextFactory.class.getName());
final ThreadContext previous = ThreadContext.enter(new ThreadContext(context, null, Operation.BUSINESS));
try {
next.evaluate();
} finally {
ThreadContext.exit(previous);
}
} finally {
assembler.destroyApplication(appInfo.path);
}
} finally {
SystemInstance.reset();
}
}
private <Module extends NamedModule> Module setId(Module module, FrameworkMethod method) {
return setId(module, method.getName());
}
private <Module extends NamedModule> Module setId(Module module, String name) {
if (module.getModuleName() != null) return module;
if (module.getId() != null) return module;
module.setId(name);
return module;
}
}
}