/*
* Copyright 2004-2009 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.container.customizer;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.Aspect;
import org.seasar.framework.aop.Pointcut;
import org.seasar.framework.aop.S2MethodInvocation;
import org.seasar.framework.aop.impl.NestedMethodInvocation;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;
import org.seasar.framework.container.AspectDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.Expression;
import org.seasar.framework.container.MetaDef;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.AspectDefFactory;
import org.seasar.framework.container.hotdeploy.HotdeployUtil;
import org.seasar.framework.container.impl.SimpleComponentDef;
import org.seasar.framework.env.Env;
import org.seasar.framework.util.StringUtil;
/**
* {@link org.seasar.framework.container.ComponentDef コンポーネント定義}に
* {@link org.seasar.framework.container.AspectDef アスペクト定義}を登録するコンポーネントカスタマイザです。
*
* このカスタマイザには、 ポイントカットとインターセプタを設定します。 インターセプタはコンポーネント名で指定し、
* 複数のインターセプタ名を設定することができます。 インターセプタ名が複数設定された場合は、 設定された順にアスペクト定義をコンポーネント定義に登録します。
* 最初に設定された名前を持つインターセプタが、 後に設定された名前を持つインターセプタよりも先に呼び出されることになります。
*
*
* コンポーネントに適用するインターセプタのインスタンス属性がsingleton
以外の場合は、
* {@link #setUseLookupAdapter(boolean) useLookupAdapter}プロパティをtrue
* に設定します。 これにより、 コンポーネントのメソッドが呼び出される度に、 コンテナからインターセプタのインスタンスをルックアップするようになります。
*
*
* @author higa
* @author jundu
*/
public class AspectCustomizer extends AbstractCustomizer {
/** interceptorName
プロパティのバインディング定義です。 */
public static final String interceptorName_BINDING = "bindingType=may";
/** pointcut
プロパティのバインディング定義です。 */
public static final String pointcut_BINDING = "bindingType=may";
/** useLookupAdapter
プロパティのバインディング定義です。 */
public static final String useLookupAdapter_BINDING = "bindingType=may";
private final List interceptorNames = new ArrayList();
private String pointcut;
private boolean useLookupAdapter;
/**
* コンポーネント定義に登録するインターセプタのコンポーネント名を設定します。
*
* すでに設定されているインターセプタ名は全て破棄されます。
*
*
* @param interceptorName
* インターセプタのコンポーネント名
*/
public void setInterceptorName(final String interceptorName) {
interceptorNames.clear();
interceptorNames.add(interceptorName);
}
/**
* コンポーネント定義に登録するインターセプタのコンポーネント名を追加します。
*
* @param interceptorName
* インターセプタのコンポーネント名
*/
public void addInterceptorName(final String interceptorName) {
interceptorNames.add(interceptorName);
}
/**
* コンポーネント定義に登録するアスペクト定義のポイントカットを設定します。
*
* @param pointcut
* ポイントカット
*/
public void setPointcut(final String pointcut) {
this.pointcut = pointcut;
}
/**
* インスタンス属性がsingleton
以外のインターセプタを適用する場合はtrue
を、
* そうでない場合はfalse
を指定します。
*
* @param useLookupAdapter
* インスタンス属性がsingleton
以外のインターセプタを適用する場合は
* true
*/
public void setUseLookupAdapter(final boolean useLookupAdapter) {
this.useLookupAdapter = useLookupAdapter;
}
/**
* カスタマイズ対象のコンポーネント定義をカスタマイズをします。
*
* 設定されたインターセプタ名を持つアスペクト定義をコンポーネント定義に登録します。 インターセプタ名が複数設定されている場合は、
* 設定された順にアスペクト定義をコンポーネント定義に登録します。
*
*
* @param componentDef
* コンポーネント定義
*/
protected void doCustomize(final ComponentDef componentDef) {
if (useLookupAdapter) {
final MethodInterceptor adaptor = new LookupAdaptorInterceptor((String[]) interceptorNames
.toArray(new String[interceptorNames.size()]));
final AspectDef aspectDef = AspectDefFactory.createAspectDef(new SimpleComponentDef(adaptor),
createPointcut());
componentDef.addAspectDef(aspectDef);
}
else if (HotdeployUtil.isHotdeploy()) {
//HotDeployの時にはinterceptorのインスタンスをチェックします。
final HotDeployCheckInterceptor adaptor = new HotDeployCheckInterceptor((String[]) interceptorNames
.toArray(new String[interceptorNames.size()]));
final AspectDef aspectDef = AspectDefFactory.createAspectDef(new SimpleComponentDef(adaptor),
createPointcut());
componentDef.addAspectDef(new HotDeployCheckAspectDef(aspectDef, adaptor, componentDef));
}
else {
for (int i = 0; i < interceptorNames.size(); ++i) {
final AspectDef aspectDef = AspectDefFactory.createAspectDef((String) interceptorNames.get(i),
createPointcut());
componentDef.addAspectDef(aspectDef);
}
}
}
/**
* ポイントカットを作成して返します。
*
* pointcut
プロパティが指定されている場合は、 その文字列からポイントカットを作成します。
* targetInterface
プロパティが指定されている場合は、 そのインターフェースからポイントカットを作成します。
* それ以外の場合はnull
を返します。
*
*
* @return ポイントカット
*/
protected Pointcut createPointcut() {
if (!StringUtil.isEmpty(pointcut)) {
return AspectDefFactory.createPointcut(pointcut);
}
if (targetInterface != null) {
return AspectDefFactory.createPointcut(targetInterface);
}
return null;
}
/**
* getAspectされた時にインターセプターのコンポーネントを取得して、
* HotDeployCheckInterceptorにセットするよ。
* @author newta
*/
public static class HotDeployCheckAspectDef implements AspectDef {
AspectDef org;
private final HotDeployCheckInterceptor adaptor;
private final ComponentDef componentDef;
public HotDeployCheckAspectDef(AspectDef org, HotDeployCheckInterceptor adaptor, ComponentDef componentDef) {
this.org = org;
this.adaptor = adaptor;
this.componentDef = componentDef;
}
public void addMetaDef(MetaDef metaDef) {
org.addMetaDef(metaDef);
}
public Aspect getAspect() {
//getAspectされたタイミングで登録インターセプターをコンポーネントから取得して
//HotDeployCheckInterceptorにセットします。
final S2Container container = componentDef.getContainer().getRoot();
String[] interceptorNames = adaptor.getInterceptorNames();
final MethodInterceptor[] interceptors = new MethodInterceptor[interceptorNames.length];
for (int i = 0; i < interceptors.length; ++i) {
interceptors[i] = ((MethodInterceptor) container.getComponent(interceptorNames[i]));
}
adaptor.setWatchInterceptors(interceptors);
return org.getAspect();
}
public S2Container getContainer() {
return org.getContainer();
}
public Expression getExpression() {
return org.getExpression();
}
public MetaDef getMetaDef(int index) {
return org.getMetaDef(index);
}
public MetaDef getMetaDef(String name) {
return org.getMetaDef(name);
}
public MetaDef[] getMetaDefs(String name) {
return org.getMetaDefs(name);
}
public int getMetaDefSize() {
return org.getMetaDefSize();
}
public Pointcut getPointcut() {
return org.getPointcut();
}
public Object getValue() {
return org.getValue();
}
public boolean isValueGettable() {
return org.isValueGettable();
}
public void setChildComponentDef(ComponentDef componentDef) {
org.setChildComponentDef(componentDef);
}
public void setContainer(S2Container container) {
org.setContainer(container);
}
public void setExpression(Expression expression) {
org.setExpression(expression);
}
public void setPointcut(Pointcut pointcut) {
org.setPointcut(pointcut);
}
public void setValue(Object value) {
org.setValue(value);
}
}
/**
* 指定されたインターセプターを別スレッドでgetComponentして、
* 最初に呼び出されたインターセプターとインスタンスが同じかどうかチェックします。
* 違うインスタンスの場合、警告が出力されます。
* @author newta
*/
public static class HotDeployCheckInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 1L;
/**
* インターセプタ名の配列です。
*/
protected final String[] interceptorNames;
protected MethodInterceptor[] otherThreadInterceptors;
private MethodInterceptor[] watchInterceptors;
/**
* LookupAdaptorInterceptorのインスタンスを構築します。
*
* @param interceptorNames
* インターセプタ名の配列
*/
public HotDeployCheckInterceptor(final String[] interceptorNames) {
this.interceptorNames = interceptorNames;
}
public String[] getInterceptorNames() {
return interceptorNames;
}
public void setWatchInterceptors(MethodInterceptor[] watchInterceptors) {
this.watchInterceptors = watchInterceptors;
}
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 別スレッドでインターセプタを取得する
HotDeployInstanceCheckRunnable checkRunnable = new HotDeployInstanceCheckRunnable(this, interceptorNames,
invocation);
Thread checkThread = new Thread(checkRunnable);
checkThread.start();
// チェック用のインターセプタが別スレッドから取得し終わるまで待つ
waitThis();
for (int i = 0; i < watchInterceptors.length; i++) {
if (watchInterceptors[i] != otherThreadInterceptors[i]) {
//ここをログに警告で出す。or Exceptionにしてしまう?
System.out.println("別スレッドでコンポーネント取得したら、インターセプターが別インスタンスだよ。大丈夫?");
System.out.println("セッションとかリクエストのスコープデータを使うなら、useLookupAdapterにtrueをセットしてね。");
System.out.println("このまま問題なしなら、警告を出ないようにするにはインターセプターのインスタンス宣言をsingletonもしくはapplicationにしたらokかもねー。");
}
}
//インターセプターを実行します。
final MethodInvocation nestInvocation = new NestedMethodInvocation((S2MethodInvocation) invocation,
watchInterceptors);
return nestInvocation.proceed();
}
protected synchronized void waitThis() throws InterruptedException {
wait();
}
protected synchronized void notifyThis() throws InterruptedException {
notify();
}
/**
* 別スレッドでインターセプターのコンポーネントを取得します。
* @author newta
*/
public static class HotDeployInstanceCheckRunnable extends AbstractInterceptor implements Runnable {
private static final long serialVersionUID = 1L;
private String[] interceptorNames;
private final HotDeployCheckInterceptor parent;
private final MethodInvocation invocation;
public HotDeployInstanceCheckRunnable(final HotDeployCheckInterceptor parent,
final String[] interceptorNames, final MethodInvocation invocation) {
this.parent = parent;
this.interceptorNames = interceptorNames;
this.invocation = invocation;
}
public void run() {
final S2Container container = getComponentDef(invocation).getContainer().getRoot();
final MethodInterceptor[] interceptors = new MethodInterceptor[interceptorNames.length];
for (int i = 0; i < interceptors.length; ++i) {
interceptors[i] = (MethodInterceptor) container.getComponent(interceptorNames[i]);
}
parent.otherThreadInterceptors = interceptors;
try {
parent.notifyThis();
} catch (InterruptedException e) {
//どうしたらいいんだろう。。
throw new RuntimeException(e);
}
}
public Object invoke(MethodInvocation arg0) throws Throwable {
//AbstractInterceptorのgetComponentDefが使いたいだけなので、実際呼び出されたらException
throw new UnsupportedOperationException();
}
}
}
/**
* インスタンス属性がsingleton
以外のインターセプタを呼び出すためのアダプタとなるインターセプタです。
*
* このインターセプタは呼び出されると、 構築時に指定されたインターセプタを
* {@link org.seasar.framework.container.S2Container}から取得して処理を引き渡します。 そのため、
* 元々呼び出したいインターセプタのインスタンス属性がsingleton
* 以外でも期待した結果を得ることが出来るようになります。
*
*
* @author koichik
*/
public static class LookupAdaptorInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 1L;
/**
* インターセプタ名の配列です。
*/
protected String[] interceptorNames;
/**
* LookupAdaptorInterceptorのインスタンスを構築します。
*
* @param interceptorNames
* インターセプタ名の配列
*/
public LookupAdaptorInterceptor(final String[] interceptorNames) {
this.interceptorNames = interceptorNames;
}
public Object invoke(final MethodInvocation invocation) throws Throwable {
final S2Container container = getComponentDef(invocation).getContainer().getRoot();
final MethodInterceptor[] interceptors = new MethodInterceptor[interceptorNames.length];
for (int i = 0; i < interceptors.length; ++i) {
interceptors[i] = (MethodInterceptor) container.getComponent(interceptorNames[i]);
}
final MethodInvocation nestInvocation = new NestedMethodInvocation((S2MethodInvocation) invocation,
interceptors);
return nestInvocation.proceed();
}
}
}