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