001    /*
002     * Copyright (c) 2002-2005 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.security;
017    
018    import java.lang.ref.WeakReference;
019    import java.util.ArrayList;
020    import java.util.Collections;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    import org.springframework.beans.BeansException;
028    import org.springframework.beans.factory.config.BeanPostProcessor;
029    import org.springframework.context.ApplicationContext;
030    import org.springframework.context.ApplicationContextAware;
031    import org.springframework.context.ApplicationEvent;
032    import org.springframework.context.ApplicationListener;
033    import org.springframework.security.Authentication;
034    
035    /**
036     * This class performs two main functions:
037     * <ol>
038     * <li>It is a bean post-processor that will set the current authentication token on any
039     * newly created beans that implement {@link AuthenticationAware}.</li>
040     * <li>It listens for application {@link ClientSecurityEvent}s and updates all the beans
041     * in the context that implement either {@link AuthenticationAware} or {@link LoginAware}
042     * according to the event received.</li>
043     * </ol>
044     * In order for all this to take place, a singleton, non-lazy instance of this class must
045     * be defined in the Spring ApplicationContext. This would be done like this:
046     * 
047     * <pre>
048     *              &lt;bean id=&quot;securityAwareConfigurer&quot;
049     *                   class=&quot;org.springframework.richclient.security.SecurityAwareConfigurer&quot;
050     *                   lazy-init=&quot;false&quot;/&gt;
051     * </pre>
052     * 
053     * @author Larry Streepy
054     * @author Andy Depue
055     * @author Inspiration from Ben Alex
056     * 
057     * @see org.springframework.richclient.security.AuthenticationAware
058     * @see org.springframework.richclient.security.LoginAware
059     * @see org.springframework.richclient.security.ClientSecurityEvent
060     * 
061     */
062    public class SecurityAwareConfigurer implements ApplicationListener, ApplicationContextAware, BeanPostProcessor {
063    
064        private final Log logger = LogFactory.getLog( getClass() );
065    
066        private ApplicationContext applicationContext;
067    
068        private final List nonSingletonListeners = Collections.synchronizedList( new ArrayList() );
069    
070        private Authentication currentAuthentication = null; // Until we know it
071    
072        /**
073         * Get the installed application context.
074         * @return context
075         */
076        public ApplicationContext getApplicationContext() {
077            return applicationContext;
078        }
079    
080        /**
081         * Broadcast an authentication event to all the AuthenticationAware beans.
082         * @param authentication token
083         */
084        protected void broadcastAuthentication(Authentication authentication) {
085            if( logger.isDebugEnabled() )
086                logger.debug( "BROADCAST authentication: token=" + authentication );
087    
088            // Save this for any new beans that we post-process
089            currentAuthentication = authentication;
090    
091            final Iterator iter = getBeansToUpdate( AuthenticationAware.class ).iterator();
092            while( iter.hasNext() ) {
093                ((AuthenticationAware) iter.next()).setAuthenticationToken( authentication );
094            }
095        }
096    
097        /**
098         * Broadcast a Login event to all the LoginAware beans.
099         * @param authentication token
100         */
101        protected void broadcastLogin(Authentication authentication) {
102            if( logger.isDebugEnabled() )
103                logger.debug( "BROADCAST login: token=" + authentication );
104    
105            final Iterator iter = getBeansToUpdate( LoginAware.class ).iterator();
106            while( iter.hasNext() ) {
107                ((LoginAware) iter.next()).userLogin( authentication );
108            }
109        }
110    
111        /**
112         * Broadcast a Logout event to all the LoginAware beans.
113         * @param authentication token
114         */
115        protected void broadcastLogout(Authentication authentication) {
116            if( logger.isDebugEnabled() )
117                logger.debug( "BROADCAST logout: token=" + authentication );
118    
119            final Iterator iter = getBeansToUpdate( LoginAware.class ).iterator();
120            while( iter.hasNext() ) {
121                ((LoginAware) iter.next()).userLogout( authentication );
122            }
123        }
124    
125        /**
126         * Construct the list of all the beans we need to update.
127         * @param beanType Type of bean to locate
128         * @return List of all beans to udpate.
129         */
130        protected List getBeansToUpdate(Class beanType) {
131            final ApplicationContext ac = getApplicationContext();
132            final List listeners = new ArrayList();
133    
134            if( ac != null ) {
135                if( logger.isDebugEnabled() )
136                    logger.debug( "Constructing list of beans to notify; bean type=" + beanType.getName() );
137    
138                final Map map = ac.getBeansOfType( beanType, false, true );
139    
140                if( logger.isDebugEnabled() )
141                    logger.debug( "bean map: " + map );
142    
143                listeners.addAll( map.values() );
144                listeners.addAll( getNonSingletonListeners( beanType ) );
145            }
146    
147            if( logger.isDebugEnabled() )
148                logger.debug( "List of beans to notify:" + listeners );
149    
150            return listeners;
151        }
152    
153        /**
154         * Get the list of non-singleton beans we have registered that still exist. Update our
155         * registration list if any have been GC'ed. Only return beans of the requested type.
156         * @param beanType Type of bean to locate
157         * @return list of extant non-singleton beans to notify
158         */
159        protected List getNonSingletonListeners(Class beanType) {
160    
161            final List listeners = new ArrayList();
162    
163            synchronized(nonSingletonListeners) {
164                for( Iterator iter = nonSingletonListeners.iterator(); iter.hasNext(); ) {
165                    final Object bean = ((WeakReference) iter.next()).get();
166                    if( bean == null ) {
167                        if( logger.isDebugEnabled() )
168                            logger.debug( "REMOVED garbage collected AuthorizationAware non-singleton from list." );
169                        iter.remove();
170                    } else if( beanType.isAssignableFrom( bean.getClass() ) ) {
171                        listeners.add( bean );
172                    }
173                }
174            }
175            return listeners;
176        }
177    
178        /**
179         * Add a non-singleton bean instance to our list for later notification.
180         * @param bean
181         */
182        protected void addToNonSingletonListeners(final Object bean) {
183            if( logger.isDebugEnabled() )
184                logger.debug( "Adding Authentication/LoginAware bean to list of non-singleton listeners: bean='" + bean );
185    
186            nonSingletonListeners.add( new WeakReference( bean ) );
187        }
188    
189        //
190        // AplicationListener implementation
191        //
192    
193        /*
194         * (non-Javadoc)
195         * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
196         */
197        public void onApplicationEvent(ApplicationEvent event) {
198    
199            // All events we care about are subtypes of ClientSecurityEvent
200            if( event instanceof ClientSecurityEvent ) {
201                Authentication authentication = (Authentication) event.getSource();
202    
203                if( logger.isDebugEnabled() ) {
204                    logger.debug( "RECEIVED ClientSecurityEvent: " + event );
205                    logger.debug( "Authentication token: " + authentication );
206                }
207    
208                // Note that we need to inspect the new authentication token and see if it is
209                // NO_AUTHENTICATION. If so, then we need to use null instead. This little
210                // dance is required because the source of an event can't actually be null.
211    
212                if( authentication == ClientSecurityEvent.NO_AUTHENTICATION ) {
213                    if( logger.isDebugEnabled() ) {
214                        logger.debug( "Converted NO_AUTHENTICATION to null" );
215                    }
216                    authentication = null;
217                }
218    
219                // And dispatch according to the event type.
220    
221                if( event instanceof AuthenticationEvent ) {
222                    broadcastAuthentication( authentication );
223                } else if( event instanceof LoginEvent ) {
224                    broadcastLogin( authentication );
225                } else if( event instanceof LogoutEvent ) {
226                    broadcastLogout( authentication );
227                } else {
228                    if( logger.isDebugEnabled() ) {
229                        logger.debug( "Unsupported event not processed" );
230                    }
231                }
232            }
233        }
234    
235        //
236        // AplicationContextAware implementation
237        //
238    
239        /*
240         * (non-Javadoc)
241         * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
242         */
243        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
244            this.applicationContext = applicationContext;
245        }
246    
247        //
248        // BeanPostProcessor implementation
249        //
250    
251        /*
252         * (non-Javadoc)
253         * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
254         *      java.lang.String)
255         */
256        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
257            if( bean instanceof AuthenticationAware || bean instanceof LoginAware ) {
258    
259                // If the bean isn't a singleton, then add it to our list
260                if( beanName == null || !getApplicationContext().containsBean( beanName )
261                        || !getApplicationContext().isSingleton( beanName ) ) {
262                    addToNonSingletonListeners( bean );
263                }
264    
265                // Install the last known authentication token
266                if( bean instanceof AuthenticationAware ) {
267                    if( logger.isDebugEnabled() )
268                        logger.debug( "NOTIFY bean '" + bean + "' of new authorization for '" + currentAuthentication
269                                + "'" );
270    
271                    AuthenticationAware aab = (AuthenticationAware) bean;
272                    aab.setAuthenticationToken(currentAuthentication);
273                }
274            }
275            return bean;
276        }
277    
278        /*
279         * (non-Javadoc)
280         * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
281         *      java.lang.String)
282         */
283        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
284            return bean;
285        }
286    
287    }