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 */
017
018 package org.apache.geronimo.j2ee.deployment.annotation;
019
020 import java.lang.reflect.Field;
021 import java.lang.reflect.Method;
022 import java.util.ArrayList;
023 import java.util.Arrays;
024 import java.util.List;
025
026 import javax.ejb.EJB;
027 import javax.ejb.EJBHome;
028 import javax.ejb.EJBLocalHome;
029 import javax.ejb.EJBs;
030 import javax.ejb.Local;
031 import javax.ejb.Remote;
032
033 import org.apache.commons.logging.Log;
034 import org.apache.commons.logging.LogFactory;
035 import org.apache.geronimo.deployment.xmlbeans.XmlBeansUtil;
036 import org.apache.geronimo.xbeans.javaee.DescriptionType;
037 import org.apache.geronimo.xbeans.javaee.EjbLinkType;
038 import org.apache.geronimo.xbeans.javaee.EjbLocalRefType;
039 import org.apache.geronimo.xbeans.javaee.EjbRefNameType;
040 import org.apache.geronimo.xbeans.javaee.EjbRefType;
041 import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType;
042 import org.apache.geronimo.xbeans.javaee.InjectionTargetType;
043 import org.apache.geronimo.xbeans.javaee.JavaIdentifierType;
044 import org.apache.geronimo.xbeans.javaee.LocalType;
045 import org.apache.geronimo.xbeans.javaee.RemoteType;
046 import org.apache.geronimo.xbeans.javaee.XsdStringType;
047 import org.apache.xbean.finder.ClassFinder;
048
049 /**
050 * Static helper class used to encapsulate all the functions related to the translation of
051 * <strong>@EJB</strong> and <strong>@EJBs</strong> annotations to deployment descriptor tags. The
052 * EJBAnnotationHelper class can be used as part of the deployment of a module into the Geronimo
053 * server. It performs the following major functions:
054 *
055 * <ol>
056 * <li>Translates annotations into corresponding deployment descriptor elements (so that the
057 * actual deployment descriptor in the module can be updated or even created if necessary)
058 * </ol>
059 *
060 * <p><strong>Note(s):</strong>
061 * <ul>
062 * <li>The user is responsible for invoking change to metadata-complete
063 * <li>This helper class will validate any changes it makes to the deployment descriptor. An
064 * exception will be thrown if it fails to parse
065 * </ul>
066 *
067 * <p><strong>Remaining ToDo(s):</strong>
068 * <ul>
069 * <li>Usage of mappedName
070 * </ul>
071 *
072 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
073 * @since 02-2007
074 */
075
076 public final class EJBAnnotationHelper {
077
078 // Private instance variables
079 private static final Log log = LogFactory.getLog(EJBAnnotationHelper.class);
080
081 // Private constructor to prevent instantiation
082 private EJBAnnotationHelper() {
083 }
084
085
086 /**
087 * Determine if there are any annotations present
088 *
089 * @return true or false
090 */
091 public static boolean annotationsPresent(ClassFinder classFinder) {
092 if (classFinder.isAnnotationPresent(EJB.class)) return true;
093 if (classFinder.isAnnotationPresent(EJBs.class)) return true;
094 return false;
095 }
096
097
098 /**
099 * Process the annotations
100 *
101 * @return Updated deployment descriptor
102 * @throws Exception if parsing or validation error
103 */
104 public static void processAnnotations(AnnotatedApp annotatedApp, ClassFinder classFinder) throws Exception {
105 if (annotatedApp != null) {
106 processEJBs(annotatedApp, classFinder);
107 processEJB(annotatedApp, classFinder);
108 }
109 }
110
111
112 /**
113 * Process annotations
114 *
115 * @param annotatedApp
116 * @param classFinder
117 * @throws Exception
118 */
119 private static void processEJB(AnnotatedApp annotatedApp, ClassFinder classFinder) throws Exception {
120 log.debug("processEJB(): Entry: AnnotatedApp: " + annotatedApp.toString());
121
122 List<Class> classesWithEJB = classFinder.findAnnotatedClasses(EJB.class);
123 List<Method> methodsWithEJB = classFinder.findAnnotatedMethods(EJB.class);
124 List<Field> fieldsWithEJB = classFinder.findAnnotatedFields(EJB.class);
125
126 // Class-level annotation
127 for (Class cls : classesWithEJB) {
128 EJB ejb = (EJB) cls.getAnnotation(EJB.class);
129 if (ejb != null) {
130 addEJB(annotatedApp, ejb, cls, null, null);
131 }
132 }
133
134 // Method-level annotation
135 for (Method method : methodsWithEJB) {
136 EJB ejb = (EJB) method.getAnnotation(EJB.class);
137 if (ejb != null) {
138 addEJB(annotatedApp, ejb, null, method, null);
139 }
140 }
141
142 // Field-level annotation
143 for (Field field : fieldsWithEJB) {
144 EJB ejb = (EJB) field.getAnnotation(EJB.class);
145 if (ejb != null) {
146 addEJB(annotatedApp, ejb, null, null, field);
147 }
148 }
149
150 // Validate deployment descriptor to ensure it's still okay
151 validateDD(annotatedApp);
152
153 log.debug("processEJB(): Exit: AnnotatedApp: " + annotatedApp.toString());
154 }
155
156
157 /**
158 * Process multiple annotations
159 *
160 * @param annotatedApp
161 * @param classFinder
162 * @throws Exception
163 */
164 private static void processEJBs(AnnotatedApp annotatedApp, ClassFinder classFinder) throws Exception {
165 log.debug("processEJBs(): Entry");
166
167 List<Class> classesWithEJBs = classFinder.findAnnotatedClasses(EJBs.class);
168
169 // Class-level annotation(s)
170 List<EJB> ejbList = new ArrayList<EJB>();
171 for (Class cls : classesWithEJBs) {
172 EJBs ejbs = (EJBs) cls.getAnnotation(EJBs.class);
173 if (ejbs != null) {
174 ejbList.addAll(Arrays.asList(ejbs.value()));
175 }
176 for (EJB ejb : ejbList) {
177 addEJB(annotatedApp, ejb, cls, null, null);
178 }
179 ejbList.clear();
180 }
181
182 log.debug("processEJBs(): Exit");
183 }
184
185
186 /**
187 * Add @EJB and @EJBs annotations to the deployment descriptor. XMLBeans are used to read and
188 * manipulate the deployment descriptor as necessary. The EJB annotation(s) will be converted to
189 * one of the following deployment descriptors if possible. Otherwise they will be listed as
190 * ambiguous and resolved in OpenEJB.
191 * <p/>
192 * <ol>
193 * <li><ejb-ref>
194 * <li><ejb-local-ref>
195 * </ol>
196 * <p/>
197 * <p><strong>Note(s):</strong>
198 * <ul>
199 * <li>The deployment descriptor is the authoritative source so this method ensures that
200 * existing elements in it are not overwritten by annoations
201 * </ul>
202 *
203 * @param annotatedApp
204 * @param annotation @EJB annotation
205 * @param cls Class name with the @EJB annoation
206 * @param method Method name with the @EJB annoation
207 * @param field Field name with the @EJB annoation
208 */
209 private static void addEJB(AnnotatedApp annotatedApp, EJB annotation, Class cls, Method method, Field field) {
210 log.debug("addEJB( [annotatedApp] " + annotatedApp.toString() + "," + '\n' +
211 "[annotation] " + annotation.toString() + "," + '\n' +
212 "[cls] " + (cls != null ? cls.getName() : null) + "," + '\n' +
213 "[method] " + (method != null ? method.getName() : null) + "," + '\n' +
214 "[field] " + (field != null ? field.getName() : null) + " ): Entry");
215
216 // First determine if the interface is "Local" or "Remote" (if we can--we may not be able to)
217 boolean localFlag = false;
218 boolean remoteFlag = false;
219 Class interfce = annotation.beanInterface();
220 if (interfce.equals(Object.class)) {
221 if (method != null) {
222 interfce = method.getParameterTypes()[0];
223 } else if (field != null) {
224 interfce = field.getType();
225 } else {
226 interfce = null;
227 }
228 }
229 log.debug("addEJB(): interfce: " + interfce);
230
231 // Just in case local and/or remote homes are still being implemented (even though
232 // they are optional in EJB 3.0)
233 if (interfce != null && !interfce.equals(Object.class)) {
234 if (EJBHome.class.isAssignableFrom(interfce)) {
235 for (Method m : interfce.getMethods()) {
236 if (m.getName().startsWith("create")) {
237 interfce = m.getReturnType();
238 break;
239 }
240 }
241 remoteFlag = true;
242 } else if (EJBLocalHome.class.isAssignableFrom(interfce)) {
243 for (Method m : interfce.getMethods()) {
244 if (m.getName().startsWith("create")) {
245 interfce = m.getReturnType();
246 break;
247 }
248 }
249 localFlag = true;
250 } else {
251 if (interfce.getAnnotation(Local.class) != null) {
252 localFlag = true;
253 } else if (interfce.getAnnotation(Remote.class) != null) {
254 remoteFlag = true;
255 }
256 }
257 }
258 log.debug("addEJB(): localFlag: " + localFlag);
259 log.debug("addEJB(): remoteFlag: " + remoteFlag);
260
261 //------------------------------------------------------------------------------------------
262 // 1. <ejb-local-ref>
263 //------------------------------------------------------------------------------------------
264 if (localFlag) {
265
266 log.debug("addEJB(): <ejb-local-ref> found");
267
268 String localRefName = annotation.name();
269 if (localRefName.equals("")) {
270 if (method != null) {
271 localRefName = method.getDeclaringClass().getName() + "/" + method.getName().substring(3); // method should start with "set"
272 } else if (field != null) {
273 localRefName = field.getDeclaringClass().getName() + "/" + field.getName();
274 }
275 }
276
277 boolean exists = false;
278 EjbLocalRefType[] ejbLocalRefEntries = annotatedApp.getEjbLocalRefArray();
279 for (EjbLocalRefType ejbLocalRefEntry : ejbLocalRefEntries) {
280 if (ejbLocalRefEntry.getEjbRefName().getStringValue().trim().equals(localRefName)) {
281 exists = true;
282 break;
283 }
284 }
285 if (!exists) {
286 try {
287
288 log.debug("addEJB(): Does not exist in DD: " + localRefName);
289
290 // Doesn't exist in deployment descriptor -- add new
291 EjbLocalRefType ejbLocalRef = annotatedApp.addNewEjbLocalRef();
292
293 //------------------------------------------------------------------------------
294 // <ejb-local-ref> required elements:
295 //------------------------------------------------------------------------------
296
297 // ejb-ref-name
298 EjbRefNameType ejbRefName = ejbLocalRef.addNewEjbRefName();
299 ejbRefName.setStringValue(localRefName);
300 ejbLocalRef.setEjbRefName(ejbRefName);
301
302 //------------------------------------------------------------------------------
303 // <ejb-local-ref> optional elements:
304 //------------------------------------------------------------------------------
305
306 // local
307 if (interfce != null) {
308 String localAnnotation = interfce.getName();
309 if (!localAnnotation.equals("")) {
310 LocalType local = ejbLocalRef.addNewLocal();
311 local.setStringValue(localAnnotation);
312 ejbLocalRef.setLocal(local);
313 }
314 }
315
316 // ejb-link
317 String beanName = annotation.beanName();
318 if (!beanName.equals("")) {
319 EjbLinkType ejbLink = ejbLocalRef.addNewEjbLink();
320 ejbLink.setStringValue(beanName);
321 ejbLocalRef.setEjbLink(ejbLink);
322 }
323
324 // mappedName
325 String mappdedNameAnnotation = annotation.mappedName();
326 if (!mappdedNameAnnotation.equals("")) {
327 XsdStringType mappedName = ejbLocalRef.addNewMappedName();
328 mappedName.setStringValue(mappdedNameAnnotation);
329 ejbLocalRef.setMappedName(mappedName);
330 }
331
332 // description
333 String descriptionAnnotation = annotation.description();
334 if (!descriptionAnnotation.equals("")) {
335 DescriptionType description = ejbLocalRef.addNewDescription();
336 description.setStringValue(descriptionAnnotation);
337 }
338
339 // injectionTarget
340 if (method != null || field != null) { // No class-level injection
341 InjectionTargetType injectionTarget = ejbLocalRef.addNewInjectionTarget();
342 FullyQualifiedClassType qualifiedClass = injectionTarget.addNewInjectionTargetClass();
343 JavaIdentifierType javaType = injectionTarget.addNewInjectionTargetName();
344 if (method != null) {
345 qualifiedClass.setStringValue(method.getDeclaringClass().getName());
346 javaType.setStringValue(method.getName().substring(3)); // method should start with "set"
347 injectionTarget.setInjectionTargetClass(qualifiedClass);
348 injectionTarget.setInjectionTargetName(javaType);
349 }
350 else if (field != null) {
351 qualifiedClass.setStringValue(field.getDeclaringClass().getName());
352 javaType.setStringValue(field.getName());
353 injectionTarget.setInjectionTargetClass(qualifiedClass);
354 injectionTarget.setInjectionTargetName(javaType);
355 }
356 }
357
358 }
359 catch (Exception anyException) {
360 log.debug("EJBAnnotationHelper: Exception caught while processing <ejb-local-ref>");
361 anyException.printStackTrace();
362 }
363 }
364 } // end if local
365 else if (remoteFlag) { // remote
366
367 //--------------------------------------------------------------------------------------
368 // 2. <ejb-ref>
369 //--------------------------------------------------------------------------------------
370
371 log.debug("addEJB(): <ejb-ref> found");
372
373 String remoteRefName = annotation.name();
374 if (remoteRefName.equals("")) {
375 if (method != null) {
376 remoteRefName = method.getDeclaringClass().getName() + "/" + method.getName().substring(3); // method should start with "set"
377 } else if (field != null) {
378 remoteRefName = field.getDeclaringClass().getName() + "/" + field.getName();
379 }
380 }
381
382 boolean exists = false;
383 EjbRefType[] ejbRefEntries = annotatedApp.getEjbRefArray();
384 for (EjbRefType ejbRefEntry : ejbRefEntries) {
385 if (ejbRefEntry.getEjbRefName().getStringValue().trim().equals(remoteRefName)) {
386 exists = true;
387 break;
388 }
389 }
390 if (!exists) {
391 try {
392
393 log.debug("addEJB(): Does not exist in DD: " + remoteRefName);
394
395 // Doesn't exist in deployment descriptor -- add new
396 EjbRefType ejbRef = annotatedApp.addNewEjbRef();
397
398 //------------------------------------------------------------------------------
399 // <ejb-ref> required elements:
400 //------------------------------------------------------------------------------
401
402 // ejb-ref-name
403 EjbRefNameType ejbRefName = ejbRef.addNewEjbRefName();
404 ejbRefName.setStringValue(remoteRefName);
405 ejbRef.setEjbRefName(ejbRefName);
406
407 //------------------------------------------------------------------------------
408 // <ejb-ref> optional elements:
409 //------------------------------------------------------------------------------
410
411 // remote
412 if (interfce != null) {
413 String remoteAnnotation = interfce.getName();
414 if (!remoteAnnotation.equals("")) {
415 RemoteType remote = ejbRef.addNewRemote();
416 remote.setStringValue(remoteAnnotation);
417 ejbRef.setRemote(remote);
418 }
419 }
420
421 // ejb-link
422 String beanName = annotation.beanName();
423 if (!beanName.equals("")) {
424 EjbLinkType ejbLink = ejbRef.addNewEjbLink();
425 ejbLink.setStringValue(beanName);
426 ejbRef.setEjbLink(ejbLink);
427 }
428
429 // mappedName
430 String mappdedNameAnnotation = annotation.mappedName();
431 if (!mappdedNameAnnotation.equals("")) {
432 XsdStringType mappedName = ejbRef.addNewMappedName();
433 mappedName.setStringValue(mappdedNameAnnotation);
434 ejbRef.setMappedName(mappedName);
435 }
436
437 // description
438 String descriptionAnnotation = annotation.description();
439 if (!descriptionAnnotation.equals("")) {
440 DescriptionType description = ejbRef.addNewDescription();
441 description.setStringValue(descriptionAnnotation);
442 }
443
444 // injectionTarget
445 if (method != null || field != null) { // No class-level injection
446 InjectionTargetType injectionTarget = ejbRef.addNewInjectionTarget();
447 FullyQualifiedClassType qualifiedClass = injectionTarget.addNewInjectionTargetClass();
448 JavaIdentifierType javaType = injectionTarget.addNewInjectionTargetName();
449 if (method != null) {
450 qualifiedClass.setStringValue(method.getDeclaringClass().getName());
451 javaType.setStringValue(method.getName().substring(3)); // method should start with "set"
452 injectionTarget.setInjectionTargetClass(qualifiedClass);
453 injectionTarget.setInjectionTargetName(javaType);
454 }
455 else if (field != null) {
456 qualifiedClass.setStringValue(field.getDeclaringClass().getName());
457 javaType.setStringValue(field.getName());
458 injectionTarget.setInjectionTargetClass(qualifiedClass);
459 injectionTarget.setInjectionTargetName(javaType);
460 }
461 }
462
463 }
464 catch (Exception anyException) {
465 log.debug("EJBAnnotationHelper: Exception caught while processing <ejb-ref>");
466 anyException.printStackTrace();
467 }
468 }
469 } // end if remote
470 else { // ambiguous
471
472 //--------------------------------------------------------------------------------------
473 // 3. <UNKNOWN>
474 //--------------------------------------------------------------------------------------
475 log.debug("addEJB(): <UNKNOWN> found");
476
477 String remoteRefName = annotation.name();
478 if (remoteRefName.equals("")) {
479 if (method != null) {
480 remoteRefName = method.getDeclaringClass().getName() + "/" + method.getName().substring(3); // method should start with "set"
481 } else if (field != null) {
482 remoteRefName = field.getDeclaringClass().getName() + "/" + field.getName();
483 }
484 }
485
486 boolean exists = false;
487 EjbRefType[] ejbRefEntries = annotatedApp.getEjbRefArray();
488 for (EjbRefType ejbRefEntry : ejbRefEntries) {
489 if (ejbRefEntry.getEjbRefName().getStringValue().trim().equals(remoteRefName)) {
490 exists = true;
491 break;
492 }
493 }
494 if (!exists) {
495 try {
496
497 log.debug("addEJB(): Does not exist in DD: " + remoteRefName);
498
499 // Doesn't exist in deployment descriptor -- add as an <ejb-ref> to the
500 // ambiguous list so that it can be resolved later
501 EjbRefType ejbRef = EjbRefType.Factory.newInstance();
502 annotatedApp.getAmbiguousEjbRefs().add(ejbRef);
503
504 //------------------------------------------------------------------------------
505 // <ejb-ref> required elements:
506 //------------------------------------------------------------------------------
507
508 // ejb-ref-name
509 EjbRefNameType ejbRefName = ejbRef.addNewEjbRefName();
510 ejbRefName.setStringValue(remoteRefName);
511 ejbRef.setEjbRefName(ejbRefName);
512
513 //------------------------------------------------------------------------------
514 // <ejb-ref> optional elements:
515 //------------------------------------------------------------------------------
516
517 // remote
518 if (interfce != null) {
519 String remoteAnnotation = interfce.getName();
520 if (!remoteAnnotation.equals("")) {
521 RemoteType remote = ejbRef.addNewRemote();
522 remote.setStringValue(remoteAnnotation);
523 ejbRef.setRemote(remote);
524 }
525 }
526
527 // ejb-link
528 String beanName = annotation.beanName();
529 if (!beanName.equals("")) {
530 EjbLinkType ejbLink = ejbRef.addNewEjbLink();
531 ejbLink.setStringValue(beanName);
532 ejbRef.setEjbLink(ejbLink);
533 }
534
535 // mappedName
536 String mappdedNameAnnotation = annotation.mappedName();
537 if (!mappdedNameAnnotation.equals("")) {
538 XsdStringType mappedName = ejbRef.addNewMappedName();
539 mappedName.setStringValue(mappdedNameAnnotation);
540 ejbRef.setMappedName(mappedName);
541 }
542
543 // description
544 String descriptionAnnotation = annotation.description();
545 if (!descriptionAnnotation.equals("")) {
546 DescriptionType description = ejbRef.addNewDescription();
547 description.setStringValue(descriptionAnnotation);
548 }
549
550 // injectionTarget
551 if (method != null || field != null) { // No class-level injection
552 InjectionTargetType injectionTarget = ejbRef.addNewInjectionTarget();
553 FullyQualifiedClassType qualifiedClass = injectionTarget.addNewInjectionTargetClass();
554 JavaIdentifierType javaType = injectionTarget.addNewInjectionTargetName();
555 if (method != null) {
556 qualifiedClass.setStringValue(method.getDeclaringClass().getName());
557 javaType.setStringValue(method.getName().substring(3)); // method should start with "set"
558 injectionTarget.setInjectionTargetClass(qualifiedClass);
559 injectionTarget.setInjectionTargetName(javaType);
560 }
561 else if (field != null) {
562 qualifiedClass.setStringValue(field.getDeclaringClass().getName());
563 javaType.setStringValue(field.getName());
564 injectionTarget.setInjectionTargetClass(qualifiedClass);
565 injectionTarget.setInjectionTargetName(javaType);
566 }
567 }
568
569 }
570 catch (Exception anyException) {
571 log.debug("EJBAnnotationHelper: Exception caught while processing <UNKNOWN>");
572 anyException.printStackTrace();
573 }
574 }
575
576 }
577 log.debug("addEJB(): Exit");
578 }
579
580
581 /**
582 * Validate deployment descriptor
583 *
584 * @param annotatedApp
585 * @throws Exception thrown if deployment descriptor cannot be parsed
586 */
587 private static void validateDD(AnnotatedApp annotatedApp) throws Exception {
588 log.debug("validateDD( " + annotatedApp.toString() + " ): Entry");
589
590 XmlBeansUtil.parse(annotatedApp.toString());
591
592 log.debug("validateDD(): Exit");
593 }
594 }