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 }