/*
* Copyright 2004-2007 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を使うテストを行なうためのTestCase
です。 TODO: 記述追加
*
* @author higa
*/
public abstract class S2FrameworkTestCase extends TestCase {
/**
* テストケースで使用する環境名設定ファイルのパスです。
*
* @see org.seasar.framework.env.Env
*/
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() {
}
/**
* name
で指定されたテストメソッドのみを実行する{@link S2FrameworkTestCase}のインスタンスを構築します。
*
* @param name
* テストメソッド名
*/
public S2FrameworkTestCase(String name) {
super(name);
}
/**
* テスト用S2コンテナに自動的(暗黙的)にWARM deployを適用する場合にtrue
を返します。
*
* 自動的(暗黙的)にWARM deployを適用する場合とは、 以下の条件を全て満たす場合です。 SMART
* deployに必要なdiconファイルが揃っていれば、 s2container.diconを用意しなくても、 ユニットテストに最適なWARM
* deployを自動的に適用します。
*
true
に設定されている。true
*/
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
* 自動的(暗黙的)にWARM deploy適用する場合true
*/
public void setWarmDeploy(boolean warmDeploy) {
this.warmDeploy = warmDeploy;
}
/**
* テスト用S2コンテナを作成する際に{@link NamingConvention 命名規約}を登録する場合はtrue
を返します。
*
* @return 命名規約を登録する場合はtrue
*/
public boolean isRegisterNamingConvention() {
return registerNamingConvention;
}
/**
* テスト用S2コンテナを作成する際に{@link NamingConvention 命名規約}を登録する場合はtrue
を設定します。
*
* @param registerNamingConvention
* 命名規約を登録する場合true
*/
public void setRegisterNamingConvention(boolean registerNamingConvention) {
this.registerNamingConvention = registerNamingConvention;
}
/**
* テスト用{@link S2Container S2コンテナ}を返します。
*
* @return テスト用S2コンテナ
*/
public S2Container getContainer() {
return container;
}
/**
* テスト用S2コンテナに登録されている、 指定した名前のコンポーネントを返します。
*
* @param componentName
* コンポーネント名
* @return コンポーネント
*/
public Object getComponent(String componentName) {
return container.getComponent(componentName);
}
/**
* テスト用S2コンテナに登録されている、 指定したクラスのコンポーネントを返します。
*
* @param componentClass
* コンポーネントクラス
* @return コンポーネント
*/
public Object getComponent(Class componentClass) {
return container.getComponent(componentClass);
}
/**
* テスト用S2コンテナに登録されている、 指定した名前の{@link ComponentDef コンポーネント定義}を返します。
*
* @param componentName
* コンポーネント名
* @return コンポーネント定義
*/
public ComponentDef getComponentDef(String componentName) {
return container.getComponentDef(componentName);
}
/**
* テスト用S2コンテナに登録されている、 指定したクラスの{@link ComponentDef コンポーネント定義}を返します。
*
* @param componentClass
* コンポーネントクラス
* @return コンポーネント定義
*/
public ComponentDef getComponentDef(Class componentClass) {
return container.getComponentDef(componentClass);
}
/**
* 指定したクラスのコンポーネントをテスト用S2コンテナに登録します。
*
* @param componentClass
* コンポーネントクラス
* @see #register(Class, String)
*/
public void register(Class componentClass) {
if (namingConvention == null) {
register(componentClass, null);
} else {
register(componentClass, namingConvention
.fromClassNameToComponentName(componentClass.getName()));
}
}
/**
* 指定したクラスのコンポーネントを、 指定した名前でテスト用S2コンテナに登録します。
*
* @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);
}
/**
* コンポーネントをテスト用S2コンテナに登録します。
*
* @param component
* コンポーネント
*/
public void register(Object component) {
container.register(component);
}
/**
* 指定した名前で、 コンポーネントをテスト用S2コンテナに登録します。
*
* @param component
* コンポーネント
* @param componentName
* コンポーネント名
*/
public void register(Object component, String componentName) {
container.register(component, componentName);
}
/**
* コンポーネント定義をテスト用S2コンテナに登録します。
*
* @param componentDef
* コンポーネント定義
*/
public void register(ComponentDef componentDef) {
container.register(componentDef);
}
/**
* テスト用S2コンテナに指定したdiconファイルをインクルードします。
*
* @param path
* diconファイルのパス
*/
public void include(String path) {
S2ContainerFactory.include(container, convertPath(path));
}
/**
* パスを変換します。 指定したパスにリソースが存在しない場合、 このクラスと同一パッケージ内にパスとして指定した名前のリソースを検索し、
* そのパスを返します。 リソースが見つからない場合、 指定したパスをそのまま返します。
*
* @param path
* パス
* @return 変換後のパス
* @see ResourceUtil#convertPath(String, Class)
*/
protected String convertPath(String path) {
return ResourceUtil.convertPath(path, getClass());
}
/**
* テストを実行します。
*
*
*
* メソッドコールシーケンス
*
*
* 1
* {@link #setUpContainer()}
* 15
* {@link #tearDownContainer()}
*
*
* 2
* {@link #setUp()}
* 14
* {@link #tearDown()}
*
*
* 3
* {@link #setUpForEachTestMethod()}
* 13
* {@link #tearDownForEachTestMethod()}
*
*
* 4
* テスト用S2コンテナの{@link S2Container#init() 初期化}
* 12
* テスト用S2コンテナの{@link S2Container#destroy() 破棄}
*
*
* 5
* {@link #setUpAfterContainerInit()}
* 11
* {@link #tearDownBeforeContainerDestroy()}
*
*
* 6
* {@link #bindFields()}
* 10
* {@link #unbindFields()}
*
*
* 7
* {@link #setUpAfterBindFields()}
* 9
* {@link #tearDownBeforeUnbindFields()}
*
*
* 8
* {@link #doRunTest()}
*
*
*
* @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のパスを返します。
*
* デフォルトではnull
を返します。
*
* @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]);
}
}
}
/**
* 指定したフィールドにコンポーネントをバインドします。 TODO: 記述追加
*
* @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 自動バインディングが可能な場合true
*/
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;
}
}