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.util.Collection;
022import java.util.Collections;
023import java.util.Set;
024import java.util.WeakHashMap;
025
026import javax.annotation.PreDestroy;
027
028import org.apache.shiro.config.ConfigurationException;
029import org.apache.shiro.env.Environment;
030import org.apache.shiro.mgt.DefaultSecurityManager;
031import org.apache.shiro.mgt.SecurityManager;
032import org.apache.shiro.realm.Realm;
033import org.apache.shiro.session.mgt.DefaultSessionManager;
034import org.apache.shiro.session.mgt.SessionManager;
035import org.apache.shiro.util.Destroyable;
036
037import com.google.inject.Key;
038import com.google.inject.PrivateModule;
039import com.google.inject.TypeLiteral;
040import com.google.inject.binder.AnnotatedBindingBuilder;
041import com.google.inject.binder.LinkedBindingBuilder;
042import com.google.inject.multibindings.Multibinder;
043import 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 */
051public 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}