001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 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.tapestry5.internal.structure; 016 017 import org.apache.tapestry5.ComponentResources; 018 import org.apache.tapestry5.internal.services.PersistentFieldManager; 019 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 020 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 021 import org.apache.tapestry5.ioc.internal.util.OneShotLock; 022 import org.apache.tapestry5.ioc.services.PerThreadValue; 023 import org.apache.tapestry5.ioc.services.PerthreadManager; 024 import org.apache.tapestry5.runtime.Component; 025 import org.apache.tapestry5.runtime.PageLifecycleListener; 026 import org.apache.tapestry5.services.PersistentFieldBundle; 027 import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 028 import org.slf4j.Logger; 029 030 import java.util.List; 031 import java.util.Map; 032 import java.util.concurrent.atomic.AtomicInteger; 033 import java.util.regex.Pattern; 034 035 public class PageImpl implements Page 036 { 037 private final String name; 038 039 private final ComponentResourceSelector selector; 040 041 private final PersistentFieldManager persistentFieldManager; 042 043 private ComponentPageElement rootElement; 044 045 private List<Runnable> loadedCallbacks = CollectionFactory.newList(); 046 047 private final List<Runnable> attachCallbacks = CollectionFactory.newList(); 048 049 private final List<Runnable> detachCallbacks = CollectionFactory.newList(); 050 051 private final List<Runnable> resetCallbacks = CollectionFactory.newList(); 052 053 private boolean loadComplete; 054 055 private final OneShotLock lifecycleListenersLock = new OneShotLock(); 056 057 private final OneShotLock verifyListenerLocks = new OneShotLock(); 058 059 // May end up with multiple mappings for the same id (with different case) to the same component. 060 // That's OK. 061 private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newConcurrentMap(); 062 063 private Stats stats; 064 065 private final AtomicInteger attachCount = new AtomicInteger(); 066 067 private List<Runnable> pageVerifyCallbacks = CollectionFactory.newList(); 068 069 /** 070 * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when 071 * first needed, 072 * discarded at the end of the request. 073 */ 074 private final PerThreadValue<PersistentFieldBundle> fieldBundle; 075 076 private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\."); 077 078 /** 079 * @param name 080 * canonicalized page name 081 * @param selector 082 * used to locate resources 083 * @param persistentFieldManager 084 * for access to cross-request persistent values 085 * @param perThreadManager 086 * for managing per-request mutable state 087 */ 088 public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager, 089 PerthreadManager perThreadManager) 090 { 091 this.name = name; 092 this.selector = selector; 093 this.persistentFieldManager = persistentFieldManager; 094 095 fieldBundle = perThreadManager.createValue(); 096 } 097 098 public void setStats(Stats stats) 099 { 100 this.stats = stats; 101 } 102 103 public Stats getStats() 104 { 105 return stats; 106 } 107 108 @Override 109 public String toString() 110 { 111 return String.format("Page[%s %s]", name, selector.toShortString()); 112 } 113 114 public ComponentPageElement getComponentElementByNestedId(String nestedId) 115 { 116 assert nestedId != null; 117 118 if (nestedId.equals("")) 119 { 120 return rootElement; 121 } 122 123 ComponentPageElement element = idToComponent.get(nestedId); 124 125 if (element == null) 126 { 127 element = rootElement; 128 129 for (String id : SPLIT_ON_DOT.split(nestedId)) 130 { 131 element = element.getEmbeddedElement(id); 132 } 133 134 idToComponent.put(nestedId, element); 135 } 136 137 return element; 138 } 139 140 public ComponentResourceSelector getSelector() 141 { 142 return selector; 143 } 144 145 public void setRootElement(ComponentPageElement component) 146 { 147 lifecycleListenersLock.check(); 148 149 rootElement = component; 150 } 151 152 public ComponentPageElement getRootElement() 153 { 154 return rootElement; 155 } 156 157 public Component getRootComponent() 158 { 159 return rootElement.getComponent(); 160 } 161 162 public void addLifecycleListener(final PageLifecycleListener listener) 163 { 164 assert listener != null; 165 166 addPageLoadedCallback(new Runnable() 167 { 168 @Override 169 public void run() 170 { 171 listener.containingPageDidLoad(); 172 } 173 }); 174 175 addPageAttachedCallback(new Runnable() 176 { 177 @Override 178 public void run() 179 { 180 listener.containingPageDidAttach(); 181 } 182 }); 183 184 addPageDetachedCallback(new Runnable() 185 { 186 @Override 187 public void run() 188 { 189 listener.containingPageDidDetach(); 190 } 191 }); 192 } 193 194 public void removeLifecycleListener(PageLifecycleListener listener) 195 { 196 lifecycleListenersLock.check(); 197 198 throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead."); 199 } 200 201 public boolean detached() 202 { 203 boolean result = false; 204 205 for (Runnable callback : detachCallbacks) 206 { 207 try 208 { 209 callback.run(); 210 } catch (RuntimeException ex) 211 { 212 result = true; 213 214 getLogger().error(String.format("Callback %s failed during page detach: %s", callback, InternalUtils.toMessage(ex)), ex); 215 } 216 } 217 218 return result; 219 } 220 221 public void loaded() 222 { 223 lifecycleListenersLock.lock(); 224 225 invokeCallbacks(loadedCallbacks); 226 227 loadedCallbacks = null; 228 229 verifyListenerLocks.lock(); 230 231 invokeCallbacks(pageVerifyCallbacks); 232 233 pageVerifyCallbacks = null; 234 235 loadComplete = true; 236 } 237 238 public void attached() 239 { 240 attachCount.incrementAndGet(); 241 242 invokeCallbacks(attachCallbacks); 243 } 244 245 public Logger getLogger() 246 { 247 return rootElement.getLogger(); 248 } 249 250 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue) 251 { 252 if (!loadComplete) 253 { 254 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete()); 255 } 256 257 persistentFieldManager.postChange(name, resources, fieldName, newValue); 258 } 259 260 public Object getFieldChange(String nestedId, String fieldName) 261 { 262 if (!fieldBundle.exists()) 263 { 264 fieldBundle.set(persistentFieldManager.gatherChanges(name)); 265 } 266 267 return fieldBundle.get().getValue(nestedId, fieldName); 268 } 269 270 public void discardPersistentFieldChanges() 271 { 272 persistentFieldManager.discardChanges(name); 273 } 274 275 public String getName() 276 { 277 return name; 278 } 279 280 @Override 281 public void addResetCallback(Runnable callback) 282 { 283 assert callback != null; 284 285 lifecycleListenersLock.check(); 286 287 resetCallbacks.add(callback); 288 } 289 290 public void addResetListener(final PageResetListener listener) 291 { 292 assert listener != null; 293 294 addResetCallback(new Runnable() 295 { 296 @Override 297 public void run() 298 { 299 listener.containingPageDidReset(); 300 } 301 }); 302 } 303 304 public void addVerifyCallback(Runnable callback) 305 { 306 verifyListenerLocks.check(); 307 308 assert callback != null; 309 310 pageVerifyCallbacks.add(callback); 311 } 312 313 public void pageReset() 314 { 315 invokeCallbacks(resetCallbacks); 316 } 317 318 public boolean hasResetListeners() 319 { 320 return !resetCallbacks.isEmpty(); 321 } 322 323 public int getAttachCount() 324 { 325 return attachCount.get(); 326 } 327 328 @Override 329 public void addPageLoadedCallback(Runnable callback) 330 { 331 lifecycleListenersLock.check(); 332 333 assert callback != null; 334 335 loadedCallbacks.add(callback); 336 } 337 338 @Override 339 public void addPageAttachedCallback(Runnable callback) 340 { 341 lifecycleListenersLock.check(); 342 343 assert callback != null; 344 345 attachCallbacks.add(callback); 346 } 347 348 @Override 349 public void addPageDetachedCallback(Runnable callback) 350 { 351 lifecycleListenersLock.check(); 352 353 assert callback != null; 354 355 detachCallbacks.add(callback); 356 } 357 358 private void invokeCallbacks(List<Runnable> callbacks) 359 { 360 for (Runnable callback : callbacks) 361 { 362 callback.run(); 363 } 364 } 365 }