001    package org.apache.commons.digester3.annotations;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import static org.apache.commons.digester3.annotations.utils.AnnotationUtils.getAnnotationsArrayValue;
023    
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.AnnotatedElement;
026    import java.lang.reflect.Constructor;
027    import java.lang.reflect.Field;
028    import java.lang.reflect.Method;
029    import java.security.AccessController;
030    import java.security.PrivilegedAction;
031    
032    import org.apache.commons.digester3.Rule;
033    import org.apache.commons.digester3.annotations.reflect.MethodArgument;
034    import org.apache.commons.digester3.binder.AbstractRulesModule;
035    
036    /**
037     * {@link org.apache.commons.digester3.binder.RulesModule} implementation that allows loading rules from
038     * annotated classes.
039     *
040     * @since 3.0
041     */
042    public abstract class FromAnnotationsRuleModule
043        extends AbstractRulesModule
044    {
045    
046        private static final String JAVA_PACKAGE = "java";
047    
048        private static final AnnotationHandlerFactory DEFAULT_HANDLER_FACTORY = new DefaultAnnotationHandlerFactory();
049    
050        private AnnotationHandlerFactory annotationHandlerFactory = DEFAULT_HANDLER_FACTORY;
051    
052        private WithMemoryRulesBinder rulesBinder;
053    
054        /**
055         * {@inheritDoc}
056         */
057        @Override
058        protected final void configure()
059        {
060            if ( rulesBinder == null )
061            {
062                rulesBinder = new WithMemoryRulesBinder( rulesBinder() );
063            }
064    
065            try
066            {
067                configureRules();
068            }
069            finally
070            {
071                rulesBinder = null;
072            }
073        }
074    
075        /**
076         * Configures a {@link org.apache.commons.digester3.binder.RulesBinder} via the exposed methods.
077         */
078        protected abstract void configureRules();
079    
080        /**
081         * Allows users plug a different {@link AnnotationHandlerFactory} to create {@link AnnotationHandler} instances.
082         *
083         * @param annotationHandlerFactory A custom {@link AnnotationHandlerFactory} to create
084         *        {@link AnnotationHandler} instances
085         */
086        protected final void useAnnotationHandlerFactory( AnnotationHandlerFactory annotationHandlerFactory )
087        {
088            if ( annotationHandlerFactory == null )
089            {
090                throw new IllegalArgumentException( "Argument 'annotationHandlerFactory' must be not null" );
091            }
092    
093            this.annotationHandlerFactory = annotationHandlerFactory;
094        }
095    
096        /**
097         * Allows users to switch back to the default {@link AnnotationHandlerFactory} implementation.
098         */
099        protected final void useDefaultAnnotationHandlerFactory()
100        {
101            useAnnotationHandlerFactory( DEFAULT_HANDLER_FACTORY );
102        }
103    
104        /**
105         * Scan the input Class, looking for Digester rules expressed via annotations, and binds them.
106         *
107         * @param type the type has to be analyzed
108         * @see DigesterRule
109         */
110        protected final void bindRulesFrom( final Class<?> type )
111        {
112            if ( type == null || type.getPackage().getName().startsWith( JAVA_PACKAGE )
113                || rulesBinder.isAlreadyBound( type ) )
114            {
115                return;
116            }
117    
118            // TYPE
119            visitElements( type );
120    
121            if ( !type.isInterface() )
122            {
123                // CONSTRUCTOR
124                visitElements( new PrivilegedAction<Constructor<?>[]>()
125                {
126                    public Constructor<?>[] run()
127                    {
128                        return type.getDeclaredConstructors();
129                    }
130                } );
131    
132                // FIELD
133                visitElements( new PrivilegedAction<Field[]>()
134                {
135                    public Field[] run()
136                    {
137                        return type.getDeclaredFields();
138                    }
139                } );
140            }
141    
142            // METHOD
143            visitElements( new PrivilegedAction<Method[]>()
144            {
145                public Method[] run()
146                {
147                    return type.getDeclaredMethods();
148                }
149            } );
150    
151            rulesBinder.markAsBound( type );
152            bindRulesFrom( type.getSuperclass() );
153        }
154    
155        /**
156         *
157         *
158         * @param <AE>
159         * @param action
160         */
161        private <AE extends AnnotatedElement> void visitElements( PrivilegedAction<AE[]> action )
162        {
163            AE[] annotatedElements = null;
164            if ( System.getSecurityManager() != null )
165            {
166                annotatedElements = AccessController.doPrivileged( action );
167            }
168            else
169            {
170                annotatedElements = action.run();
171            }
172            visitElements( annotatedElements );
173        }
174    
175        /**
176         *
177         *
178         * @param annotatedElements
179         */
180        private void visitElements( AnnotatedElement... annotatedElements )
181        {
182            for ( AnnotatedElement element : annotatedElements )
183            {
184                for ( Annotation annotation : element.getAnnotations() )
185                {
186                    handle( annotation, element );
187                }
188    
189                if ( element instanceof Constructor || element instanceof Method )
190                {
191                    Annotation[][] parameterAnnotations;
192                    Class<?>[] parameterTypes;
193    
194                    if ( element instanceof Constructor )
195                    {
196                        // constructor args
197                        Constructor<?> construcotr = (Constructor<?>) element;
198                        parameterAnnotations = construcotr.getParameterAnnotations();
199                        parameterTypes = construcotr.getParameterTypes();
200                    }
201                    else
202                    {
203                        // method args
204                        Method method = (Method) element;
205                        parameterAnnotations = method.getParameterAnnotations();
206                        parameterTypes = method.getParameterTypes();
207                    }
208    
209                    for ( int i = 0; i < parameterTypes.length; i++ )
210                    {
211                        visitElements( new MethodArgument( i, parameterTypes[i], parameterAnnotations[i] ) );
212                    }
213                }
214            }
215        }
216    
217        /**
218         * Handles the current visited element and related annotation, invoking the
219         * right handler putting the rule provider in the rule set.
220         *
221         * @param annotation the current visited annotation.
222         * @param element the current visited element.
223         */
224        @SuppressWarnings( "unchecked" )
225        private <A extends Annotation, E extends AnnotatedElement, R extends Rule> void handle( A annotation, E element )
226        {
227            Class<?> annotationType = annotation.annotationType();
228    
229            // check if it is one of the @*.List annotation
230            if ( annotationType.isAnnotationPresent( DigesterRuleList.class ) )
231            {
232                Annotation[] annotations = getAnnotationsArrayValue( annotation );
233                if ( annotations != null && annotations.length > 0 )
234                {
235                    // if it is an annotations array, process them
236                    for ( Annotation ptr : annotations )
237                    {
238                        handle( ptr, element );
239                    }
240                }
241            }
242            else if ( annotationType.isAnnotationPresent( DigesterRule.class ) )
243            {
244                DigesterRule digesterRule = annotationType.getAnnotation( DigesterRule.class );
245    
246                // the default behavior if the handler is not specified
247                Class<? extends AnnotationHandler<Annotation, AnnotatedElement>> handlerType =
248                    (Class<? extends AnnotationHandler<Annotation, AnnotatedElement>>) digesterRule.handledBy();
249                try
250                {
251                    AnnotationHandler<Annotation, AnnotatedElement> handler =
252                        annotationHandlerFactory.newInstance( handlerType );
253    
254                    // run!
255                    handler.handle( annotation, element, this.rulesBinder );
256                }
257                catch ( Exception e )
258                {
259                    rulesBinder.addError( e );
260                }
261            }
262        }
263    
264    }