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}