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 }