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: 534273 $ $Date: 2007-05-01 19:22:35 -0400 (Tue, 01 May 2007) $
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    }