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.PropertyUtils;
47  import org.apache.shiro.SecurityUtils;
48  
49  /**
50   * TypeListener that injects setter methods on Shiro objects.
51   */
52  class BeanTypeListener implements TypeListener {
53      public static final Package SHIRO_GUICE_PACKAGE = ShiroModule.class.getPackage();
54      public static final Package SHIRO_PACKAGE = SecurityUtils.class.getPackage();
55  
56      private static Matcher<Class> shiroMatcher = Matchers.inSubpackage(SHIRO_PACKAGE.getName());
57      private static Matcher<Class> shiroGuiceMatcher = Matchers.inSubpackage(SHIRO_GUICE_PACKAGE.getName());
58  
59      private static Matcher<Class> classMatcher = ShiroMatchers.ANY_PACKAGE.and(shiroMatcher.and(Matchers.not(shiroGuiceMatcher)));
60  
61      public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(classMatcher);
62  
63      private static final String BEAN_TYPE_MAP_NAME = "__SHIRO_BEAN_TYPES__";
64      static final Key<?> MAP_KEY = Key.get(Types.mapOf(TypeLiteral.class, BeanTypeKey.class), Names.named(BEAN_TYPE_MAP_NAME));
65  
66      private static final Set<Class<?>> WRAPPER_TYPES = new HashSet<Class<?>>(Arrays.asList(
67          Byte.class,
68          Boolean.class,
69          Character.class,
70          Double.class,
71          Float.class,
72          Integer.class,
73          Long.class,
74          Short.class,
75          Void.class));
76  
77      public <I> void hear(TypeLiteral<I> type, final TypeEncounter<I> encounter) {
78          PropertyDescriptor propertyDescriptors[] = PropertyUtils.getPropertyDescriptors(type.getRawType());
79          final Map<PropertyDescriptor, Key<?>> propertyDependencies = new HashMap<PropertyDescriptor, Key<?>>(propertyDescriptors.length);
80          final Provider<Injector> injectorProvider = encounter.getProvider(Injector.class);
81          for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
82              if (propertyDescriptor.getWriteMethod() != null && Modifier.isPublic(propertyDescriptor.getWriteMethod().getModifiers())) {
83                  Type propertyType = propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0];
84                  propertyDependencies.put(propertyDescriptor, createDependencyKey(propertyDescriptor, propertyType));
85              }
86          }
87          encounter.register(new MembersInjector<I>() {
88              public void injectMembers(I instance) {
89                  for (Map.Entry<PropertyDescriptor, Key<?>> dependency : propertyDependencies.entrySet()) {
90                      try {
91                          final Injector injector = injectorProvider.get();
92  
93                          Object value = injector.getInstance(getMappedKey(injector, dependency.getValue()));
94                          dependency.getKey().getWriteMethod().invoke(instance, value);
95  
96                      } catch (ConfigurationException e) {
97                          // This is ok, it simply means that we can't fulfill this dependency.
98                          // Is there a better way to do this?
99                      } catch (InvocationTargetException e) {
100                         throw new RuntimeException("Couldn't set property " + dependency.getKey().getDisplayName(), e);
101                     } catch (IllegalAccessException e) {
102                         throw new RuntimeException("We shouldn't have ever reached this point, we don't try to inject to non-accessible methods.", e);
103                     }
104                 }
105 
106             }
107         });
108     }
109 
110     private static Key<?> getMappedKey(Injector injector, Key<?> key) {
111         Map<TypeLiteral, BeanTypeKey> beanTypeMap = getBeanTypeMap(injector);
112         if(key.getAnnotation() == null && beanTypeMap.containsKey(key.getTypeLiteral())) {
113             return beanTypeMap.get(key.getTypeLiteral()).key;
114         } else {
115             return key;
116         }
117     }
118 
119     @SuppressWarnings({"unchecked"})
120     private static Map<TypeLiteral, BeanTypeKey> getBeanTypeMap(Injector injector) {
121         return (Map<TypeLiteral, BeanTypeKey>) injector.getInstance(MAP_KEY);
122     }
123 
124     private static Key<?> createDependencyKey(PropertyDescriptor propertyDescriptor, Type propertyType) {
125         if(requiresName(propertyType)) {
126             return Key.get(propertyType, Names.named("shiro." + propertyDescriptor.getName()));
127         } else {
128             return Key.get(propertyType);
129         }
130     }
131 
132     private static boolean requiresName(Type propertyType) {
133         if (propertyType instanceof Class) {
134             Class<?> aClass = (Class<?>) propertyType;
135             return aClass.isPrimitive() || aClass.isEnum() || WRAPPER_TYPES.contains(aClass) || CharSequence.class.isAssignableFrom(aClass);
136         } else {
137             return false;
138         }
139     }
140 
141     static void ensureBeanTypeMapExists(Binder binder) {
142         beanTypeMapBinding(binder).addBinding(TypeLiteral.get(BeanTypeKey.class)).toInstance(new BeanTypeKey(null));
143     }
144 
145     static <T> void bindBeanType(Binder binder, TypeLiteral<T> typeLiteral, Key<? extends T> key) {
146         beanTypeMapBinding(binder).addBinding(typeLiteral).toInstance(new BeanTypeKey(key));
147     }
148 
149     private static MapBinder<TypeLiteral, BeanTypeKey> beanTypeMapBinding(Binder binder) {
150         return MapBinder.newMapBinder(binder, TypeLiteral.class, BeanTypeKey.class, Names.named(BEAN_TYPE_MAP_NAME));
151     }
152 
153     private static class BeanTypeKey {
154         Key<?> key;
155 
156         private BeanTypeKey(Key<?> key) {
157             this.key = key;
158         }
159     }
160 }