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 */
019package org.tynamo.shiro.extension.authz.aop;
020
021import org.apache.shiro.authz.annotation.*;
022import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
023import org.tynamo.shiro.extension.authz.annotations.utils.casters.method.HandlerCreateVisitor;
024import org.tynamo.shiro.extension.authz.annotations.utils.casters.method.MethodAnnotationCaster;
025
026import java.lang.annotation.Annotation;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.util.*;
030
031
032/**
033 * Simple util class, help work with annotations and create interceptors
034 * based on annotations.
035 *
036 */
037public 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}