View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.guice;
20  
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Modifier;
24  import java.lang.reflect.Type;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import com.google.inject.Binder;
32  import com.google.inject.ConfigurationException;
33  import com.google.inject.Injector;
34  import com.google.inject.Key;
35  import com.google.inject.MembersInjector;
36  import com.google.inject.Provider;
37  import com.google.inject.TypeLiteral;
38  import com.google.inject.matcher.Matcher;
39  import com.google.inject.matcher.Matchers;
40  import com.google.inject.multibindings.MapBinder;
41  import com.google.inject.name.Names;
42  import com.google.inject.spi.TypeEncounter;
43  import com.google.inject.spi.TypeListener;
44  import com.google.inject.util.Types;
45  
46  import org.apache.commons.beanutils.BeanUtilsBean;
47  import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
48  import org.apache.shiro.SecurityUtils;
49  
50  /**
51   * TypeListener that injects setter methods on Shiro objects.
52   */
53  class BeanTypeListener implements TypeListener {
54      public static final Package SHIRO_GUICE_PACKAGE = ShiroModule.class.getPackage();
55      public static final Package SHIRO_PACKAGE = SecurityUtils.class.getPackage();
56  
57      private static Matcher<Class> shiroMatcher = Matchers.inSubpackage(SHIRO_PACKAGE.getName());
58      private static Matcher<Class> shiroGuiceMatcher = Matchers.inSubpackage(SHIRO_GUICE_PACKAGE.getName());
59  
60      private static Matcher<Class> classMatcher = ShiroMatchers.ANY_PACKAGE.and(shiroMatcher.and(Matchers.not(shiroGuiceMatcher)));
61  
62      public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(classMatcher);
63  
64      private static final String BEAN_TYPE_MAP_NAME = "__SHIRO_BEAN_TYPES__";
65      static final Key<?> MAP_KEY = Key.get(Types.mapOf(TypeLiteral.class, BeanTypeKey.class), Names.named(BEAN_TYPE_MAP_NAME));
66  
67      /**
68       * @since 1.4
69       */
70      private final BeanUtilsBean beanUtilsBean;
71  
72      private static final Set<Class<?>> WRAPPER_TYPES = new HashSet<Class<?>>(Arrays.asList(
73          Byte.class,
74          Boolean.class,
75          Character.class,
76          Double.class,
77          Float.class,
78          Integer.class,
79          Long.class,
80          Short.class,
81          Void.class));
82  
83      public BeanTypeListener() {
84          // SHIRO-619
85          beanUtilsBean = new BeanUtilsBean();
86          beanUtilsBean.getPropertyUtils().addBeanIntrospector(
87                  SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
88      }
89  
90      public <I> void hear(TypeLiteral<I> type, final TypeEncounter<I> encounter) {
91          PropertyDescriptor propertyDescriptors[] = beanUtilsBean.getPropertyUtils().getPropertyDescriptors(type.getRawType());
92          final Map<PropertyDescriptor, Key<?>> propertyDependencies = new HashMap<PropertyDescriptor, Key<?>>(propertyDescriptors.length);
93          final Provider<Injector> injectorProvider = encounter.getProvider(Injector.class);
94          for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
95              if (propertyDescriptor.getWriteMethod() != null && Modifier.isPublic(propertyDescriptor.getWriteMethod().getModifiers())) {
96                  Type propertyType = propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0];
97                  propertyDependencies.put(propertyDescriptor, createDependencyKey(propertyDescriptor, propertyType));
98              }
99          }
100         encounter.register(new MembersInjector<I>() {
101             public void injectMembers(I instance) {
102                 for (Map.Entry<PropertyDescriptor, Key<?>> dependency : propertyDependencies.entrySet()) {
103                     try {
104                         final Injector injector = injectorProvider.get();
105 
106                         Object value = injector.getInstance(getMappedKey(injector, dependency.getValue()));
107                         dependency.getKey().getWriteMethod().invoke(instance, value);
108 
109                     } catch (ConfigurationException e) {
110                         // This is ok, it simply means that we can't fulfill this dependency.
111                         // Is there a better way to do this?
112                     } catch (InvocationTargetException e) {
113                         throw new RuntimeException("Couldn't set property " + dependency.getKey().getDisplayName(), e);
114                     } catch (IllegalAccessException e) {
115                         throw new RuntimeException("We shouldn't have ever reached this point, we don't try to inject to non-accessible methods.", e);
116                     }
117                 }
118 
119             }
120         });
121     }
122 
123     private static Key<?> getMappedKey(Injector injector, Key<?> key) {
124         Map<TypeLiteral, BeanTypeKey> beanTypeMap = getBeanTypeMap(injector);
125         if(key.getAnnotation() == null && beanTypeMap.containsKey(key.getTypeLiteral())) {
126             return beanTypeMap.get(key.getTypeLiteral()).key;
127         } else {
128             return key;
129         }
130     }
131 
132     @SuppressWarnings({"unchecked"})
133     private static Map<TypeLiteral, BeanTypeKey> getBeanTypeMap(Injector injector) {
134         return (Map<TypeLiteral, BeanTypeKey>) injector.getInstance(MAP_KEY);
135     }
136 
137     private static Key<?> createDependencyKey(PropertyDescriptor propertyDescriptor, Type propertyType) {
138         if(requiresName(propertyType)) {
139             return Key.get(propertyType, Names.named("shiro." + propertyDescriptor.getName()));
140         } else {
141             return Key.get(propertyType);
142         }
143     }
144 
145     private static boolean requiresName(Type propertyType) {
146         if (propertyType instanceof Class) {
147             Class<?> aClass = (Class<?>) propertyType;
148             return aClass.isPrimitive() || aClass.isEnum() || WRAPPER_TYPES.contains(aClass) || CharSequence.class.isAssignableFrom(aClass);
149         } else {
150             return false;
151         }
152     }
153 
154     static void ensureBeanTypeMapExists(Binder binder) {
155         beanTypeMapBinding(binder).addBinding(TypeLiteral.get(BeanTypeKey.class)).toInstance(new BeanTypeKey(null));
156     }
157 
158     static <T> void bindBeanType(Binder binder, TypeLiteral<T> typeLiteral, Key<? extends T> key) {
159         beanTypeMapBinding(binder).addBinding(typeLiteral).toInstance(new BeanTypeKey(key));
160     }
161 
162     private static MapBinder<TypeLiteral, BeanTypeKey> beanTypeMapBinding(Binder binder) {
163         return MapBinder.newMapBinder(binder, TypeLiteral.class, BeanTypeKey.class, Names.named(BEAN_TYPE_MAP_NAME));
164     }
165 
166     private static class BeanTypeKey {
167         Key<?> key;
168 
169         private BeanTypeKey(Key<?> key) {
170             this.key = key;
171         }
172     }
173 }