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 public void run() 169 { 170 listener.containingPageDidLoad(); 171 } 172 }); 173 174 addPageAttachedCallback(new Runnable() 175 { 176 public void run() 177 { 178 listener.containingPageDidAttach(); 179 } 180 }); 181 182 addPageDetachedCallback(new Runnable() 183 { 184 public void run() 185 { 186 listener.containingPageDidDetach(); 187 } 188 }); 189 } 190 191 public void removeLifecycleListener(PageLifecycleListener listener) 192 { 193 lifecycleListenersLock.check(); 194 195 throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead."); 196 } 197 198 public boolean detached() 199 { 200 boolean result = false; 201 202 for (Runnable callback : detachCallbacks) 203 { 204 try 205 { 206 callback.run(); 207 } catch (RuntimeException ex) 208 { 209 result = true; 210 211 getLogger().error(String.format("Callback %s failed during page detach: %s", callback, InternalUtils.toMessage(ex)), ex); 212 } 213 } 214 215 return result; 216 } 217 218 public void loaded() 219 { 220 lifecycleListenersLock.lock(); 221 222 invokeCallbacks(loadedCallbacks); 223 224 loadedCallbacks = null; 225 226 verifyListenerLocks.lock(); 227 228 invokeCallbacks(pageVerifyCallbacks); 229 230 pageVerifyCallbacks = null; 231 232 loadComplete = true; 233 } 234 235 public void attached() 236 { 237 attachCount.incrementAndGet(); 238 239 invokeCallbacks(attachCallbacks); 240 } 241 242 public Logger getLogger() 243 { 244 return rootElement.getLogger(); 245 } 246 247 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue) 248 { 249 if (!loadComplete) 250 { 251 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete()); 252 } 253 254 persistentFieldManager.postChange(name, resources, fieldName, newValue); 255 } 256 257 public Object getFieldChange(String nestedId, String fieldName) 258 { 259 if (!fieldBundle.exists()) 260 { 261 fieldBundle.set(persistentFieldManager.gatherChanges(name)); 262 } 263 264 return fieldBundle.get().getValue(nestedId, fieldName); 265 } 266 267 public void discardPersistentFieldChanges() 268 { 269 persistentFieldManager.discardChanges(name); 270 } 271 272 public String getName() 273 { 274 return name; 275 } 276 277 public void addResetCallback(Runnable callback) 278 { 279 assert callback != null; 280 281 lifecycleListenersLock.check(); 282 283 resetCallbacks.add(callback); 284 } 285 286 public void addResetListener(final PageResetListener listener) 287 { 288 assert listener != null; 289 290 addResetCallback(new Runnable() 291 { 292 public void run() 293 { 294 listener.containingPageDidReset(); 295 } 296 }); 297 } 298 299 public void addVerifyCallback(Runnable callback) 300 { 301 verifyListenerLocks.check(); 302 303 assert callback != null; 304 305 pageVerifyCallbacks.add(callback); 306 } 307 308 public void pageReset() 309 { 310 invokeCallbacks(resetCallbacks); 311 } 312 313 public boolean hasResetListeners() 314 { 315 return !resetCallbacks.isEmpty(); 316 } 317 318 public int getAttachCount() 319 { 320 return attachCount.get(); 321 } 322 323 public void addPageLoadedCallback(Runnable callback) 324 { 325 lifecycleListenersLock.check(); 326 327 assert callback != null; 328 329 loadedCallbacks.add(callback); 330 } 331 332 public void addPageAttachedCallback(Runnable callback) 333 { 334 lifecycleListenersLock.check(); 335 336 assert callback != null; 337 338 attachCallbacks.add(callback); 339 } 340 341 public void addPageDetachedCallback(Runnable callback) 342 { 343 lifecycleListenersLock.check(); 344 345 assert callback != null; 346 347 detachCallbacks.add(callback); 348 } 349 350 private void invokeCallbacks(List<Runnable> callbacks) 351 { 352 for (Runnable callback : callbacks) 353 { 354 callback.run(); 355 } 356 } 357 }