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 com.google.common.primitives.Primitives;
22  import com.google.inject.*;
23  import com.google.inject.matcher.Matcher;
24  import com.google.inject.matcher.Matchers;
25  import com.google.inject.multibindings.MapBinder;
26  import com.google.inject.name.Names;
27  import com.google.inject.spi.TypeEncounter;
28  import com.google.inject.spi.TypeListener;
29  import com.google.inject.util.Types;
30  import org.apache.commons.beanutils.PropertyUtils;
31  import org.apache.shiro.SecurityUtils;
32  
33  import java.beans.PropertyDescriptor;
34  import java.lang.reflect.InvocationTargetException;
35  import java.lang.reflect.Modifier;
36  import java.lang.reflect.Type;
37  import java.util.HashMap;
38  import java.util.Map;
39  
40  /**
41   * TypeListener that injects setter methods on Shiro objects.
42   */
43  class BeanTypeListener implements TypeListener {
44      public static final Package SHIRO_GUICE_PACKAGE = ShiroModule.class.getPackage();
45      public static final Package SHIRO_PACKAGE = SecurityUtils.class.getPackage();
46  
47      private static Matcher<Class> shiroMatcher = Matchers.inSubpackage(SHIRO_PACKAGE.getName());
48      private static Matcher<Class> shiroGuiceMatcher = Matchers.inSubpackage(SHIRO_GUICE_PACKAGE.getName());
49  
50      private static Matcher<Class> classMatcher = ShiroMatchers.ANY_PACKAGE.and(shiroMatcher.and(Matchers.not(shiroGuiceMatcher)));
51  
52      public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(classMatcher);
53  
54      private static final String BEAN_TYPE_MAP_NAME = "__SHIRO_BEAN_TYPES__";
55      static final Key<?> MAP_KEY = Key.get(Types.mapOf(TypeLiteral.class, BeanTypeKey.class), Names.named(BEAN_TYPE_MAP_NAME));
56  
57      public <I> void hear(TypeLiteral<I> type, final TypeEncounter<I> encounter) {
58          PropertyDescriptor propertyDescriptors[] = PropertyUtils.getPropertyDescriptors(type.getRawType());
59          final Map<PropertyDescriptor, Key<?>> propertyDependencies = new HashMap<PropertyDescriptor, Key<?>>(propertyDescriptors.length);
60          final Provider<Injector> injectorProvider = encounter.getProvider(Injector.class);
61          for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
62              if (propertyDescriptor.getWriteMethod() != null && Modifier.isPublic(propertyDescriptor.getWriteMethod().getModifiers())) {
63                  Type propertyType = propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0];
64                  propertyDependencies.put(propertyDescriptor, createDependencyKey(propertyDescriptor, propertyType));
65              }
66          }
67          encounter.register(new MembersInjector<I>() {
68              public void injectMembers(I instance) {
69                  for (Map.Entry<PropertyDescriptor, Key<?>> dependency : propertyDependencies.entrySet()) {
70                      try {
71                          final Injector injector = injectorProvider.get();
72  
73                          Object value = injector.getInstance(getMappedKey(injector, dependency.getValue()));
74                          dependency.getKey().getWriteMethod().invoke(instance, value);
75  
76                      } catch (ConfigurationException e) {
77                          // This is ok, it simply means that we can't fulfill this dependency.
78                          // Is there a better way to do this?
79                      } catch (InvocationTargetException e) {
80                          throw new RuntimeException("Couldn't set property " + dependency.getKey().getDisplayName(), e);
81                      } catch (IllegalAccessException e) {
82                          throw new RuntimeException("We shouldn't have ever reached this point, we don't try to inject to non-accessible methods.", e);
83                      }
84                  }
85  
86              }
87          });
88      }
89  
90      private static Key<?> getMappedKey(Injector injector, Key<?> key) {
91          Map<TypeLiteral, BeanTypeKey> beanTypeMap = getBeanTypeMap(injector);
92          if(key.getAnnotation() == null && beanTypeMap.containsKey(key.getTypeLiteral())) {
93              return beanTypeMap.get(key.getTypeLiteral()).key;
94          } else {
95              return key;
96          }
97      }
98  
99      @SuppressWarnings({"unchecked"})
100     private static Map<TypeLiteral, BeanTypeKey> getBeanTypeMap(Injector injector) {
101         return (Map<TypeLiteral, BeanTypeKey>) injector.getInstance(MAP_KEY);
102     }
103 
104     private static Key<?> createDependencyKey(PropertyDescriptor propertyDescriptor, Type propertyType) {
105         if(requiresName(propertyType)) {
106             return Key.get(propertyType, Names.named("shiro." + propertyDescriptor.getName()));
107         } else {
108             return Key.get(propertyType);
109         }
110     }
111 
112     private static boolean requiresName(Type propertyType) {
113         if (propertyType instanceof Class) {
114             Class<?> aClass = (Class<?>) propertyType;
115             return aClass.isPrimitive() || aClass.isEnum() || Primitives.isWrapperType(aClass) || CharSequence.class.isAssignableFrom(aClass);
116         } else {
117             return false;
118         }
119     }
120 
121     static void ensureBeanTypeMapExists(Binder binder) {
122         beanTypeMapBinding(binder).addBinding(TypeLiteral.get(BeanTypeKey.class)).toInstance(new BeanTypeKey(null));
123     }
124 
125     static <T> void bindBeanType(Binder binder, TypeLiteral<T> typeLiteral, Key<? extends T> key) {
126         beanTypeMapBinding(binder).addBinding(typeLiteral).toInstance(new BeanTypeKey(key));
127     }
128 
129     private static MapBinder<TypeLiteral, BeanTypeKey> beanTypeMapBinding(Binder binder) {
130         return MapBinder.newMapBinder(binder, TypeLiteral.class, BeanTypeKey.class, Names.named(BEAN_TYPE_MAP_NAME));
131     }
132 
133     private static class BeanTypeKey {
134         Key<?> key;
135 
136         private BeanTypeKey(Key<?> key) {
137             this.key = key;
138         }
139     }
140 }