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.inject.Key;
22  import com.google.inject.PrivateModule;
23  import com.google.inject.Provider;
24  import com.google.inject.TypeLiteral;
25  import com.google.inject.binder.AnnotatedBindingBuilder;
26  import com.google.inject.binder.LinkedBindingBuilder;
27  import com.google.inject.matcher.Matchers;
28  import com.google.inject.multibindings.Multibinder;
29  import com.google.inject.spi.InjectionListener;
30  import com.google.inject.spi.TypeEncounter;
31  import com.google.inject.spi.TypeListener;
32  import com.google.inject.util.Types;
33  import org.apache.shiro.config.ConfigurationException;
34  import org.apache.shiro.env.Environment;
35  import org.apache.shiro.event.EventBus;
36  import org.apache.shiro.event.EventBusAware;
37  import org.apache.shiro.event.Subscribe;
38  import org.apache.shiro.event.support.DefaultEventBus;
39  import org.apache.shiro.lang.util.ClassUtils;
40  import org.apache.shiro.lang.util.Destroyable;
41  import org.apache.shiro.mgt.DefaultSecurityManager;
42  import org.apache.shiro.mgt.SecurityManager;
43  import org.apache.shiro.realm.Realm;
44  import org.apache.shiro.session.mgt.DefaultSessionManager;
45  import org.apache.shiro.session.mgt.SessionManager;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import javax.annotation.PreDestroy;
50  import java.lang.reflect.Method;
51  import java.util.Collection;
52  import java.util.Collections;
53  import java.util.List;
54  import java.util.Set;
55  import java.util.WeakHashMap;
56  
57  /**
58   * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
59   * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.
60   * At least one realm must be added by using
61   * {@link #bindRealm() bindRealm}.
62   */
63  public abstract class ShiroModule extends PrivateModule implements Destroyable {
64  
65      private final Logger log = LoggerFactory.getLogger(ShiroModule.class);
66  
67      private final Set<Destroyable> destroyables = Collections.newSetFromMap(new WeakHashMap<Destroyable, Boolean>());
68  
69      public void configure() {
70          // setup security manager
71          bindSecurityManager(bind(SecurityManager.class));
72          bindSessionManager(bind(SessionManager.class));
73          bindEnvironment(bind(Environment.class));
74          bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
75          bindEventBus(bind(EventBus.class));
76          bindListener(Matchers.any(), new SubscribedEventTypeListener());
77          bindListener(Matchers.any(), new EventBusAwareTypeListener());
78          final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
79              public void add(Destroyable destroyable) {
80                  ShiroModule.this.add(destroyable);
81              }
82  
83              @PreDestroy
84              public void destroy() {
85                  ShiroModule.this.destroy();
86              }
87          };
88          bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
89  
90          expose(SecurityManager.class);
91          expose(EventBus.class);
92  
93          configureShiro();
94          bind(realmCollectionKey())
95                  .to(realmSetKey());
96  
97          bind(DestroyableInjectionListener.DestroyableRegistry.class).toInstance(registry);
98          BeanTypeListener.ensureBeanTypeMapExists(binder());
99      }
100 
101     @SuppressWarnings({"unchecked"})
102     private Key<Set<Realm>> realmSetKey() {
103         return (Key<Set<Realm>>) Key.get(TypeLiteral.get(Types.setOf(Realm.class)));
104     }
105 
106     @SuppressWarnings({"unchecked"})
107     private Key<Collection<Realm>> realmCollectionKey() {
108         return (Key<Collection<Realm>>) Key.get(Types.newParameterizedType(Collection.class, Realm.class));
109     }
110 
111     /**
112      * Implement this method in order to configure your realms and any other Shiro customization you may need.
113      */
114     protected abstract void configureShiro();
115 
116     /**
117      * This is the preferred manner to bind a realm.
118      * The {@link org.apache.shiro.mgt.SecurityManager} will be injected with any Realm bound
119      * with this method.
120      *
121      * @return a binding builder for a realm
122      */
123     protected final LinkedBindingBuilder<Realm> bindRealm() {
124         Multibinder<Realm> multibinder = Multibinder.newSetBinder(binder(), Realm.class);
125         return multibinder.addBinding();
126     }
127 
128     /**
129      * Binds the security manager.  Override this method in order to provide your own security manager binding.
130      * <p/>
131      * By default, a {@link org.apache.shiro.mgt.DefaultSecurityManager} is bound as an eager singleton.
132      *
133      * @param bind
134      */
135     protected void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
136         try {
137             bind.toConstructor(DefaultSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
138         } catch (NoSuchMethodException e) {
139             throw new ConfigurationException("This really shouldn't happen."
140                     + " Either something has changed in Shiro, or there's a bug in "
141                     + ShiroModule.class.getSimpleName(), e);
142         }
143     }
144 
145     /**
146      * Binds the session manager.  Override this method in order to provide your own session manager binding.
147      * <p/>
148      * By default, a {@link org.apache.shiro.session.mgt.DefaultSessionManager} is bound as an eager singleton.
149      *
150      * @param bind
151      */
152     protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
153         bind.to(DefaultSessionManager.class).asEagerSingleton();
154     }
155 
156     /**
157      * Binds the environment.  Override this method in order to provide your own environment binding.
158      * <p/>
159      * By default, a {@link GuiceEnvironment} is bound as an eager singleton.
160      *
161      * @param bind
162      */
163     protected void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
164         bind.to(GuiceEnvironment.class).asEagerSingleton();
165     }
166 
167     /**
168      * Binds a key to use for injecting setters in shiro classes.
169      *
170      * @param typeLiteral the bean property type
171      * @param key         the key to use to satisfy the bean property dependency
172      * @param <T>
173      */
174     protected final <T> void bindBeanType(TypeLiteral<T> typeLiteral, Key<? extends T> key) {
175         BeanTypeListener.bindBeanType(binder(), typeLiteral, key);
176     }
177 
178     /**
179      * Binds the EventBus.  Override this method in order to provide your own {@link EventBus} binding.
180      *
181      * @param bind
182      * @since 1.4
183      */
184     protected void bindEventBus(AnnotatedBindingBuilder<EventBus> bind) {
185         bind.to(DefaultEventBus.class).asEagerSingleton();
186     }
187 
188     /**
189      * Destroys all beans created within this module that implement {@link org.apache.shiro.lang.util.Destroyable}.
190      * Should be called when this module will no longer be used.
191      *
192      * @throws Exception
193      */
194     public final void destroy() {
195         for (Destroyable destroyable : destroyables) {
196             try {
197                 destroyable.destroy();
198             } catch (Exception e) {
199                 log.warn("Error destroying component class: " + destroyable.getClass(), e);
200             }
201         }
202     }
203 
204     public void add(Destroyable destroyable) {
205         this.destroyables.add(destroyable);
206     }
207 
208     private final class SubscribedEventTypeListener implements TypeListener {
209         @Override
210         public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
211 
212             final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
213 
214             List<Method> methods = ClassUtils.getAnnotatedMethods(typeLiteral.getRawType(), Subscribe.class);
215             if (methods != null && !methods.isEmpty()) {
216                 typeEncounter.register(new InjectionListener<I>() {
217                     @Override
218                     public void afterInjection(Object o) {
219                         eventBusProvider.get().register(o);
220                     }
221                 });
222             }
223         }
224     }
225 
226     private final class EventBusAwareTypeListener implements TypeListener {
227         @Override
228         public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
229 
230             final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
231 
232             if (EventBusAware.class.isAssignableFrom(typeLiteral.getRawType())) {
233                 typeEncounter.register(new InjectionListener<I>() {
234                     @Override
235                     public void afterInjection(Object o) {
236                         ((EventBusAware) o).setEventBus(eventBusProvider.get());
237                     }
238                 });
239             }
240         }
241     }
242 }