001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.bean;
016    
017    import java.util.Collection;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.HashSet;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.hivemind.ApplicationRuntimeException;
029    import org.apache.hivemind.ClassResolver;
030    import org.apache.tapestry.IBeanProvider;
031    import org.apache.tapestry.IComponent;
032    import org.apache.tapestry.INamespace;
033    import org.apache.tapestry.event.PageDetachListener;
034    import org.apache.tapestry.event.PageEndRenderListener;
035    import org.apache.tapestry.event.PageEvent;
036    import org.apache.tapestry.services.ClassFinder;
037    import org.apache.tapestry.services.Infrastructure;
038    import org.apache.tapestry.spec.BeanLifecycle;
039    import org.apache.tapestry.spec.IBeanSpecification;
040    import org.apache.tapestry.spec.IComponentSpecification;
041    
042    /**
043     * Basic implementation of the {@link IBeanProvider} interface.
044     * 
045     * @author Howard Lewis Ship
046     * @since 1.0.4
047     */
048    
049    public class BeanProvider implements IBeanProvider, PageDetachListener, PageEndRenderListener
050    {
051        private static final Log LOG = LogFactory.getLog(BeanProvider.class);
052    
053        /**
054         * Indicates whether this instance has been registered with its page as a PageDetachListener.
055         * Registration only occurs the first time a bean with lifecycle REQUEST is instantiated.
056         */
057    
058        private boolean _registeredForDetach = false;
059    
060        /**
061         * Indicates whether this instance has been registered as a render listener with the page.
062         */
063    
064        private boolean _registeredForRender = false;
065    
066        /**
067         * The component for which beans are being created and tracked.
068         */
069    
070        private final IComponent _component;
071    
072        /**
073         * Used for instantiating classes.
074         */
075    
076        private final ClassResolver _resolver;
077    
078        /**
079         * Used for resolving partial class names.
080         */
081    
082        private final ClassFinder _classFinder;
083    
084        private final String _packageList;
085    
086        /**
087         * Map of beans, keyed on name.
088         */
089    
090        private Map _beans;
091    
092        /**
093         * Set of bean names provided by this provider.
094         * 
095         * @since 2.2
096         */
097    
098        private Set _beanNames;
099    
100        public BeanProvider(IComponent component)
101        {
102            _component = component;
103    
104            Infrastructure infrastructure = component.getPage().getRequestCycle().getInfrastructure();
105    
106            _resolver = infrastructure.getClassResolver();
107    
108            INamespace namespace = component.getNamespace();
109            _packageList = namespace.getPropertyValue("org.apache.tapestry.bean-class-packages");
110    
111            _classFinder = infrastructure.getClassFinder();
112        }
113    
114        /** @since 1.0.6 * */
115    
116        public Collection getBeanNames()
117        {
118            if (_beanNames == null)
119            {
120                Collection c = _component.getSpecification().getBeanNames();
121    
122                if (c == null || c.isEmpty())
123                    _beanNames = Collections.EMPTY_SET;
124                else
125                    _beanNames = Collections.unmodifiableSet(new HashSet(c));
126            }
127    
128            return _beanNames;
129        }
130    
131        /**
132         * @since 1.0.5
133         */
134    
135        public IComponent getComponent()
136        {
137            return _component;
138        }
139    
140        public Object getBean(String name)
141        {
142            if (LOG.isDebugEnabled())
143                LOG.debug("getBean(" + name + ")");
144    
145            Object bean = null;
146    
147            if (_beans != null)
148                bean = _beans.get(name);
149    
150            if (bean != null)
151                return bean;
152    
153            IBeanSpecification spec = _component.getSpecification().getBeanSpecification(name);
154    
155            if (spec == null)
156                throw new ApplicationRuntimeException(BeanMessages.beanNotDefined(_component, name));
157    
158            bean = instantiateBean(name, spec);
159    
160            BeanLifecycle lifecycle = spec.getLifecycle();
161    
162            if (lifecycle == BeanLifecycle.NONE)
163                return bean;
164    
165            if (_beans == null)
166                _beans = new HashMap();
167    
168            _beans.put(name, bean);
169    
170            // The first time in a request that a REQUEST lifecycle bean is created,
171            // register with the page to be notified at the end of the
172            // request cycle.
173    
174            if (lifecycle == BeanLifecycle.REQUEST && !_registeredForDetach)
175            {
176                _component.getPage().addPageDetachListener(this);
177                _registeredForDetach = true;
178            }
179    
180            if (lifecycle == BeanLifecycle.RENDER && !_registeredForRender)
181            {
182                _component.getPage().addPageEndRenderListener(this);
183                _registeredForRender = true;
184            }
185    
186            // No need to register if a PAGE lifecycle bean; those can stick around
187            // forever.
188    
189            return bean;
190        }
191    
192        Object instantiateBean(String beanName, IBeanSpecification spec)
193        {
194            String className = spec.getClassName();
195            Object bean = null;
196    
197            if (LOG.isDebugEnabled())
198                LOG.debug("Instantiating instance of " + className);
199    
200            Class beanClass = _classFinder.findClass(_packageList, className);
201    
202            if (beanClass == null)
203                throw new ApplicationRuntimeException(BeanMessages.missingBeanClass(
204                        _component,
205                        beanName,
206                        className,
207                        _packageList), _component, spec.getLocation(), null);
208    
209            // Do it the hard way!
210    
211            try
212            {
213                bean = beanClass.newInstance();
214            }
215            catch (Exception ex)
216            {
217                throw new ApplicationRuntimeException(BeanMessages.instantiationError(
218                        beanName,
219                        _component,
220                        beanClass,
221                        ex), _component, spec.getLocation(), ex);
222            }
223    
224            // OK, have the bean, have to initialize it.
225    
226            List initializers = spec.getInitializers();
227    
228            if (initializers == null)
229                return bean;
230    
231            Iterator i = initializers.iterator();
232            while (i.hasNext())
233            {
234                IBeanInitializer iz = (IBeanInitializer) i.next();
235    
236                if (LOG.isDebugEnabled())
237                    LOG.debug("Initializing property " + iz.getPropertyName());
238    
239                try
240                {
241                    iz.setBeanProperty(this, bean);
242                }
243                catch (Exception ex)
244                {
245                    throw new ApplicationRuntimeException(BeanMessages.initializationError(
246                            _component,
247                            beanName,
248                            iz.getPropertyName(),
249                            ex), bean, iz.getLocation(), ex);
250    
251                }
252            }
253    
254            return bean;
255        }
256    
257        /**
258         * Removes all beans with the REQUEST lifecycle. Beans with the PAGE lifecycle stick around, and
259         * beans with no lifecycle were never stored in the first place.
260         */
261    
262        public void pageDetached(PageEvent event)
263        {
264            removeBeans(BeanLifecycle.REQUEST);
265        }
266    
267        /**
268         * Removes any beans with the specified lifecycle.
269         * 
270         * @since 2.2
271         */
272    
273        private void removeBeans(BeanLifecycle lifecycle)
274        {
275            if (_beans == null)
276                return;
277    
278            IComponentSpecification spec = null;
279    
280            Iterator i = _beans.entrySet().iterator();
281            while (i.hasNext())
282            {
283                Map.Entry e = (Map.Entry) i.next();
284                String name = (String) e.getKey();
285    
286                if (spec == null)
287                    spec = _component.getSpecification();
288    
289                IBeanSpecification s = spec.getBeanSpecification(name);
290    
291                if (s.getLifecycle() == lifecycle)
292                {
293                    Object bean = e.getValue();
294    
295                    if (LOG.isDebugEnabled())
296                        LOG.debug("Removing " + lifecycle.getName() + " bean " + name + ": " + bean);
297    
298                    i.remove();
299                }
300            }
301        }
302    
303        /** @since 1.0.8 * */
304    
305        public ClassResolver getClassResolver()
306        {
307            return _resolver;
308        }
309    
310        /** @since 2.2 * */
311    
312        public void pageEndRender(PageEvent event)
313        {
314            removeBeans(BeanLifecycle.RENDER);
315        }
316    
317        /** @since 2.2 * */
318    
319        public boolean canProvideBean(String name)
320        {
321            return getBeanNames().contains(name);
322        }
323    
324    }