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