001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.assembler.classic;
017:
018: import org.apache.openejb.core.CoreDeploymentInfo;
019: import org.apache.openejb.core.interceptor.InterceptorData;
020: import org.apache.openejb.util.LogCategory;
021: import org.apache.openejb.util.Logger;
022: import org.apache.openejb.util.SetAccessible;
023: import org.apache.openejb.util.Classes;
024: import org.apache.openejb.OpenEJBException;
025:
026: import javax.interceptor.InvocationContext;
027: import java.util.Comparator;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.HashMap;
033: import java.util.Set;
034: import java.util.HashSet;
035: import java.lang.reflect.Method;
036: import java.lang.reflect.Modifier;
037:
038: /**
039: * @version $Rev: 607343 $ $Date: 2007-12-28 14:00:51 -0800 $
040: */
041: public class InterceptorBindingBuilder {
042:
043: public static final Logger logger = Logger.getInstance(
044: LogCategory.OPENEJB_STARTUP,
045: InterceptorBindingBuilder.class.getPackage().getName());
046: private final List<InterceptorBindingInfo> packageAndClassBindings;
047:
048: public static enum Level {
049: PACKAGE, CLASS, OVERLOADED_METHOD, EXACT_METHOD
050: }
051:
052: public static enum Type {
053: ADDITION_OR_LOWER_EXCLUSION, SAME_LEVEL_EXCLUSION, SAME_AND_LOWER_EXCLUSION, EXPLICIT_ORDERING
054: }
055:
056: private final EjbJarInfo ejbJarInfo;
057: private final ArrayList<InterceptorBindingInfo> bindings;
058: private final Map<String, InterceptorData> interceptors = new HashMap<String, InterceptorData>();
059:
060: public InterceptorBindingBuilder(ClassLoader cl,
061: EjbJarInfo ejbJarInfo) throws OpenEJBException {
062: this .ejbJarInfo = ejbJarInfo;
063: bindings = new ArrayList<InterceptorBindingInfo>(
064: ejbJarInfo.interceptorBindings);
065: Collections.sort(bindings, new IntercpetorBindingComparator());
066: Collections.reverse(bindings);
067:
068: packageAndClassBindings = new ArrayList<InterceptorBindingInfo>();
069: for (InterceptorBindingInfo binding : bindings) {
070: Level level = level(binding);
071: if (level == Level.PACKAGE || level == Level.CLASS) {
072: packageAndClassBindings.add(binding);
073: }
074: }
075:
076: for (InterceptorInfo info : ejbJarInfo.interceptors) {
077: Class clazz = null;
078: try {
079: clazz = Class.forName(info.clazz, true, cl);
080: } catch (ClassNotFoundException e) {
081: throw new OpenEJBException(
082: "Interceptor class cannot be loaded: "
083: + info.clazz);
084: }
085: InterceptorData interceptor = new InterceptorData(clazz);
086:
087: toMethods(clazz, info.aroundInvoke, interceptor
088: .getAroundInvoke());
089: toMethods(clazz, info.postActivate, interceptor
090: .getPostActivate());
091: toMethods(clazz, info.prePassivate, interceptor
092: .getPrePassivate());
093: toMethods(clazz, info.postConstruct, interceptor
094: .getPostConstruct());
095: toMethods(clazz, info.preDestroy, interceptor
096: .getPreDestroy());
097: interceptors.put(info.clazz, interceptor);
098: }
099: }
100:
101: private void toMethods(Class clazz,
102: List<CallbackInfo> callbackInfos, List<Method> methods) {
103: for (CallbackInfo callbackInfo : callbackInfos) {
104: try {
105: Method method = getMethod(clazz, callbackInfo.method,
106: InvocationContext.class);
107: if (callbackInfo.className == null
108: && method.getDeclaringClass().equals(clazz)) {
109: methods.add(method);
110: }
111: if (method.getDeclaringClass().getName().equals(
112: callbackInfo.className)) {
113: methods.add(method);
114: }
115: } catch (NoSuchMethodException e) {
116: logger
117: .warning("Interceptor method not found (skipping): public Object "
118: + callbackInfo.method
119: + "(InvocationContext); in class "
120: + clazz.getName());
121: }
122: }
123: Collections.sort(methods, new MethodCallbackComparator());
124: }
125:
126: public static class MethodCallbackComparator implements
127: Comparator<Method> {
128: public int compare(Method m1, Method m2) {
129: Class<?> c1 = m1.getDeclaringClass();
130: Class<?> c2 = m2.getDeclaringClass();
131: if (c1.equals(c2))
132: return 0;
133: if (c1.isAssignableFrom(c2))
134: return -1;
135: return 1;
136: }
137: }
138:
139: private Method getMethod(Class clazz, String methodName,
140: Class... parameterTypes) throws NoSuchMethodException {
141: NoSuchMethodException original = null;
142: while (clazz != null) {
143: try {
144: Method method = clazz.getDeclaredMethod(methodName,
145: parameterTypes);
146: return SetAccessible.on(method);
147: } catch (NoSuchMethodException e) {
148: if (original == null)
149: original = e;
150: }
151: clazz = clazz.getSuperclass();
152: }
153: throw original;
154: }
155:
156: private void toCallback(Class clazz,
157: List<CallbackInfo> callbackInfos, List<Method> methods) {
158: for (CallbackInfo callbackInfo : callbackInfos) {
159: try {
160: Method method = getMethod(clazz, callbackInfo.method);
161: if (callbackInfo.className == null) {
162: methods.add(method);
163: } else if (method.getDeclaringClass().getName().equals(
164: callbackInfo.className)) {
165: methods.add(method);
166: } else {
167: // check for a private method on the declared class
168:
169: // find declared class
170: Class c = clazz;
171: while (c != null
172: && !c.getName().equals(
173: callbackInfo.className))
174: c = c.getSuperclass();
175:
176: // get callback method
177: if (c != null) {
178: try {
179: method = c
180: .getDeclaredMethod(callbackInfo.method);
181: // make sure it is private
182: if (Modifier.isPrivate(method
183: .getModifiers())) {
184: SetAccessible.on(method);
185: methods.add(method);
186: }
187: } catch (NoSuchMethodException e) {
188: }
189: }
190: }
191: } catch (NoSuchMethodException e) {
192: String message = "Bean Callback method not found (skipping): public void "
193: + callbackInfo.method
194: + "(); in class "
195: + clazz.getName();
196: logger.warning(message);
197: throw new IllegalStateException(message, e);
198: }
199: }
200: Collections.sort(methods, new MethodCallbackComparator());
201: }
202:
203: public void build(CoreDeploymentInfo deploymentInfo,
204: EnterpriseBeanInfo beanInfo) {
205: Class clazz = deploymentInfo.getBeanClass();
206:
207: InterceptorData beanAsInterceptor = new InterceptorData(clazz);
208:
209: toMethods(clazz, beanInfo.aroundInvoke, beanAsInterceptor
210: .getAroundInvoke());
211: toCallback(clazz, beanInfo.postConstruct, beanAsInterceptor
212: .getPostConstruct());
213: toCallback(clazz, beanInfo.preDestroy, beanAsInterceptor
214: .getPreDestroy());
215:
216: if (beanInfo instanceof StatefulBeanInfo) {
217: StatefulBeanInfo stateful = (StatefulBeanInfo) beanInfo;
218: toCallback(clazz, stateful.postActivate, beanAsInterceptor
219: .getPostActivate());
220: toCallback(clazz, stateful.prePassivate, beanAsInterceptor
221: .getPrePassivate());
222: }
223:
224: for (Method method : deploymentInfo.getBeanClass().getMethods()) {
225: List<InterceptorData> methodInterceptors = createInterceptorDatas(
226: method, beanInfo.ejbName, this .bindings);
227:
228: // The bean itself gets to intercept too and is always last.
229: methodInterceptors.add(beanAsInterceptor);
230:
231: deploymentInfo.setMethodInterceptors(method,
232: methodInterceptors);
233: }
234:
235: List<InterceptorData> callbackInterceptorDatas = createInterceptorDatas(
236: null, beanInfo.ejbName, this .packageAndClassBindings);
237:
238: // The bean itself gets to intercept too and is always last.
239: callbackInterceptorDatas.add(beanAsInterceptor);
240:
241: deploymentInfo
242: .setCallbackInterceptors(callbackInterceptorDatas);
243: }
244:
245: private List<InterceptorData> createInterceptorDatas(Method method,
246: String ejbName, List<InterceptorBindingInfo> bindings) {
247: List<InterceptorBindingInfo> methodBindings = processBindings(
248: method, ejbName, bindings);
249: Collections.reverse(methodBindings);
250: List<InterceptorData> methodInterceptors = new ArrayList<InterceptorData>();
251:
252: for (InterceptorBindingInfo info : methodBindings) {
253: List<String> classes = (info.interceptorOrder.size() > 0) ? info.interceptorOrder
254: : info.interceptors;
255: for (String interceptorClassName : classes) {
256: InterceptorData interceptorData = interceptors
257: .get(interceptorClassName);
258: if (interceptorData == null) {
259: logger
260: .warning("InterceptorBinding references non-existent (undeclared) interceptor: "
261: + interceptorClassName);
262: continue;
263: }
264: methodInterceptors.add(interceptorData);
265: }
266: }
267: return methodInterceptors;
268: }
269:
270: private List<InterceptorBindingInfo> processBindings(Method method,
271: String ejbName, List<InterceptorBindingInfo> bindings) {
272: List<InterceptorBindingInfo> methodBindings = new ArrayList<InterceptorBindingInfo>();
273:
274: // The only critical thing to understand in this loop is that
275: // the bindings have already been sorted high to low (first to last)
276: // in this order:
277: //
278: // Primary sort is "level":
279: // (highest/first)
280: // - Method-level bindings with params
281: // - Method-level with no params
282: // - Class-level
283: // - Package-level (aka. default level)
284: // (lowest/last)
285: //
286: // They've been secondarily sorted *within* these levels by "type":
287: //
288: // (highest)
289: // - Explicit order
290: // - Exclude applying to current and lower levels
291: // - Exclude applying to just current level
292: // - Any addition for current level and/or exclusion for a lower level
293: // (lowest)
294: //
295: Set<Level> excludes = new HashSet<Level>();
296: for (InterceptorBindingInfo info : bindings) {
297: Level level = level(info);
298:
299: if (!implies(method, ejbName, level, info))
300: continue;
301:
302: Type type = type(level, info);
303:
304: if (type == Type.EXPLICIT_ORDERING
305: && !excludes.contains(level)) {
306: methodBindings.add(info);
307: // An explicit ordering trumps all other bindings of the same level or below
308: // (even other explicit order bindings). Any bindings that were higher than
309: // this one still apply (and will have already been applied).
310: //
311: // So we keep what we have, add this last binding and we're done with this method.
312: return methodBindings;
313: }
314:
315: if (type == Type.SAME_AND_LOWER_EXCLUSION) {
316: // We're done as the only things that will come after this will be
317: // at the same or lower level and we've just been told to exclude them.
318: // Nothing more to do for this method.
319: return methodBindings;
320: }
321:
322: if (type == Type.SAME_LEVEL_EXCLUSION) {
323: excludes.add(level);
324: }
325:
326: if (!excludes.contains(level))
327: methodBindings.add(info);
328:
329: if (info.excludeClassInterceptors)
330: excludes.add(Level.CLASS);
331: if (info.excludeDefaultInterceptors)
332: excludes.add(Level.PACKAGE);
333: }
334: return methodBindings;
335: }
336:
337: private boolean implies(Method method, String ejbName, Level level,
338: InterceptorBindingInfo info) {
339: if (level == Level.PACKAGE)
340: return true;
341: if (!ejbName.equals(info.ejbName))
342: return false;
343: if (level == Level.CLASS)
344: return true;
345:
346: NamedMethodInfo methodInfo = info.method;
347:
348: if (!methodInfo.methodName.equals(method.getName()))
349: return false;
350:
351: // do we have parameters?
352: List<String> params = methodInfo.methodParams;
353: if (params == null)
354: return true;
355:
356: // do we have the same number of parameters?
357: if (params.size() != method.getParameterTypes().length)
358: return false;
359:
360: // match parameters names
361: Class<?>[] parameterTypes = method.getParameterTypes();
362:
363: for (int i = 0; i < parameterTypes.length; i++) {
364:
365: Class<?> parameterType = parameterTypes[i];
366: String methodParam = params.get(i);
367:
368: try {
369: Class param = Classes.forName(methodParam,
370: parameterType.getClassLoader());
371: if (!param.equals(parameterType)) {
372: return false;
373: }
374: } catch (ClassNotFoundException e) {
375: return false;
376: }
377: }
378:
379: return true;
380: }
381:
382: public static class IntercpetorBindingComparator implements
383: Comparator<InterceptorBindingInfo> {
384: public int compare(InterceptorBindingInfo a,
385: InterceptorBindingInfo b) {
386: Level levelA = level(a);
387: Level levelB = level(b);
388:
389: if (levelA != levelB)
390: return levelA.ordinal() - levelB.ordinal();
391:
392: // Now resort to secondary sorting.
393:
394: return type(levelA, a).ordinal()
395: - type(levelB, b).ordinal();
396: }
397: }
398:
399: private static Level level(InterceptorBindingInfo info) {
400: if (info.ejbName.equals("*")) {
401: return Level.PACKAGE;
402: }
403:
404: if (info.method == null) {
405: return Level.CLASS;
406: }
407:
408: if (info.method.methodParams == null) {
409: return Level.OVERLOADED_METHOD;
410: }
411:
412: return Level.EXACT_METHOD;
413: }
414:
415: private static Type type(Level level, InterceptorBindingInfo info) {
416: if (info.interceptorOrder.size() > 0) {
417: return Type.EXPLICIT_ORDERING;
418: }
419:
420: if (level == Level.CLASS && info.excludeClassInterceptors
421: && info.excludeDefaultInterceptors) {
422: return Type.SAME_AND_LOWER_EXCLUSION;
423: }
424:
425: if (level == Level.CLASS && info.excludeClassInterceptors) {
426: return Type.SAME_LEVEL_EXCLUSION;
427: }
428:
429: return Type.ADDITION_OR_LOWER_EXCLUSION;
430: }
431:
432: }
|