001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.config.j2ee;
031:
032: import com.caucho.config.program.SingletonGenerator;
033: import com.caucho.config.program.ComponentValueGenerator;
034: import com.caucho.config.program.FieldGeneratorProgram;
035: import com.caucho.config.program.MethodGeneratorProgram;
036: import com.caucho.config.program.ValueGenerator;
037: import com.caucho.amber.manager.*;
038: import com.caucho.config.program.ConfigProgram;
039: import com.caucho.config.ConfigException;
040: import com.caucho.config.program.ConfigProgram;
041: import com.caucho.naming.Jndi;
042: import com.caucho.util.L10N;
043: import com.caucho.util.Log;
044: import com.caucho.webbeans.component.ComponentImpl;
045: import com.caucho.webbeans.manager.WebBeansContainer;
046:
047: import javax.annotation.*;
048: import javax.ejb.EJB;
049: import javax.ejb.EJBs;
050: import javax.naming.*;
051: import javax.interceptor.*;
052: import javax.persistence.*;
053: import javax.webbeans.*;
054: import javax.xml.ws.WebServiceRef;
055:
056: import java.beans.Introspector;
057: import java.lang.annotation.Annotation;
058: import java.lang.reflect.Field;
059: import java.lang.reflect.Method;
060: import java.util.*;
061: import java.util.logging.Logger;
062: import java.util.concurrent.*;
063:
064: /**
065: * Analyzes a bean for @Inject tags.
066: */
067: public class InjectIntrospector {
068: private static final L10N L = new L10N(InjectIntrospector.class);
069: private static final Logger log = Log
070: .open(InjectIntrospector.class);
071:
072: private static HashMap<Class, Class> _primitiveTypeMap = new HashMap<Class, Class>();
073:
074: public static InjectProgram introspectProgram(Class type) {
075: ArrayList<ConfigProgram> injectList = new ArrayList<ConfigProgram>();
076:
077: introspectInject(injectList, type);
078: introspectInit(injectList, type);
079:
080: return new InjectProgram(injectList);
081: }
082:
083: public static void introspectInit(
084: ArrayList<ConfigProgram> initList, Class type)
085: throws ConfigException {
086: if (type == null || type.equals(Object.class))
087: return;
088:
089: introspectInit(initList, type.getSuperclass());
090:
091: for (Method method : type.getDeclaredMethods()) {
092: if (!method.isAnnotationPresent(PostConstruct.class))
093: continue;
094:
095: if (method.getParameterTypes().length == 1
096: && InvocationContext.class.equals(method
097: .getParameterTypes()[0]))
098: continue;
099:
100: if (method.getParameterTypes().length != 0)
101: throw new ConfigException(
102: location(method)
103: + L
104: .l("{0}: @PostConstruct is requires zero arguments"));
105:
106: PostConstructProgram initProgram = new PostConstructProgram(
107: method);
108:
109: if (!initList.contains(initProgram))
110: initList.add(initProgram);
111: }
112: }
113:
114: public static void introspectDestroy(
115: ArrayList<ConfigProgram> destroyList, Class type)
116: throws ConfigException {
117: if (type == null || type.equals(Object.class))
118: return;
119:
120: introspectDestroy(destroyList, type.getSuperclass());
121:
122: for (Method method : type.getDeclaredMethods()) {
123: if (method.isAnnotationPresent(PreDestroy.class)) {
124: Class[] types = method.getParameterTypes();
125:
126: if (types.length == 0) {
127: } else if (types.length == 1
128: && types[0].equals(InvocationContext.class)) {
129: // XXX:
130: continue;
131: } else
132: throw new ConfigException(
133: location(method)
134: + L
135: .l("@PreDestroy is requires zero arguments"));
136:
137: PreDestroyInject destroyProgram = new PreDestroyInject(
138: method);
139:
140: if (!destroyList.contains(destroyProgram))
141: destroyList.add(destroyProgram);
142: }
143: }
144: }
145:
146: public static void introspectConstruct(
147: ArrayList<ConfigProgram> initList, Class type)
148: throws ConfigException {
149: if (type == null || type.equals(Object.class))
150: return;
151:
152: for (Method method : type.getDeclaredMethods()) {
153: if (method.isAnnotationPresent(PostConstruct.class)) {
154: if (method.getParameterTypes().length != 0)
155: throw new ConfigException(
156: L
157: .l(
158: "{0}: @PostConstruct is requires zero arguments",
159: method.getName()));
160:
161: PostConstructProgram initProgram = new PostConstructProgram(
162: method);
163:
164: if (!initList.contains(initProgram))
165: initList.add(initProgram);
166: }
167:
168: if (method.isAnnotationPresent(PreDestroy.class)) {
169: if (method.getParameterTypes().length != 0)
170: throw new ConfigException(
171: L
172: .l(
173: "{0}: @PreDestroy is requires zero arguments",
174: method.getName()));
175:
176: initList.add(new PreDestroyProgram(method));
177: }
178: }
179:
180: introspectConstruct(initList, type.getSuperclass());
181: }
182:
183: public static void introspectInject(
184: ArrayList<ConfigProgram> injectList, Class type)
185: throws ConfigException {
186: try {
187: introspectInjectImpl(injectList, type);
188: } catch (ClassNotFoundException e) {
189: } catch (NoClassDefFoundError e) {
190: // occurs in some TCK tests
191: }
192: }
193:
194: private static void introspectInjectImpl(
195: ArrayList<ConfigProgram> injectList, Class type)
196: throws ConfigException, ClassNotFoundException {
197: if (type == null || type.equals(Object.class))
198: return;
199:
200: introspectInjectImpl(injectList, type.getSuperclass());
201:
202: configureClassResources(injectList, type);
203:
204: for (Field field : type.getDeclaredFields()) {
205: if (hasBindingAnnotation(field)) {
206: WebBeansContainer webBeans = WebBeansContainer.create();
207:
208: webBeans.createProgram(injectList, field);
209:
210: continue;
211: }
212:
213: introspect(injectList, field);
214: }
215:
216: for (Method method : type.getDeclaredMethods()) {
217: String fieldName = method.getName();
218: Class[] param = method.getParameterTypes();
219:
220: if (hasBindingAnnotation(method)) {
221: WebBeansContainer webBeans = WebBeansContainer.create();
222:
223: webBeans.createProgram(injectList, method);
224:
225: continue;
226: }
227:
228: if (param.length != 1)
229: continue;
230:
231: /*
232: if (fieldName.startsWith("set") && fieldName.length() > 3) {
233: fieldName = fieldName.substring(3);
234:
235: char ch = fieldName.charAt(0);
236:
237: if (Character.isUpperCase(ch)
238: && (fieldName.length() == 1
239: || Character.isLowerCase(fieldName.charAt(1)))) {
240: fieldName = Character.toLowerCase(ch) + fieldName.substring(1);
241: }
242: }
243: */
244:
245: introspect(injectList, method);
246: }
247: }
248:
249: public static void configureClassResources(
250: ArrayList<ConfigProgram> initList, Class type)
251: throws ConfigException {
252: String location = type.getName() + ": ";
253:
254: Resources resources = (Resources) type
255: .getAnnotation(Resources.class);
256: if (resources != null) {
257: for (Resource resource : resources.value()) {
258: introspectClassResource(initList, type, resource);
259: }
260: }
261:
262: Resource resource = (Resource) type
263: .getAnnotation(Resource.class);
264: if (resource != null) {
265: introspectClassResource(initList, type, resource);
266: }
267:
268: PersistenceContext pc = (PersistenceContext) type
269: .getAnnotation(PersistenceContext.class);
270:
271: if (pc != null)
272: introspectClassPersistenceContext(initList, type, pc);
273:
274: // ejb/0f66
275: EJB ejb = (EJB) type.getAnnotation(EJB.class);
276:
277: // ejb/0f67
278: EJBs ejbs = (EJBs) type.getAnnotation(EJBs.class);
279:
280: if (ejb != null && ejbs != null) {
281: throw new ConfigException(L.l(
282: "{0} cannot have both @EJBs and @EJB", type
283: .getName()));
284: } else if (ejb != null) {
285: if (Object.class.equals(ejb.beanInterface()))
286: throw new ConfigException(
287: location
288: + L
289: .l("@EJB at the class level must have a beanInterface()"));
290:
291: if ("".equals(ejb.name()))
292: throw new ConfigException(
293: location
294: + L
295: .l("@EJB at the class level must have a name()"));
296:
297: generateEjb(location, Object.class, "", ejb);
298: } else if (ejbs != null) {
299: for (EJB childEjb : ejbs.value()) {
300: if (Object.class.equals(childEjb.beanInterface()))
301: throw new ConfigException(
302: location
303: + L
304: .l("@EJB at the class level must have a beanInterface()"));
305:
306: if ("".equals(childEjb.name()))
307: throw new ConfigException(
308: location
309: + L
310: .l("@EJB at the class level must have a name()"));
311:
312: generateEjb(location, Object.class, "", childEjb);
313: }
314: }
315: }
316:
317: private static void introspectClassResource(
318: ArrayList<ConfigProgram> initList, Class type,
319: Resource resource) throws ConfigException {
320: String name = resource.name();
321:
322: Field field = findField(type, name);
323:
324: if (field != null) {
325: ValueGenerator gen = generateResource(location(field),
326: field.getType(), "", resource);
327:
328: return;
329: }
330:
331: Method method = findMethod(type, name);
332:
333: if (method != null) {
334: ValueGenerator gen = generateResource(location(method),
335: method.getParameterTypes()[0], "", resource);
336:
337: return;
338: }
339: }
340:
341: private static void introspectClassPersistenceContext(
342: ArrayList<ConfigProgram> initList, Class type,
343: PersistenceContext pContext) throws ConfigException {
344: String location = type.getSimpleName() + ": ";
345:
346: ValueGenerator gen = generatePersistenceContext(location,
347: EntityManager.class, "", pContext);
348: }
349:
350: private static void introspect(ArrayList<ConfigProgram> injectList,
351: Field field) throws ConfigException {
352: String location = location(field);
353: ValueGenerator gen = null;
354:
355: // default jndiName
356: String jndiName = field.getDeclaringClass().getName() + "/"
357: + field.getName();
358:
359: if (field.isAnnotationPresent(Resource.class)) {
360: Resource resource = field.getAnnotation(Resource.class);
361:
362: gen = generateResource(location, field.getType(), jndiName,
363: resource);
364: } else if (field.isAnnotationPresent(EJB.class)) {
365: EJB ejb = field.getAnnotation(EJB.class);
366:
367: gen = generateEjb(location, field.getType(), jndiName, ejb);
368: } else if (field.isAnnotationPresent(PersistenceUnit.class)) {
369: PersistenceUnit pUnit = field
370: .getAnnotation(PersistenceUnit.class);
371:
372: gen = generatePersistenceUnit(location, field.getType(),
373: jndiName, pUnit);
374: } else if (field.isAnnotationPresent(PersistenceContext.class)) {
375: PersistenceContext pContext = field
376: .getAnnotation(PersistenceContext.class);
377:
378: gen = generatePersistenceContext(location, field.getType(),
379: jndiName, pContext);
380: } else if (field.isAnnotationPresent(WebServiceRef.class)) {
381: WebServiceRef webService = field
382: .getAnnotation(WebServiceRef.class);
383:
384: gen = generateWebService(location, field.getType(),
385: jndiName, webService);
386: } else if (hasBindingAnnotation(field))
387: introspectWebBean(injectList, field);
388:
389: if (gen != null)
390: injectList.add(new FieldGeneratorProgram(field, gen));
391: }
392:
393: private static void introspect(ArrayList<ConfigProgram> injectList,
394: Method method) throws ConfigException {
395: String location = location(method);
396: ValueGenerator gen = null;
397: Class type = null;
398:
399: // default jndi name
400: String jndiName = method.getDeclaringClass().getName() + "/"
401: + method.getName();
402:
403: if (method.getParameterTypes().length > 0)
404: type = method.getParameterTypes()[0];
405:
406: if (method.isAnnotationPresent(Resource.class)) {
407: Resource resource = method.getAnnotation(Resource.class);
408:
409: gen = generateResource(location, type, jndiName, resource);
410: } else if (method.isAnnotationPresent(EJB.class)) {
411: EJB ejb = method.getAnnotation(EJB.class);
412:
413: gen = generateEjb(location, type, jndiName, ejb);
414: } else if (method.isAnnotationPresent(PersistenceUnit.class)) {
415: PersistenceUnit pUnit = method
416: .getAnnotation(PersistenceUnit.class);
417:
418: gen = generatePersistenceUnit(location, type, jndiName,
419: pUnit);
420: } else if (method.isAnnotationPresent(PersistenceContext.class)) {
421: PersistenceContext pContext = method
422: .getAnnotation(PersistenceContext.class);
423:
424: gen = generatePersistenceContext(location, type, jndiName,
425: pContext);
426: } else if (method.isAnnotationPresent(WebServiceRef.class)) {
427: WebServiceRef webService = method
428: .getAnnotation(WebServiceRef.class);
429:
430: gen = generateWebService(location, type, jndiName,
431: webService);
432: } else if (hasBindingAnnotation(method))
433: introspectWebBean(injectList, method);
434:
435: if (gen != null)
436: injectList.add(new MethodGeneratorProgram(method, gen));
437: }
438:
439: private static ValueGenerator generateWebService(String location,
440: Class type, String jndiName, WebServiceRef ref)
441: throws ConfigException {
442: String mappedName = ref.mappedName();
443:
444: if (!"".equals(ref.name()))
445: jndiName = ref.name();
446:
447: /*
448: if (Service.class.isAssignableFrom(fieldType)) {
449: program = new ServiceInjectProgram(name,
450: fieldType,
451: inject);
452: }
453: else {
454: program = new ServiceProxyInjectProgram(name,
455: fieldType,
456: inject);
457: }
458: */
459:
460: return null;
461: }
462:
463: private static ValueGenerator generatePersistenceContext(
464: String location, Class type, String jndiName,
465: PersistenceContext pContext) throws ConfigException {
466: AmberContainer.create().start();
467:
468: if (!type.isAssignableFrom(EntityManager.class)) {
469: throw new ConfigException(
470: location
471: + L
472: .l(
473: "@PersistenceContext field type '{0}' must be assignable from EntityManager",
474: type.getName()));
475: }
476:
477: String unitName = pContext.unitName();
478:
479: if (!"".equals(pContext.name()))
480: jndiName = pContext.name();
481:
482: WebBeansContainer webBeans = WebBeansContainer.create();
483:
484: ComponentImpl component;
485:
486: if ("".equals(unitName)) {
487: component = webBeans.bind(location, EntityManager.class);
488:
489: if (component == null)
490: throw new ConfigException(
491: location
492: + L
493: .l("@PersistenceContext cannot find any persistence contexts. No JPA persistence-units have been deployed"));
494: } else {
495: component = webBeans.bind(location, EntityManager.class,
496: unitName);
497:
498: if (component == null)
499: throw new ConfigException(
500: location
501: + L
502: .l(
503: "'{0}' is an unknown @PersistenceContext.",
504: unitName));
505: }
506:
507: bindJndi(location, jndiName, component);
508:
509: return new ComponentValueGenerator(location, component);
510: }
511:
512: private static ValueGenerator generatePersistenceUnit(
513: String location, Class type, String jndiName,
514: PersistenceUnit pUnit) throws ConfigException {
515: if (!type.isAssignableFrom(EntityManagerFactory.class)) {
516: throw new ConfigException(
517: location
518: + L
519: .l(
520: "@PersistenceUnit field type '{0}' must be assignable from EntityManagerFactory",
521: type.getName()));
522: }
523:
524: String unitName = pUnit.unitName();
525:
526: if (!"".equals(pUnit.name()))
527: jndiName = pUnit.name();
528:
529: WebBeansContainer webBeans = WebBeansContainer.create();
530:
531: ComponentImpl component;
532:
533: if ("".equals(unitName)) {
534: component = webBeans.bind(location,
535: EntityManagerFactory.class);
536:
537: if (component == null)
538: throw new ConfigException(
539: location
540: + L
541: .l("@PersistenceUnit cannot find any persistence units. No JPA persistence-units have been deployed"));
542: } else {
543: component = webBeans.bind(location,
544: EntityManagerFactory.class, unitName);
545:
546: if (component == null)
547: throw new ConfigException(
548: location
549: + L
550: .l(
551: "@PersistenceUnit(unitName='{0}') is an unknown persistence unit. No matching JPA persistence-units have been deployed",
552: unitName));
553: }
554:
555: bindJndi(location, jndiName, component);
556:
557: return new ComponentValueGenerator(location, component);
558: }
559:
560: private static ValueGenerator generateEjb(String location,
561: Class fieldType, String jndiName, EJB ejb)
562: throws ConfigException {
563: Class type = ejb.beanInterface();
564: String mappedName = ejb.mappedName();
565: String beanName = ejb.beanName();
566:
567: if (!"".equals(ejb.name()))
568: jndiName = ejb.name();
569:
570: return generateJndiComponent(location, fieldType, type,
571: jndiName, beanName, mappedName);
572: }
573:
574: private static ValueGenerator generateResource(String location,
575: Class fieldType, String jndiName, Resource resource)
576: throws ConfigException {
577: String mappedName = resource.mappedName();
578: Class type = resource.type();
579:
580: if (!"".equals(resource.name()))
581: jndiName = resource.name();
582:
583: return generateJndiComponent(location, fieldType, type,
584: jndiName, mappedName, "");
585: }
586:
587: private static void introspectWebBean(
588: ArrayList<ConfigProgram> injectList, Field field)
589: throws ConfigException {
590: WebBeansContainer webBeans = WebBeansContainer.create();
591:
592: webBeans.createProgram(injectList, field);
593: }
594:
595: private static void introspectWebBean(
596: ArrayList<ConfigProgram> injectList, Method method)
597: throws ConfigException {
598: WebBeansContainer webBeans = WebBeansContainer.create();
599:
600: webBeans.createProgram(injectList, method);
601: }
602:
603: /**
604: * Creates the value.
605: */
606: private static ValueGenerator generateJndiComponent(
607: String location, Class fieldType, Class type,
608: String jndiName, String mappedName, String beanName) {
609: if (!fieldType.isAssignableFrom(type))
610: type = fieldType;
611:
612: if (type.isPrimitive())
613: type = _primitiveTypeMap.get(type);
614:
615: Object value = Jndi.lookup(jndiName);
616:
617: // XXX: can use lookup-link and store the proxy
618:
619: if (value != null)
620: return new SingletonGenerator(value);
621:
622: WebBeansContainer webBeans = WebBeansContainer.create();
623:
624: ComponentImpl component = null;
625:
626: if (mappedName == null || "".equals(mappedName))
627: mappedName = jndiName;
628:
629: component = webBeans.bind(location, type, mappedName);
630:
631: if (component != null) {
632: bindJndi(location, jndiName, component);
633:
634: return new ComponentValueGenerator(location, component);
635: }
636:
637: if (component == null && beanName != null
638: && !"".equals(beanName)) {
639: component = webBeans.bind(location, type, beanName);
640: if (component != null) {
641: bindJndi(location, jndiName, component);
642:
643: return new ComponentValueGenerator(location, component);
644: }
645: }
646:
647: if (component == null && jndiName != null
648: && !"".equals(jndiName)) {
649: component = webBeans.bind(location, type, jndiName);
650:
651: if (component != null) {
652: bindJndi(location, jndiName, component);
653:
654: return new ComponentValueGenerator(location, component);
655: }
656: }
657:
658: if (component == null)
659: component = webBeans.bind(location, type);
660:
661: if (component != null) {
662: bindJndi(location, jndiName, component);
663:
664: return new ComponentValueGenerator(location, component);
665: }
666:
667: else
668: throw new ConfigException(
669: location
670: + L
671: .l(
672: "{0} with mappedName={1}, beanName={2}, and jndiName={3} does not match anything",
673: type.getName(), mappedName,
674: beanName, jndiName));
675:
676: /*
677: if (_component != null && _jndiName != null && ! "".equals(_jndiName)) {
678: try {
679: Jndi.bindDeepShort(_jndiName, _component);
680: } catch (NamingException e) {
681: throw new ConfigException(e);
682: }
683: }
684: }
685:
686: if (component != null)
687: return component.get();
688: else
689: return getJndiValue(_type);
690: */
691: }
692:
693: private static void bindJndi(String location, String name,
694: Object value) {
695: try {
696: if (!"".equals(name))
697: Jndi.bindDeepShort(name, value);
698: } catch (NamingException e) {
699: throw new ConfigException(location + e.getMessage(), e);
700: }
701: }
702:
703: private static Field findField(Class type, String name) {
704: for (Field field : type.getDeclaredFields()) {
705: if (field.getName().equals(name))
706: return field;
707: }
708:
709: return null;
710: }
711:
712: private static Method findMethod(Class type, String name) {
713: for (Method method : type.getDeclaredMethods()) {
714: if (method.getParameterTypes().length != 1)
715: continue;
716:
717: String methodName = method.getName();
718: if (!methodName.startsWith("set"))
719: continue;
720:
721: methodName = Introspector.decapitalize(methodName
722: .substring(3));
723:
724: if (name.equals(methodName))
725: return method;
726: }
727:
728: return null;
729: }
730:
731: private static boolean hasBindingAnnotation(Field field) {
732: if (field.isAnnotationPresent(In.class))
733: return true;
734:
735: for (Annotation ann : field.getAnnotations()) {
736: if (ann.annotationType().isAnnotationPresent(
737: BindingType.class))
738: return true;
739: }
740:
741: return false;
742: }
743:
744: private static boolean hasBindingAnnotation(Method method) {
745: if (method.isAnnotationPresent(Produces.class))
746: return false;
747: else if (method.isAnnotationPresent(Destroys.class))
748: return false;
749: else if (method.isAnnotationPresent(In.class))
750: return true;
751:
752: boolean hasBinding = false;
753: for (Annotation[] annList : method.getParameterAnnotations()) {
754: if (annList == null)
755: continue;
756:
757: for (Annotation ann : annList) {
758: if (ann.annotationType().equals(Observes.class))
759: return false;
760: if (ann.annotationType().equals(Disposes.class))
761: return false;
762: else if (ann.annotationType().isAnnotationPresent(
763: BindingType.class))
764: hasBinding = true;
765: }
766: }
767:
768: return hasBinding;
769: }
770:
771: private static String toFullName(String jndiName) {
772: int colon = jndiName.indexOf(':');
773: int slash = jndiName.indexOf('/');
774:
775: if (colon < 0 || slash > 0 && slash < colon)
776: jndiName = "java:comp/env/" + jndiName;
777:
778: return jndiName;
779: }
780:
781: private static ConfigException error(Field field, String msg) {
782: return new ConfigException(location(field) + msg);
783: }
784:
785: private static ConfigException error(Method method, String msg) {
786: return new ConfigException(location(method) + msg);
787: }
788:
789: private static String location(Field field) {
790: String className = field.getDeclaringClass().getName();
791:
792: return className + "." + field.getName() + ": ";
793: }
794:
795: private static String location(Method method) {
796: String className = method.getDeclaringClass().getName();
797:
798: return className + "." + method.getName() + ": ";
799: }
800:
801: static {
802: _primitiveTypeMap.put(boolean.class, Boolean.class);
803: _primitiveTypeMap.put(byte.class, Byte.class);
804: _primitiveTypeMap.put(char.class, Character.class);
805: _primitiveTypeMap.put(short.class, Short.class);
806: _primitiveTypeMap.put(int.class, Integer.class);
807: _primitiveTypeMap.put(long.class, Long.class);
808: _primitiveTypeMap.put(float.class, Float.class);
809: _primitiveTypeMap.put(double.class, Double.class);
810: }
811: }
|