001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.tynamo.shiro.extension.authz.aop;
020    
021    import org.apache.shiro.authz.annotation.*;
022    import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
023    import org.tynamo.shiro.extension.authz.annotations.utils.casters.method.HandlerCreateVisitor;
024    import org.tynamo.shiro.extension.authz.annotations.utils.casters.method.MethodAnnotationCaster;
025    
026    import java.lang.annotation.Annotation;
027    import java.lang.reflect.Method;
028    import java.lang.reflect.Modifier;
029    import java.util.*;
030    
031    
032    /**
033     * Simple util class, help work with annotations and create interceptors
034     * based on annotations.
035     *
036     */
037    public class AopHelper
038    {
039    
040            /**
041             * List annotations classes which can be applied (either method or a class).
042             */
043            private final static Collection<Class<? extends Annotation>> autorizationAnnotationClasses;
044    
045            /**
046             * Initialize annotations lists.
047             */
048            static
049            {
050                    autorizationAnnotationClasses = new ArrayList<Class<? extends Annotation>>(5);
051                    autorizationAnnotationClasses.add(RequiresPermissions.class);
052                    autorizationAnnotationClasses.add(RequiresRoles.class);
053                    autorizationAnnotationClasses.add(RequiresUser.class);
054                    autorizationAnnotationClasses.add(RequiresGuest.class);
055                    autorizationAnnotationClasses.add(RequiresAuthentication.class);
056            }
057    
058            /**
059             * Create {@link org.apache.shiro.authz.aop.AuthorizingAnnotationHandler}
060             * for annotation.
061             *
062             * @param annotation
063             * @return
064             */
065            public static AuthorizingAnnotationHandler createHandler(Annotation annotation)
066            {
067                    HandlerCreateVisitor visitor = new HandlerCreateVisitor();
068                    MethodAnnotationCaster.getInstance().accept(visitor, annotation);
069                    return visitor.getHandler();
070            }
071    
072            /**
073             * Create list of {@link org.tynamo.shiro.extension.authz.aop.SecurityInterceptor}
074             * instances for method. This method search all method and class annotations and use
075             * annotation data for create interceptors.
076             * <p/>
077             * This method considers only those annotations that have been declared
078             * in the set through parameters of the method and class, regardless of the
079             * inheritance or interface implementations
080             *
081             * @param method
082             * @param clazz
083             * @return
084             */
085            public static List<SecurityInterceptor> createSecurityInterceptors(Method method, Class<?> clazz)
086            {
087                    List<SecurityInterceptor> result = new ArrayList<SecurityInterceptor>();
088    
089                    if (isInterceptOnClassAnnotation(method.getModifiers()))
090                    {
091                            for (Class<? extends Annotation> ac :
092                                            getAutorizationAnnotationClasses())
093                            {
094                                    Annotation annotationOnClass = clazz.getAnnotation(ac);
095                                    if (annotationOnClass != null)
096                                    {
097                                            result.add(new DefaultSecurityInterceptor(annotationOnClass));
098                                    }
099                            }
100                    }
101    
102                    for (Class<? extends Annotation> ac :
103                                    getAutorizationAnnotationClasses())
104                    {
105                            Annotation annotation = method.getAnnotation(ac);
106                            if (annotation != null)
107                            {
108                                    result.add(new DefaultSecurityInterceptor(annotation));
109                            }
110                    }
111    
112                    return result;
113            }
114    
115            /**
116             * Create list of {@link org.tynamo.shiro.extension.authz.aop.SecurityInterceptor}
117             * instances for method. This method search all method and class annotations and use
118             * annotation data for create interceptors.
119             * <p/>
120             * In contrast of the {@link #createSecurityInterceptors(Method, Class)}, this method
121             * looking for the annotations in all interfaces, witch implement the targetClass.
122             * <p/>
123             * The following rules
124             * <ul>
125             * <li>If annotation on class presents, will be intercepted all methods in the class,
126             * that satisfy the {@link #isInterceptOnClassAnnotation(Method) rule}.</li>
127             * <li>Annotations on methods are <b>not</b> inherited.</li>
128             * <li>Annotations on classes are <b>not</b> inherited.</li>
129             * <li>The annotations are searched in all interfaces, witch implement the targetClass.</li>
130             * <ul>
131             *
132             * @param method
133             * @param targetClass
134             * @return
135             */
136            public static List<SecurityInterceptor> createSecurityInterceptorsSeeingInterfaces(Method method,
137                                                                                               Class<?> targetClass)
138            {
139                    List<SecurityInterceptor> interceptors = new ArrayList<SecurityInterceptor>();
140    
141                    method = findTargetMethod(method, targetClass);
142    
143                    interceptors.addAll(createSecurityInterceptors(method, targetClass));
144    
145                    Set<Class<?>> allInterfaces = new HashSet<Class<?>>();
146                    getAllInterfaces(allInterfaces, targetClass);
147    
148                    for (Class<?> intf : allInterfaces)
149                    {
150                            try
151                            {
152                                    Method candidate = intf.getMethod(method.getName(), method.getParameterTypes());
153                                    interceptors.addAll(createSecurityInterceptors(candidate, intf));
154                            } catch (SecurityException e)
155                            {
156                                    new RuntimeException(e);
157                            } catch (NoSuchMethodException e)
158                            {
159                                    //nothing to do
160                            }
161                    }
162    
163                    return interceptors;
164            }
165    
166            /**
167             * Find the target method of interface.
168             * <p/>
169             * Ensure this: If a class have an interface, then method parameter from interface and
170             * targetClass implementation.
171             * <p/>
172             */
173            public static Method findTargetMethod(Method method, Class<?> targetClass)
174            {
175                    try
176                    {
177                            method = targetClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
178                    } catch (SecurityException e)
179                    {
180                            throw new RuntimeException(e);
181                    } catch (NoSuchMethodException e)
182                    {
183                            //That's ok
184                    }
185                    return method;
186            }
187    
188            /**
189             * Recursively finds all the interfaces.
190             *
191             * @param searchInterfaces set of result interfaces
192             * @param clazz
193             */
194            private static void getAllInterfaces(Set<Class<?>> searchInterfaces, Class<?> clazz)
195            {
196                    while (clazz != null)
197                    {
198                            searchInterfaces.addAll(Arrays.asList(clazz.getInterfaces()));
199                            for (Class<?> intf : clazz.getInterfaces())
200                            {
201                                    getAllInterfaces(searchInterfaces, intf);
202                            }
203                            clazz = clazz.getSuperclass();
204                    }
205            }
206    
207            /**
208             * Rule under which determined the fate of the class contains annotation.
209             * <p/>
210             * All public and protected methods.
211             */
212            public static boolean isInterceptOnClassAnnotation(int modifiers)
213            {
214                    return Modifier.isPublic(modifiers)
215                                    || Modifier.isProtected(modifiers);
216            }
217    
218            public static Collection<Class<? extends Annotation>> getAutorizationAnnotationClasses()
219            {
220                    return autorizationAnnotationClasses;
221            }
222    }