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.persistence.PersistenceUnit;
027    import javax.persistence.PersistenceUnits;
028    
029    import org.apache.commons.logging.Log;
030    import org.apache.commons.logging.LogFactory;
031    import org.apache.geronimo.common.DeploymentException;
032    import org.apache.geronimo.xbeans.javaee.DescriptionType;
033    import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType;
034    import org.apache.geronimo.xbeans.javaee.InjectionTargetType;
035    import org.apache.geronimo.xbeans.javaee.JndiNameType;
036    import org.apache.geronimo.xbeans.javaee.PersistenceUnitRefType;
037    import org.apache.geronimo.xbeans.javaee.XsdAnyURIType;
038    import org.apache.xbean.finder.ClassFinder;
039    
040    
041    /**
042     * Static helper class used to encapsulate all the functions related to the translation of
043     * <strong>@PersistenceUnit</strong> and <strong>@PersistenceUnits</strong> annotations to deployment
044     * descriptor tags. The PersistenceUnitAnnotationHelper class can be used as part of the deployment of
045     * a module into the Geronimo server. It performs the following major functions:
046     * <p/>
047     * <ol>
048     * <li>Translates annotations into corresponding deployment descriptor elements (so that the
049     * actual deployment descriptor in the module can be updated or even created if necessary)
050     * </ol>
051     * <p/>
052     * <p><strong>Note(s):</strong>
053     * <ul>
054     * <li>The user is responsible for invoking change to metadata-complete
055     * <li>This helper class will validate any changes it makes to the deployment descriptor. An
056     * exception will be thrown if it fails to parse
057     * </ul>
058     * <p/>
059     * <p><strong>Remaining ToDo(s):</strong>
060     * <ul>
061     * <li>None
062     * </ul>
063     *
064     * @version $Rev $Date
065     * @since 04-2007
066     */
067    public final class PersistenceUnitAnnotationHelper extends AnnotationHelper {
068    
069        // Private instance variables
070        private static final Log log = LogFactory.getLog(PersistenceUnitAnnotationHelper.class);
071    
072        // Private constructor to prevent instantiation
073        private PersistenceUnitAnnotationHelper() {
074        }
075    
076        /**
077         * Update the deployment descriptor from the PersistenceUnit and PersistenceUnits annotations
078         *
079         * @param annotatedApp Access to the spec dd
080         * @param classFinder Access to the classes of interest
081         * @throws DeploymentException if parsing or validation error
082         */
083        public static void processAnnotations(AnnotatedApp annotatedApp, ClassFinder classFinder) throws DeploymentException {
084            if (annotatedApp != null) {
085                if (classFinder.isAnnotationPresent(PersistenceUnits.class)) {
086                    processPersistenceUnits(annotatedApp, classFinder);
087                }
088                if (classFinder.isAnnotationPresent(PersistenceUnit.class)) {
089                    processPersistenceUnit(annotatedApp, classFinder);
090                }
091            }
092        }
093    
094    
095        /**
096         * Process annotations
097         *
098         * @param annotatedApp Access to the spec dd
099         * @param classFinder Access to the classes of interest
100         * @throws DeploymentException if parsing or validation error
101         */
102        private static void processPersistenceUnit(AnnotatedApp annotatedApp, ClassFinder classFinder) throws DeploymentException {
103            log.debug("processPersistenceUnit(): Entry: AnnotatedApp: " + annotatedApp.toString());
104    
105            List<Class> classeswithPersistenceUnit = classFinder.findAnnotatedClasses(PersistenceUnit.class);
106            List<Method> methodswithPersistenceUnit = classFinder.findAnnotatedMethods(PersistenceUnit.class);
107            List<Field> fieldswithPersistenceUnit = classFinder.findAnnotatedFields(PersistenceUnit.class);
108    
109            // Class-level annotation
110            for (Class cls : classeswithPersistenceUnit) {
111                PersistenceUnit persistenceUnit = (PersistenceUnit) cls.getAnnotation(PersistenceUnit.class);
112                if (persistenceUnit != null) {
113                    addPersistenceUnit(annotatedApp, persistenceUnit, cls, null, null);
114                }
115            }
116    
117            // Method-level annotation
118            for (Method method : methodswithPersistenceUnit) {
119                PersistenceUnit persistenceUnit = method.getAnnotation(PersistenceUnit.class);
120                if (persistenceUnit != null) {
121                    addPersistenceUnit(annotatedApp, persistenceUnit, null, method, null);
122                }
123            }
124    
125            // Field-level annotation
126            for (Field field : fieldswithPersistenceUnit) {
127                PersistenceUnit persistenceUnit = field.getAnnotation(PersistenceUnit.class);
128                if (persistenceUnit != null) {
129                    addPersistenceUnit(annotatedApp, persistenceUnit, null, null, field);
130                }
131            }
132    
133            // Validate deployment descriptor to ensure it's still okay
134            validateDD(annotatedApp);
135    
136            log.debug("processPersistenceUnit(): Exit: AnnotatedApp: " + annotatedApp.toString());
137        }
138    
139    
140        /**
141         * Process multiple annotations
142         *
143         * @param annotatedApp Access to the spec dd
144         * @param classFinder Access to the classes of interest
145         * @throws DeploymentException if parsing or validation error
146         */
147        private static void processPersistenceUnits(AnnotatedApp annotatedApp, ClassFinder classFinder) throws DeploymentException {
148            log.debug("processPersistenceUnits(): Entry");
149    
150            List<Class> classeswithPersistenceUnits = classFinder.findAnnotatedClasses(PersistenceUnits.class);
151    
152            // Class-level annotation(s)
153            List<PersistenceUnit> persistenceUnitList = new ArrayList<PersistenceUnit>();
154            for (Class cls : classeswithPersistenceUnits) {
155                PersistenceUnits persistenceUnits = (PersistenceUnits) cls.getAnnotation(PersistenceUnits.class);
156                if (persistenceUnits != null) {
157                    persistenceUnitList.addAll(Arrays.asList(persistenceUnits.value()));
158                }
159                for (PersistenceUnit persistenceUnit : persistenceUnitList) {
160                    addPersistenceUnit(annotatedApp, persistenceUnit, cls, null, null);
161                }
162                persistenceUnitList.clear();
163            }
164    
165            log.debug("processPersistenceUnits(): Exit");
166        }
167    
168    
169        /**
170         * Add @PersistenceUnit and @PersistenceUnits annotations to the deployment descriptor. XMLBeans are used to
171         * read and manipulate the deployment descriptor as necessary. The PersistenceUnit annotation(s) will be
172         * converted to one of the following deployment descriptors:
173         *
174         * <ol>
175         *      <li><persistence-unit-ref> -- Describes a single entity manager factory reference for the
176         *          persistence unit
177         * </ol>
178         *
179         * <p><strong>Note(s):</strong>
180         * <ul>
181         *      <li>The deployment descriptor is the authoritative source so this method ensures that
182         *          existing elements in it are not overwritten by annoations
183         * </ul>
184         *
185         * @param annotation @PersistenceUnit annotation
186         * @param cls        Class name with the @PersistenceUnit annoation
187         * @param method     Method name with the @PersistenceUnit annoation
188         * @param field      Field name with the @PersistenceUnit annoation
189         * @param annotatedApp  Access to the specc dd
190         */
191        private static void addPersistenceUnit(AnnotatedApp annotatedApp, PersistenceUnit annotation, Class cls, Method method, Field field) {
192            log.debug("addPersistenceUnit( [annotatedApp] " + annotatedApp.toString() + "," + '\n' +
193                    "[annotation] " + annotation.toString() + "," + '\n' +
194                    "[cls] " + (cls != null ? cls.getName() : null) + "," + '\n' +
195                    "[method] " + (method != null ? method.getName() : null) + "," + '\n' +
196                    "[field] " + (field != null ? field.getName() : null) + " ): Entry");
197    
198            //------------------------------------------------------------------------------------------
199            // PersistenceUnitRef name:
200            // -- When annotation is applied on a class:    Name must be provided (cannot be inferred)
201            // -- When annotation is applied on a method:   Name is JavaBeans property name qualified
202            //                                              by the class (or as provided on the
203            //                                              annotation)
204            // -- When annotation is applied on a field:    Name is the field name qualified by the
205            //                                              class (or as provided on the annotation)
206            //------------------------------------------------------------------------------------------
207            String persistenceUnitRefName = annotation.name();
208            if (persistenceUnitRefName.equals("")) {
209                if (method != null) {
210                    StringBuilder stringBuilder = new StringBuilder(method.getName().substring(3));
211                    stringBuilder.setCharAt(0, Character.toLowerCase(stringBuilder.charAt(0)));
212                    persistenceUnitRefName = method.getDeclaringClass().getName() + "/" + stringBuilder.toString();
213                } else if (field != null) {
214                    persistenceUnitRefName = field.getDeclaringClass().getName() + "/" + field.getName();
215                }
216            }
217            log.debug("addPersistenceUnit(): persistenceUnitRefName: " + persistenceUnitRefName);
218    
219            // If there is already xml for the persistence unit ref, just add injection targets and return.
220            PersistenceUnitRefType[] persistenceUnitRefs = annotatedApp.getPersistenceUnitRefArray();
221            for (PersistenceUnitRefType persistenceUnitRef : persistenceUnitRefs) {
222                if (persistenceUnitRef.getPersistenceUnitRefName().getStringValue().trim().equals(persistenceUnitRefName)) {
223                    if (method != null || field != null) {
224                        InjectionTargetType[] targets = persistenceUnitRef.getInjectionTargetArray();
225                        if (!hasTarget(method, field, targets)) {
226                            configureInjectionTarget(persistenceUnitRef.addNewInjectionTarget(), method, field);
227                        }
228                    }
229                    return;
230                }
231            }
232    
233            // Doesn't exist in deployment descriptor -- add new
234            PersistenceUnitRefType persistenceUnitRef = annotatedApp.addNewPersistenceUnitRef();
235    
236            //------------------------------------------------------------------------------
237            // <persistence-unit-ref> required elements:
238            //------------------------------------------------------------------------------
239    
240            // persistence-unit-ref-name
241            JndiNameType unitRefName = persistenceUnitRef.addNewPersistenceUnitRefName();
242            unitRefName.setStringValue(persistenceUnitRefName);
243    
244            //------------------------------------------------------------------------------
245            // <persistence-unit-ref> optional elements:
246            //------------------------------------------------------------------------------
247    
248            // persistence-unit-name
249            String unitNameAnnotation = annotation.unitName();
250            if (!unitNameAnnotation.equals("")) {
251                org.apache.geronimo.xbeans.javaee.String persistenceUnitName = persistenceUnitRef.addNewPersistenceUnitName();
252                persistenceUnitName.setStringValue(unitNameAnnotation);
253            }
254    
255            // injection targets
256            if (method != null || field != null) {
257                configureInjectionTarget(persistenceUnitRef.addNewInjectionTarget(), method, field);
258            }
259    
260        }
261    
262    }