blob: 28b5728bd8edb5864f272fb1f69446e36690d7ad [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.context;
import org.apache.openejb.OpenEJB;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.api.LocalClient;
import org.apache.openejb.junit.ContextConfig;
import org.apache.openejb.junit.Property;
import org.apache.openejb.junit.RunTestAs;
import org.apache.openejb.junit.TestResource;
import org.apache.openejb.junit.TestResourceTypes;
import org.apache.openejb.loader.SystemInstance;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLDecoder;
import java.util.Properties;
/**
* To implement your own context, you need to create an implementation of TestContext
* which would configure the test when instructed. You can then use whatever method
* of configuration you choose.
*/
public class OpenEjbTestContext implements TestContext {
protected static final String REALM_PROPERTY_KEY = "openejb.authentication.realmName";
protected static final String LOGIN_CONFIG_RESOURCE = "/META-INF/openejb-test-login.config";
protected static final String DEFAULT_CONFIG_FILE_RESOURCE = "/META-INF/default-openejb-test-config.properties";
/**
* Properties object used to initialize InitialContext
*/
protected Properties contextConfig;
/**
* Context's InitialContext
*/
private InitialContext initialContext;
/**
* Test class
*/
private final Class<?> clazz;
/**
* Method being run on test
*/
private Method method;
/**
* Security role to execute as
*/
private String securityRole;
/**
* Constructs a context for a class
*
* @param clazz
*/
public OpenEjbTestContext(final Class clazz) {
this(clazz, null);
}
/**
* Constructs a context for a method
*
* @param method
*/
public OpenEjbTestContext(final Method method) {
this(method, null);
}
/**
* Constructs a context for a class
*
* @param clazz
* @param securityRole
*/
public OpenEjbTestContext(final Class clazz, final String securityRole) {
this.clazz = clazz;
this.securityRole = securityRole;
}
/**
* Constructs a context for a method
*
* @param method
* @param securityRole
*/
public OpenEjbTestContext(final Method method, final String securityRole) {
this.clazz = method.getDeclaringClass();
this.method = method;
this.securityRole = securityRole;
}
public void configureTest(final Object testObj) {
try {
if (testObj.getClass().isAnnotationPresent(LocalClient.class)) {
getInitialContext().bind("inject", testObj);
}
// perform custom injections
performInjections(testObj);
} catch (final IOException e) {
throw new OpenEJBRuntimeException("Failed to load configuration.", e);
} catch (final NamingException e) {
throw new OpenEJBRuntimeException("Failed to configure object.", e);
} catch (final Exception e) {
throw new OpenEJBRuntimeException("Unknown error trying to configure object.", e);
}
}
@Override
public void close() {
try {
if(initialContext != null) {
initialContext.close();
}
} catch (final NamingException e) {
// ignored
}
OpenEJB.destroy();
SystemInstance.reset();
}
/**
* Returns this context's InitialContext, creating it if necessary.
*
* @return InitialContext for this TestContext
* @throws NamingException
*/
protected InitialContext getInitialContext() throws NamingException {
if (initialContext == null) {
// set the property for security realm RIGHT before we load the InitialContext
final String loginConfig = OpenEjbTestContext.class.getResource(LOGIN_CONFIG_RESOURCE).toExternalForm();
System.setProperty("java.security.auth.login.config", URLDecoder.decode(loginConfig));
try {
final Properties config = getContextConfig();
initialContext = new InitialContext(config);
} catch (final IOException e) {
throw new NamingException("Failed to load initial context configuration: " + e.getMessage());
}
}
return initialContext;
}
/**
* Constructs the configuration needed to create the InitialContext. This will
* be determined from the class/method supplied to the constructor.
*
* @return
*/
protected Properties getContextConfig() throws IOException {
if (contextConfig != null) {
return contextConfig;
}
final Properties env = new Properties();
boolean loadedConfig = false;
if (clazz.isAnnotationPresent(ContextConfig.class)) {
loadedConfig |= loadConfig(env, clazz.getAnnotation(ContextConfig.class));
}
if (method != null && method.isAnnotationPresent(ContextConfig.class)) {
loadedConfig |= loadConfig(env, method.getAnnotation(ContextConfig.class));
}
// no properties loaded, use the "default" configuration
if (!loadedConfig) {
final InputStream in = OpenEjbTestContext.class.getResourceAsStream(DEFAULT_CONFIG_FILE_RESOURCE);
if (in == null) {
throw new FileNotFoundException("Default configuration file not found. Specify configuration " +
"properties to initialize OpenEJB using @ContextConfig.");
}
env.load(in);
// if it's still empty, something bad has happened, and OpenEJB won't initialize. Complain.
if (env.size() == 0) {
throw new IOException("Context configuration failed to load, so OpenEJB won't load either. Specify configuration " +
"properties for initializing OpenEJB using @ContextConfig.");
}
}
configureSecurity(env);
contextConfig = env;
return env;
}
/**
* Interprets and loads InitialContext properties from the ContextConfig annotation
*
* @param env
* @param contextConfig
* @return true if any properties were loaded
*/
protected boolean loadConfig(final Properties env, final ContextConfig contextConfig) throws IOException {
boolean loadedConfig = false;
loadedConfig = loadConfigFile(env, contextConfig);
loadedConfig |= loadConfigProperties(env, contextConfig);
return loadedConfig;
}
/**
* Loads the direct properties from the annotation configuration into the given Properties object
*
* @param env
* @param contextConfig
* @return true if any properties were loaded
*/
protected boolean loadConfigProperties(final Properties env, final ContextConfig contextConfig) {
boolean loadedConfig = false;
if (contextConfig.properties().length > 0) {
for (final Property p : contextConfig.properties()) {
if (p.value() != null) {
loadedConfig = true;
Util.addProperty(env, p.value());
}
}
}
return loadedConfig;
}
/**
* Loads the configuration file specified in the {@link org.apache.openejb.junit.ContextConfig} annotation
* into the specified Properties instance
*
* @param env
* @param contextConfig
* @return true if any properties were loaded
*/
protected boolean loadConfigFile(final Properties env, final ContextConfig contextConfig)
throws IOException {
// properties file
if (contextConfig.configFile().length() > 0) {
final InputStream in = clazz.getResourceAsStream(contextConfig.configFile());
if (in == null) {
throw new FileNotFoundException("Cannot find resource '" + contextConfig.configFile() + "' in classpath: " + clazz.getName());
}
env.load(in);
return env.size() > 0;
}
return false;
}
/**
* Loads the security configuration into the given Properties object
*
* @param env
*/
protected void configureSecurity(final Properties env) {
// if a securityRole isn't already configured, use the RunTestAs annotation if available
if (securityRole == null) {
if (method != null && method.isAnnotationPresent(RunTestAs.class)) {
securityRole = method.getAnnotation(RunTestAs.class).value();
} else if (clazz.isAnnotationPresent(RunTestAs.class)) {
securityRole = clazz.getAnnotation(RunTestAs.class).value();
}
}
if (securityRole != null) {
env.put(REALM_PROPERTY_KEY, "OpenEjbJunitSecurityRealm");
env.put(InitialContext.SECURITY_PRINCIPAL, securityRole);
env.put(InitialContext.SECURITY_CREDENTIALS, "[no-password-needed]");
}
}
/**
* Performs any non-OpenEJB type injections on the test object. It will "prefer"
* a setter, and therefore I made it work on private fields as well. So if you
* are injecting to a private field and wish to have some control over it, create
* a setter according to the JavaBeans idioms.
*
* If the setter isn't found OR it fails, then an attempt will be made to set
* it directly, and a message will be printed when it fails.
*/
private void performInjections(final Object testObj) throws Exception {
for (final Field field : clazz.getDeclaredFields()) {
final Object injectValue = getInjectionValue(field);
// no value determined, try next field
if (injectValue == null) {
continue;
}
// now inject it through the setter
try {
final Method setterMethod = Util.findSetter(clazz, field, injectValue);
if (setterMethod != null) {
setterMethod.invoke(testObj, injectValue);
continue;
}
} catch (final Exception e) {
System.err.println("Failed to perform setter injection on: " + clazz.getCanonicalName() + "." + field.getName());
e.printStackTrace();
}
// do direct injection
try {
if (!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
field.set(testObj, injectValue);
} catch (final Exception e) {
throw new OpenEJBRuntimeException("Failed to inject on: " + clazz.getCanonicalName() + "." + field.getName(), e);
}
}
}
/**
* Analyzes the field and returns any values which should be injected on it
*
* @param field
* @return reference to value to inject, or null if nothing should be injected
*/
protected Object getInjectionValue(final Field field) throws Exception {
// determine the value to inject
if (field.isAnnotationPresent(TestResource.class)) {
final TestResource resourceConfig = field.getAnnotation(TestResource.class);
final String resourceType = resourceConfig.value();
if (resourceType == null) {
throw new IllegalArgumentException("Null TestResource type '" + resourceType +
"' on field: " + clazz.getCanonicalName() + "." + field.getName());
} else {
if (TestResourceTypes.CONTEXT_CONFIG.equals(resourceType)) {
return getContextConfig();
} else if (TestResourceTypes.INITIALCONTEXT.equals(resourceType)) {
return getInitialContext();
} else {
return getOtherTestResource(resourceConfig);
}
}
}
return null;
}
/**
* Override to perform custom resource types injection. This method will be called
* when whatever value was specified in the {@link TestResource} annotation wasn't
* understood by the {@link #performInjections(java.lang.Object) } method. This
* method will be called, supplying the annotation, and you can then interpret and
* create the value to be injected. By default this method just returns null.
*
* You can use this to inject values into annotated fields which contain custom
* values in their names.
*
* @param resourceConfig
* @return instance to inject into annotated field.
*/
protected Object getOtherTestResource(final TestResource resourceConfig) {
return null;
}
/**
* @return the test class
*/
protected Class<?> getTestClass() {
return clazz;
}
/**
* @return the test method for which this context was created
*/
protected Method getTestMethod() {
return method;
}
}