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.apache.shiro.guice;
020
021import com.google.inject.Key;
022import com.google.inject.PrivateModule;
023import com.google.inject.Provider;
024import com.google.inject.TypeLiteral;
025import com.google.inject.binder.AnnotatedBindingBuilder;
026import com.google.inject.binder.LinkedBindingBuilder;
027import com.google.inject.matcher.Matchers;
028import com.google.inject.multibindings.Multibinder;
029import com.google.inject.spi.InjectionListener;
030import com.google.inject.spi.TypeEncounter;
031import com.google.inject.spi.TypeListener;
032import com.google.inject.util.Types;
033import org.apache.shiro.config.ConfigurationException;
034import org.apache.shiro.env.Environment;
035import org.apache.shiro.event.EventBus;
036import org.apache.shiro.event.EventBusAware;
037import org.apache.shiro.event.Subscribe;
038import org.apache.shiro.event.support.DefaultEventBus;
039import org.apache.shiro.lang.util.ClassUtils;
040import org.apache.shiro.lang.util.Destroyable;
041import org.apache.shiro.mgt.DefaultSecurityManager;
042import org.apache.shiro.mgt.SecurityManager;
043import org.apache.shiro.realm.Realm;
044import org.apache.shiro.session.mgt.DefaultSessionManager;
045import org.apache.shiro.session.mgt.SessionManager;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import javax.annotation.PreDestroy;
050import java.lang.reflect.Method;
051import java.util.Collection;
052import java.util.Collections;
053import java.util.List;
054import java.util.Set;
055import java.util.WeakHashMap;
056
057/**
058 * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
059 * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.
060 * At least one realm must be added by using
061 * {@link #bindRealm() bindRealm}.
062 */
063public abstract class ShiroModule extends PrivateModule implements Destroyable {
064
065    private final Logger log = LoggerFactory.getLogger(ShiroModule.class);
066
067    private final Set<Destroyable> destroyables = Collections.newSetFromMap(new WeakHashMap<Destroyable, Boolean>());
068
069    public void configure() {
070        // setup security manager
071        bindSecurityManager(bind(SecurityManager.class));
072        bindSessionManager(bind(SessionManager.class));
073        bindEnvironment(bind(Environment.class));
074        bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
075        bindEventBus(bind(EventBus.class));
076        bindListener(Matchers.any(), new SubscribedEventTypeListener());
077        bindListener(Matchers.any(), new EventBusAwareTypeListener());
078        final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
079            public void add(Destroyable destroyable) {
080                ShiroModule.this.add(destroyable);
081            }
082
083            @PreDestroy
084            public void destroy() {
085                ShiroModule.this.destroy();
086            }
087        };
088        bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
089
090        expose(SecurityManager.class);
091        expose(EventBus.class);
092
093        configureShiro();
094        bind(realmCollectionKey())
095                .to(realmSetKey());
096
097        bind(DestroyableInjectionListener.DestroyableRegistry.class).toInstance(registry);
098        BeanTypeListener.ensureBeanTypeMapExists(binder());
099    }
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}