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     */
019    package org.apache.shiro.guice;
020    
021    import com.google.common.collect.Sets;
022    import com.google.inject.Key;
023    import com.google.inject.PrivateModule;
024    import com.google.inject.TypeLiteral;
025    import com.google.inject.binder.AnnotatedBindingBuilder;
026    import com.google.inject.binder.LinkedBindingBuilder;
027    import com.google.inject.multibindings.Multibinder;
028    import com.google.inject.util.Types;
029    import org.apache.shiro.config.ConfigurationException;
030    import org.apache.shiro.env.Environment;
031    import org.apache.shiro.mgt.DefaultSecurityManager;
032    import org.apache.shiro.mgt.SecurityManager;
033    import org.apache.shiro.realm.Realm;
034    import org.apache.shiro.session.mgt.DefaultSessionManager;
035    import org.apache.shiro.session.mgt.SessionManager;
036    import org.apache.shiro.util.Destroyable;
037    
038    import javax.annotation.PreDestroy;
039    import java.util.Collection;
040    import java.util.Set;
041    import java.util.WeakHashMap;
042    
043    
044    /**
045     * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
046     * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.  At least one realm must be added by using
047     * {@link #bindRealm() bindRealm}.
048     */
049    public abstract class ShiroModule extends PrivateModule implements Destroyable {
050    
051        private Set<Destroyable> destroyables = Sets.newSetFromMap(new WeakHashMap<Destroyable, Boolean>());
052    
053        public void configure() {
054            // setup security manager
055            bindSecurityManager(bind(SecurityManager.class));
056            bindSessionManager(bind(SessionManager.class));
057            bindEnvironment(bind(Environment.class));
058            bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
059            final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
060                public void add(Destroyable destroyable) {
061                    ShiroModule.this.add(destroyable);
062                }
063    
064                @PreDestroy
065                public void destroy() throws Exception {
066                    ShiroModule.this.destroy();
067                }
068            };
069            bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
070    
071            expose(SecurityManager.class);
072    
073            configureShiro();
074            bind(realmCollectionKey())
075                    .to(realmSetKey());
076    
077            bind(DestroyableInjectionListener.DestroyableRegistry.class).toInstance(registry);
078            BeanTypeListener.ensureBeanTypeMapExists(binder());
079        }
080    
081        @SuppressWarnings({"unchecked"})
082        private Key<Set<Realm>> realmSetKey() {
083            return (Key<Set<Realm>>) Key.get(TypeLiteral.get(Types.setOf(Realm.class)));
084        }
085    
086        @SuppressWarnings({"unchecked"})
087        private Key<Collection<Realm>> realmCollectionKey() {
088            return (Key<Collection<Realm>>) Key.get(Types.newParameterizedType(Collection.class, Realm.class));
089        }
090    
091        /**
092         * Implement this method in order to configure your realms and any other Shiro customization you may need.
093         */
094        protected abstract void configureShiro();
095    
096        /**
097         * This is the preferred manner to bind a realm.  The {@link org.apache.shiro.mgt.SecurityManager} will be injected with any Realm bound
098         * with this method.
099         *
100         * @return a binding builder for a realm
101         */
102        protected final LinkedBindingBuilder<Realm> bindRealm() {
103            Multibinder<Realm> multibinder = Multibinder.newSetBinder(binder(), Realm.class);
104            return multibinder.addBinding();
105        }
106    
107        /**
108         * Binds the security manager.  Override this method in order to provide your own security manager binding.
109         * <p/>
110         * By default, a {@link org.apache.shiro.mgt.DefaultSecurityManager} is bound as an eager singleton.
111         *
112         * @param bind
113         */
114        protected void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
115            try {
116                bind.toConstructor(DefaultSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
117            } catch (NoSuchMethodException e) {
118                throw new ConfigurationException("This really shouldn't happen.  Either something has changed in Shiro, or there's a bug in " + ShiroModule.class.getSimpleName(), e);
119            }
120        }
121    
122        /**
123         * Binds the session manager.  Override this method in order to provide your own session manager binding.
124         * <p/>
125         * By default, a {@link org.apache.shiro.session.mgt.DefaultSessionManager} is bound as an eager singleton.
126         *
127         * @param bind
128         */
129        protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
130            bind.to(DefaultSessionManager.class).asEagerSingleton();
131        }
132    
133        /**
134         * Binds the environment.  Override this method in order to provide your own environment binding.
135         * <p/>
136         * By default, a {@link GuiceEnvironment} is bound as an eager singleton.
137         *
138         * @param bind
139         */
140        protected void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
141            bind.to(GuiceEnvironment.class).asEagerSingleton();
142        }
143    
144        /**
145         * Binds a key to use for injecting setters in shiro classes.
146         * @param typeLiteral the bean property type
147         * @param key the key to use to satisfy the bean property dependency
148         * @param <T>
149         */
150        protected final <T> void bindBeanType(TypeLiteral<T> typeLiteral, Key<? extends T> key) {
151            BeanTypeListener.bindBeanType(binder(), typeLiteral, key);
152        }
153    
154        /**
155         * Destroys all beans created within this module that implement {@link org.apache.shiro.util.Destroyable}.  Should be called when this
156         * module will no longer be used.
157         *
158         * @throws Exception
159         */
160        public final void destroy() throws Exception {
161            for (Destroyable destroyable : destroyables) {
162                destroyable.destroy();
163            }
164        }
165    
166        public void add(Destroyable destroyable) {
167            this.destroyables.add(destroyable);
168        }
169    }