001package 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
022import static org.apache.commons.digester3.annotations.utils.AnnotationUtils.getAnnotationsArrayValue;
023
024import java.lang.annotation.Annotation;
025import java.lang.reflect.AnnotatedElement;
026import java.lang.reflect.Constructor;
027import java.lang.reflect.Field;
028import java.lang.reflect.Method;
029import java.security.AccessController;
030import java.security.PrivilegedAction;
031
032import org.apache.commons.digester3.Rule;
033import org.apache.commons.digester3.annotations.reflect.MethodArgument;
034import 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 */
042public 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}