/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* Licensed 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.seasar.framework.unit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Servlet;
import junit.framework.TestCase;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.ExternalContext;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.deployer.ComponentDeployerFactory;
import org.seasar.framework.container.deployer.ExternalComponentDeployerProvider;
import org.seasar.framework.container.deployer.InstanceDefFactory;
import org.seasar.framework.container.external.servlet.HttpServletExternalContext;
import org.seasar.framework.container.external.servlet.HttpServletExternalContextComponentDefRegister;
import org.seasar.framework.container.factory.AnnotationHandler;
import org.seasar.framework.container.factory.AnnotationHandlerFactory;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.container.impl.S2ContainerBehavior;
import org.seasar.framework.container.servlet.S2ContainerServlet;
import org.seasar.framework.convention.NamingConvention;
import org.seasar.framework.convention.impl.NamingConventionImpl;
import org.seasar.framework.env.Env;
import org.seasar.framework.exception.NoSuchMethodRuntimeException;
import org.seasar.framework.message.MessageResourceBundleFactory;
import org.seasar.framework.mock.servlet.MockHttpServletRequest;
import org.seasar.framework.mock.servlet.MockHttpServletResponse;
import org.seasar.framework.mock.servlet.MockHttpServletResponseImpl;
import org.seasar.framework.mock.servlet.MockServletConfig;
import org.seasar.framework.mock.servlet.MockServletConfigImpl;
import org.seasar.framework.mock.servlet.MockServletContext;
import org.seasar.framework.mock.servlet.MockServletContextImpl;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.DisposableUtil;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.StringUtil;
/**
* Seasar2を使うテストを行なうための<code>TestCase</code>です。
*
* @author higa
*/
public abstract class S2FrameworkTestCase extends TestCase {
/**
* 環境が設定されているファイルのパスです。
*/
protected static final String ENV_PATH = "env_ut.txt";
/**
* 環境が設定されていない場合のデフォルト値です。
*/
protected static final String ENV_VALUE = "ut";
private S2Container container;
private Servlet servlet;
private MockServletConfig servletConfig;
private MockServletContext servletContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private ClassLoader originalClassLoader;
private UnitClassLoader unitClassLoader;
private NamingConvention namingConvention;
private List boundFields;
private boolean warmDeploy = true;
private boolean registerNamingConvention = true;
private AnnotationHandler annotationHandler = AnnotationHandlerFactory
.getAnnotationHandler();
/**
* {@link S2FrameworkTestCase}を作成します。
*/
public S2FrameworkTestCase() {
}
/**
* {@link S2FrameworkTestCase}を作成します。
*
* @param name
*/
public S2FrameworkTestCase(String name) {
super(name);
}
/**
* WARM deployかどうかを返します。
*
* @return WARM deployかどうか
*/
public boolean isWarmDeploy() {
return warmDeploy && !ResourceUtil.isExist("s2container.dicon")
&& ResourceUtil.isExist("convention.dicon")
&& ResourceUtil.isExist("creator.dicon")
&& ResourceUtil.isExist("customizer.dicon");
}
/**
* WARM deployかどうかを設定します。
*
* @param warmDeploy
*/
public void setWarmDeploy(boolean warmDeploy) {
this.warmDeploy = warmDeploy;
}
/**
* テスト用のS2コンテナを作成する際に{@link NamingConvention}を登録する場合は<code>true</code>を返します。
*
* @return テスト用のS2コンテナを作成する際に{@link NamingConvention}を登録する場合は<code>true</code>
*/
public boolean isRegisterNamingConvention() {
return registerNamingConvention;
}
/**
* テスト用のS2コンテナを作成する際に{@link NamingConvention}を登録する場合は<code>true</code>を設定します。
*
* @param registerNamingConvention
* テスト用のS2コンテナを作成する際に{@link NamingConvention}を登録する場合は<code>true</code>
*/
public void setRegisterNamingConvention(boolean registerNamingConvention) {
this.registerNamingConvention = registerNamingConvention;
}
/**
* {@link S2Container}を返します。
*
* @return {@link S2Container}
*/
public S2Container getContainer() {
return container;
}
/**
* コンポーネントを返します。
*
* @param componentName
* @return
*/
public Object getComponent(String componentName) {
return container.getComponent(componentName);
}
/**
* コンポーネントを返します。
*
* @param componentClass
* @return
*/
public Object getComponent(Class componentClass) {
return container.getComponent(componentClass);
}
/**
* {@link ComponentDef}を返します。
*
* @param componentName
* @return {@link ComponentDef}
*/
public ComponentDef getComponentDef(String componentName) {
return container.getComponentDef(componentName);
}
/**
* {@link ComponentDef}を返します。
*
* @param componentClass
* @return {@link ComponentDef}
*/
public ComponentDef getComponentDef(Class componentClass) {
return container.getComponentDef(componentClass);
}
/**
* コンポーネントを登録します。
*
* @param componentClass
* @see #register(Class, String)
*/
public void register(Class componentClass) {
if (namingConvention == null) {
register(componentClass, null);
} else {
register(componentClass, namingConvention
.fromClassNameToComponentName(componentClass.getName()));
}
}
/**
* コンポーネントを登録します。
*
* @param componentClass
* @param componentName
*/
public void register(Class componentClass, String componentName) {
ComponentDef cd = annotationHandler.createComponentDef(componentClass,
InstanceDefFactory.SINGLETON);
cd.setComponentName(componentName);
annotationHandler.appendDI(cd);
annotationHandler.appendAspect(cd);
annotationHandler.appendInterType(cd);
annotationHandler.appendInitMethod(cd);
annotationHandler.appendDestroyMethod(cd);
container.register(cd);
}
/**
* コンポーネントを登録します。
*
* @param component
*/
public void register(Object component) {
container.register(component);
}
/**
* コンポーネントを登録します。
*
* @param component
* @param componentName
*/
public void register(Object component, String componentName) {
container.register(component, componentName);
}
/**
* コンポーネントを登録します。
*
* @param componentDef
*/
public void register(ComponentDef componentDef) {
container.register(componentDef);
}
/**
* diconファイルをインクルードします。
*
* @param path
*/
public void include(String path) {
S2ContainerFactory.include(container, convertPath(path));
}
/**
* パスを変換します。
*
* @param path
* パス
* @return 変換後のパス
*/
protected String convertPath(String path) {
return ResourceUtil.convertPath(path, getClass());
}
/**
* @see junit.framework.TestCase#runBare()
*/
public void runBare() throws Throwable {
setUpContainer();
try {
setUp();
try {
setUpForEachTestMethod();
try {
container.init();
try {
setUpAfterContainerInit();
try {
bindFields();
try {
setUpAfterBindFields();
try {
doRunTest();
} finally {
tearDownBeforeUnbindFields();
}
} finally {
unbindFields();
}
} finally {
tearDownBeforeContainerDestroy();
}
} finally {
container.destroy();
}
} finally {
tearDownForEachTestMethod();
}
} finally {
tearDown();
}
} finally {
tearDownContainer();
}
}
/**
* ルートのdiconのパスを返します。
*
* @return ルートのdiconのパス
* @throws Throwable
*/
protected String getRootDicon() throws Throwable {
return null;
}
/**
* S2コンテナをセットアップします。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void setUpContainer() throws Throwable {
Env.setFilePath(ENV_PATH);
Env.setValueIfAbsent(ENV_VALUE);
originalClassLoader = getOriginalClassLoader();
unitClassLoader = new UnitClassLoader(originalClassLoader);
Thread.currentThread().setContextClassLoader(unitClassLoader);
if (isWarmDeploy()) {
S2ContainerFactory.configure("warmdeploy.dicon");
}
String rootDicon = resolveRootDicon();
container = StringUtil.isEmpty(rootDicon) ? S2ContainerFactory.create()
: S2ContainerFactory.create(rootDicon);
SingletonS2ContainerFactory.setContainer(container);
if (servletContext == null) {
servletContext = new MockServletContextImpl("s2-example");
}
request = servletContext.createRequest("/hello.html");
response = new MockHttpServletResponseImpl(request);
servletConfig = new MockServletConfigImpl();
servletConfig.setServletContext(servletContext);
servlet = new S2ContainerServlet();
servlet.init(servletConfig);
ExternalContext externalContext = new HttpServletExternalContext();
externalContext.setApplication(servletContext);
externalContext.setRequest(request);
externalContext.setResponse(response);
container.setExternalContext(externalContext);
container
.setExternalContextComponentDefRegister(new HttpServletExternalContextComponentDefRegister());
ComponentDeployerFactory
.setProvider(new ExternalComponentDeployerProvider());
if (!container.hasComponentDef(NamingConvention.class)
&& isRegisterNamingConvention()) {
namingConvention = new NamingConventionImpl();
container.register(namingConvention);
}
}
/**
* オリジナルのクラスローダを返します。
*
* @return オリジナルのクラスローダ
*/
protected ClassLoader getOriginalClassLoader() {
S2Container configurationContainer = S2ContainerFactory
.getConfigurationContainer();
if (configurationContainer != null
&& configurationContainer.hasComponentDef(ClassLoader.class)) {
return (ClassLoader) configurationContainer
.getComponent(ClassLoader.class);
}
return Thread.currentThread().getContextClassLoader();
}
/**
* ルートのdiconを解決します。
*
* @return ルートのdicon
* @throws Throwable
* 例外が発生した場合
*/
protected String resolveRootDicon() throws Throwable {
String targetName = getTargetName();
if (targetName.length() > 0) {
try {
Method method = getClass().getMethod(
"getRootDicon" + targetName, null);
return (String) method.invoke(this, null);
} catch (Exception ignore) {
}
}
return getRootDicon();
}
/**
* S2コンテナの終了処理を行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void tearDownContainer() throws Throwable {
ComponentDeployerFactory
.setProvider(new ComponentDeployerFactory.DefaultProvider());
SingletonS2ContainerFactory.setContainer(null);
S2ContainerServlet.clearInstance();
MessageResourceBundleFactory.clear();
DisposableUtil.dispose();
S2ContainerBehavior
.setProvider(new S2ContainerBehavior.DefaultProvider());
Thread.currentThread().setContextClassLoader(originalClassLoader);
unitClassLoader = null;
originalClassLoader = null;
container = null;
servletContext = null;
request = null;
response = null;
servletConfig = null;
servlet = null;
namingConvention = null;
Env.initialize();
}
/**
* S2コンテナが初期化された後のセットアップを行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void setUpAfterContainerInit() throws Throwable {
}
/**
* フィールドのバインディング後のセットアップを行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void setUpAfterBindFields() throws Throwable {
}
/**
* フィールドがアンバインディングされる前に終了処理を行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void tearDownBeforeUnbindFields() throws Throwable {
}
/**
* テストメソッドごとのセットアップを行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void setUpForEachTestMethod() throws Throwable {
String targetName = getTargetName();
if (targetName.length() > 0) {
invoke("setUp" + targetName);
}
}
/**
* S2コンテナが破棄される前に終了処理を行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void tearDownBeforeContainerDestroy() throws Throwable {
}
/**
* テストメソッドごとの終了処理を行ないます。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void tearDownForEachTestMethod() throws Throwable {
String targetName = getTargetName();
if (targetName.length() > 0) {
invoke("tearDown" + getTargetName());
}
}
/**
* テストを実行します。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void doRunTest() throws Throwable {
runTest();
}
/**
* サーブレットを返します。
*
* @return サーブレット
*/
protected Servlet getServlet() {
return servlet;
}
/**
* サーブレットを設定します。
*
* @param servlet
* サーブレット
*/
protected void setServlet(Servlet servlet) {
this.servlet = servlet;
}
/**
* サーブレットの設定を返します。
*
* @return サーブレットの設定
*/
protected MockServletConfig getServletConfig() {
return servletConfig;
}
/**
* サーブレットの設定を設定します。
*
* @param servletConfig
* サーブレットの設定
*/
protected void setServletConfig(MockServletConfig servletConfig) {
this.servletConfig = servletConfig;
}
/**
* サーブレットコンテキストを返します。
*
* @return サーブレットコンテキスト
*/
protected MockServletContext getServletContext() {
return servletContext;
}
/**
* サーブレットコンテキストを設定します。
*
* @param servletContext
* サーブレットコンテキスト
*/
protected void setServletContext(MockServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* リクエストを返します。
*
* @return リクエスト
*/
protected MockHttpServletRequest getRequest() {
return request;
}
/**
* リクエストを設定します。
*
* @param request
* リクエスト
*/
protected void setRequest(MockHttpServletRequest request) {
this.request = request;
}
/**
* レスポンスを返します。
*
* @return レスポンス
*/
protected MockHttpServletResponse getResponse() {
return response;
}
/**
* @param response
*/
protected void setResponse(MockHttpServletResponse response) {
this.response = response;
}
/**
* 命名規約を返します。
*
* @return 命名規約
*/
protected NamingConvention getNamingConvention() {
return namingConvention;
}
/**
* ターゲット名を返します。
*
* @return ターゲット名
*/
protected String getTargetName() {
return getName().substring(4);
}
/**
* ターゲットメソッドを返します。
*
* @return ターゲットメソッド
*/
protected Method getTargetMethod() {
return ClassUtil.getMethod(getClass(), getName(), null);
}
/**
* メソッドを実行します。
*
* @param methodName
* メソッド名
* @return 実行結果
* @throws Throwable
* 例外が発生した場合
*/
protected Object invoke(String methodName) throws Throwable {
try {
Method method = ClassUtil.getMethod(getClass(), methodName, null);
return MethodUtil.invoke(method, this, null);
} catch (NoSuchMethodRuntimeException ignore) {
return null;
}
}
/**
* フィールドにコンポーネントをバインドします。
*
* @throws Throwable
* 例外が発生した場合
*/
protected void bindFields() throws Throwable {
boundFields = new ArrayList();
for (Class clazz = getClass(); clazz != S2FrameworkTestCase.class
&& clazz != null; clazz = clazz.getSuperclass()) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; ++i) {
bindField(fields[i]);
}
}
}
/**
* フィールドにコンポーネントをバインドします。
*
* @param field
* フィールド
*/
protected void bindField(Field field) {
if (isAutoBindable(field)) {
field.setAccessible(true);
if (FieldUtil.get(field, this) != null) {
return;
}
String name = normalizeName(field.getName());
Object component = null;
if (getContainer().hasComponentDef(name)) {
Class componentClass = getComponentDef(name)
.getComponentClass();
if (componentClass == null) {
component = getComponent(name);
if (component != null) {
componentClass = component.getClass();
}
}
if (componentClass != null
&& field.getType().isAssignableFrom(componentClass)) {
if (component == null) {
component = getComponent(name);
}
} else {
component = null;
}
}
if (component == null
&& getContainer().hasComponentDef(field.getType())) {
component = getComponent(field.getType());
}
if (component != null) {
FieldUtil.set(field, this, component);
boundFields.add(field);
}
}
}
/**
* 名前を正規化します。
*
* @param name
* 名前
* @return 正規化された名前
*/
protected String normalizeName(String name) {
return StringUtil.replace(name, "_", "");
}
/**
* 自動バインディング可能かどうか返します。
*
* @param field
* フィールド
* @return 自動バインディング可能かどうか
*/
protected boolean isAutoBindable(Field field) {
int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)
&& !field.getType().isPrimitive();
}
/**
* バインディングされた情報をクリアします。
*/
protected void unbindFields() {
for (int i = 0; i < boundFields.size(); ++i) {
Field field = (Field) boundFields.get(i);
try {
field.set(this, null);
} catch (IllegalArgumentException e) {
System.err.println(e);
} catch (IllegalAccessException e) {
System.err.println(e);
}
}
boundFields = null;
}
}