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