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
015package org.apache.tapestry.bean;
016
017import java.util.Collection;
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.hivemind.ApplicationRuntimeException;
029import org.apache.hivemind.ClassResolver;
030import org.apache.tapestry.IBeanProvider;
031import org.apache.tapestry.IComponent;
032import org.apache.tapestry.INamespace;
033import org.apache.tapestry.event.PageDetachListener;
034import org.apache.tapestry.event.PageEndRenderListener;
035import org.apache.tapestry.event.PageEvent;
036import org.apache.tapestry.services.ClassFinder;
037import org.apache.tapestry.services.Infrastructure;
038import org.apache.tapestry.spec.BeanLifecycle;
039import org.apache.tapestry.spec.IBeanSpecification;
040import 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
049public 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}