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 java.lang.reflect.Method;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.List;
025import java.util.Set;
026import java.util.WeakHashMap;
027
028import javax.annotation.PreDestroy;
029
030import com.google.inject.Provider;
031import com.google.inject.matcher.Matchers;
032import com.google.inject.spi.InjectionListener;
033import com.google.inject.spi.TypeEncounter;
034import com.google.inject.spi.TypeListener;
035import org.apache.shiro.config.ConfigurationException;
036import org.apache.shiro.env.Environment;
037import org.apache.shiro.event.EventBus;
038import org.apache.shiro.event.EventBusAware;
039import org.apache.shiro.event.Subscribe;
040import org.apache.shiro.event.support.DefaultEventBus;
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.apache.shiro.util.ClassUtils;
047import org.apache.shiro.util.Destroyable;
048
049import com.google.inject.Key;
050import com.google.inject.PrivateModule;
051import com.google.inject.TypeLiteral;
052import com.google.inject.binder.AnnotatedBindingBuilder;
053import com.google.inject.binder.LinkedBindingBuilder;
054import com.google.inject.multibindings.Multibinder;
055import com.google.inject.util.Types;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059
060/**
061 * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
062 * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.  At least one realm must be added by using
063 * {@link #bindRealm() bindRealm}.
064 */
065public abstract class ShiroModule extends PrivateModule implements Destroyable {
066
067    private final Logger log = LoggerFactory.getLogger(ShiroModule.class);
068
069        private Set<Destroyable> destroyables = Collections.newSetFromMap(new WeakHashMap<Destroyable, Boolean>());
070    public void configure() {
071        // setup security manager
072        bindSecurityManager(bind(SecurityManager.class));
073        bindSessionManager(bind(SessionManager.class));
074        bindEnvironment(bind(Environment.class));
075        bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
076        bindEventBus(bind(EventBus.class));
077        bindListener(Matchers.any(), new SubscribedEventTypeListener());
078        bindListener(Matchers.any(), new EventBusAwareTypeListener());
079        final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
080            public void add(Destroyable destroyable) {
081                ShiroModule.this.add(destroyable);
082            }
083
084            @PreDestroy
085            public void destroy() {
086                ShiroModule.this.destroy();
087            }
088        };
089        bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
090
091        expose(SecurityManager.class);
092        expose(EventBus.class);
093
094        configureShiro();
095        bind(realmCollectionKey())
096                .to(realmSetKey());
097
098        bind(DestroyableInjectionListener.DestroyableRegistry.class).toInstance(registry);
099        BeanTypeListener.ensureBeanTypeMapExists(binder());
100    }
101
102    @SuppressWarnings({"unchecked"})
103    private Key<Set<Realm>> realmSetKey() {
104        return (Key<Set<Realm>>) Key.get(TypeLiteral.get(Types.setOf(Realm.class)));
105    }
106
107    @SuppressWarnings({"unchecked"})
108    private Key<Collection<Realm>> realmCollectionKey() {
109        return (Key<Collection<Realm>>) Key.get(Types.newParameterizedType(Collection.class, Realm.class));
110    }
111
112    /**
113     * Implement this method in order to configure your realms and any other Shiro customization you may need.
114     */
115    protected abstract void configureShiro();
116
117    /**
118     * This is the preferred manner to bind a realm.  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.  Either something has changed in Shiro, or there's a bug in " + ShiroModule.class.getSimpleName(), e);
140        }
141    }
142
143    /**
144     * Binds the session manager.  Override this method in order to provide your own session manager binding.
145     * <p/>
146     * By default, a {@link org.apache.shiro.session.mgt.DefaultSessionManager} is bound as an eager singleton.
147     *
148     * @param bind
149     */
150    protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
151        bind.to(DefaultSessionManager.class).asEagerSingleton();
152    }
153
154    /**
155     * Binds the environment.  Override this method in order to provide your own environment binding.
156     * <p/>
157     * By default, a {@link GuiceEnvironment} is bound as an eager singleton.
158     *
159     * @param bind
160     */
161    protected void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
162        bind.to(GuiceEnvironment.class).asEagerSingleton();
163    }
164
165    /**
166     * Binds a key to use for injecting setters in shiro classes.
167     * @param typeLiteral the bean property type
168     * @param key the key to use to satisfy the bean property dependency
169     * @param <T>
170     */
171    protected final <T> void bindBeanType(TypeLiteral<T> typeLiteral, Key<? extends T> key) {
172        BeanTypeListener.bindBeanType(binder(), typeLiteral, key);
173    }
174
175    /**
176     * Binds the EventBus.  Override this method in order to provide your own {@link EventBus} binding.
177     * @param bind
178     * @since 1.4
179     */
180    protected void bindEventBus(AnnotatedBindingBuilder<EventBus> bind) {
181        bind.to(DefaultEventBus.class).asEagerSingleton();
182    }
183
184    /**
185     * Destroys all beans created within this module that implement {@link org.apache.shiro.util.Destroyable}.  Should be called when this
186     * module will no longer be used.
187     *
188     * @throws Exception
189     */
190    public final void destroy() {
191        for (Destroyable destroyable : destroyables) {
192            try {
193                destroyable.destroy();
194            }
195            catch(Exception e) {
196                log.warn("Error destroying component class: " + destroyable.getClass(), e);
197            }
198        }
199    }
200
201    public void add(Destroyable destroyable) {
202        this.destroyables.add(destroyable);
203    }
204
205    private class SubscribedEventTypeListener implements TypeListener {
206        @Override
207        public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
208
209            final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
210
211            List<Method> methods = ClassUtils.getAnnotatedMethods(typeLiteral.getRawType(), Subscribe.class);
212            if (methods != null && !methods.isEmpty()) {
213                typeEncounter.register( new InjectionListener<I>() {
214                    @Override
215                    public void afterInjection(Object o) {
216                        eventBusProvider.get().register(o);
217                    }
218                });
219            }
220        }
221    }
222
223    private class EventBusAwareTypeListener implements TypeListener {
224        @Override
225        public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
226
227            final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
228
229            if (EventBusAware.class.isAssignableFrom(typeLiteral.getRawType())) {
230                typeEncounter.register( new InjectionListener<I>() {
231                    @Override
232                    public void afterInjection(Object o) {
233                        ((EventBusAware)o).setEventBus(eventBusProvider.get());
234                    }
235                });
236            }
237        }
238    }
239}