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.commons.ObjectCreator; 020import org.apache.tapestry5.commons.util.CollectionFactory; 021import org.apache.tapestry5.commons.util.ExceptionUtils; 022import org.apache.tapestry5.internal.services.PersistentFieldManager; 023import org.apache.tapestry5.ioc.internal.util.OneShotLock; 024import org.apache.tapestry5.ioc.services.PerthreadManager; 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 ObjectCreator<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(new ObjectCreator<PersistentFieldBundle>() { 100 @Override 101 public PersistentFieldBundle createObject() { 102 return PageImpl.this.persistentFieldManager.gatherChanges(PageImpl.this.name); 103 } 104 }); 105 106 107 exactParameterCountMatch = metaDataLocator.findMeta(MetaDataConstants.UNKNOWN_ACTIVATION_CONTEXT_CHECK, name, Boolean.class); 108 } 109 110 public void setStats(Stats stats) 111 { 112 this.stats = stats; 113 } 114 115 public Stats getStats() 116 { 117 return stats; 118 } 119 120 @Override 121 public String toString() 122 { 123 return String.format("Page[%s %s]", name, selector.toShortString()); 124 } 125 126 public ComponentPageElement getComponentElementByNestedId(String nestedId) 127 { 128 assert nestedId != null; 129 130 if (nestedId.equals("")) 131 { 132 return rootElement; 133 } 134 135 ComponentPageElement element = idToComponent.get(nestedId); 136 137 if (element == null) 138 { 139 element = rootElement; 140 141 for (String id : SPLIT_ON_DOT.split(nestedId)) 142 { 143 element = element.getEmbeddedElement(id); 144 } 145 146 idToComponent.put(nestedId, element); 147 } 148 149 return element; 150 } 151 152 public ComponentResourceSelector getSelector() 153 { 154 return selector; 155 } 156 157 public void setRootElement(ComponentPageElement component) 158 { 159 lifecycleListenersLock.check(); 160 161 rootElement = component; 162 } 163 164 public ComponentPageElement getRootElement() 165 { 166 return rootElement; 167 } 168 169 public Component getRootComponent() 170 { 171 return rootElement.getComponent(); 172 } 173 174 public void addLifecycleListener(final PageLifecycleListener listener) 175 { 176 assert listener != null; 177 178 addPageLoadedCallback(new Runnable() 179 { 180 public void run() 181 { 182 listener.containingPageDidLoad(); 183 } 184 }); 185 186 addPageAttachedCallback(new Runnable() 187 { 188 public void run() 189 { 190 listener.containingPageDidAttach(); 191 } 192 }); 193 194 addPageDetachedCallback(new Runnable() 195 { 196 public void run() 197 { 198 listener.containingPageDidDetach(); 199 } 200 }); 201 } 202 203 public void removeLifecycleListener(PageLifecycleListener listener) 204 { 205 lifecycleListenersLock.check(); 206 207 throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead."); 208 } 209 210 public boolean detached() 211 { 212 boolean result = false; 213 214 for (Runnable callback : detachCallbacks) 215 { 216 try 217 { 218 callback.run(); 219 } catch (RuntimeException ex) 220 { 221 result = true; 222 223 getLogger().error(String.format("Callback %s failed during page detach: %s", callback, ExceptionUtils.toMessage(ex)), ex); 224 } 225 } 226 227 return result; 228 } 229 230 public void loaded() 231 { 232 lifecycleListenersLock.lock(); 233 234 invokeCallbacks(loadedCallbacks); 235 236 loadedCallbacks = null; 237 238 verifyListenerLocks.lock(); 239 240 invokeCallbacks(pageVerifyCallbacks); 241 242 pageVerifyCallbacks = null; 243 244 loadComplete = true; 245 } 246 247 public void attached() 248 { 249 attachCount.incrementAndGet(); 250 251 invokeCallbacks(attachCallbacks); 252 } 253 254 public Logger getLogger() 255 { 256 return rootElement.getLogger(); 257 } 258 259 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue) 260 { 261 if (!loadComplete) 262 { 263 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete()); 264 } 265 266 persistentFieldManager.postChange(name, resources, fieldName, newValue); 267 } 268 269 public Object getFieldChange(String nestedId, String fieldName) 270 { 271 return fieldBundle.createObject().getValue(nestedId, fieldName); 272 } 273 274 public void discardPersistentFieldChanges() 275 { 276 persistentFieldManager.discardChanges(name); 277 } 278 279 public String getName() 280 { 281 return name; 282 } 283 284 public void addResetCallback(Runnable callback) 285 { 286 assert callback != null; 287 288 lifecycleListenersLock.check(); 289 290 resetCallbacks.add(callback); 291 } 292 293 public void addResetListener(final PageResetListener listener) 294 { 295 assert listener != null; 296 297 addResetCallback(new Runnable() 298 { 299 public void run() 300 { 301 listener.containingPageDidReset(); 302 } 303 }); 304 } 305 306 public void addVerifyCallback(Runnable callback) 307 { 308 verifyListenerLocks.check(); 309 310 assert callback != null; 311 312 pageVerifyCallbacks.add(callback); 313 } 314 315 public void pageReset() 316 { 317 invokeCallbacks(resetCallbacks); 318 } 319 320 public boolean hasResetListeners() 321 { 322 return !resetCallbacks.isEmpty(); 323 } 324 325 public int getAttachCount() 326 { 327 return attachCount.get(); 328 } 329 330 public boolean isExactParameterCountMatch() 331 { 332 return exactParameterCountMatch; 333 } 334 335 public void addPageLoadedCallback(Runnable callback) 336 { 337 lifecycleListenersLock.check(); 338 339 assert callback != null; 340 341 loadedCallbacks.add(callback); 342 } 343 344 public void addPageAttachedCallback(Runnable callback) 345 { 346 lifecycleListenersLock.check(); 347 348 assert callback != null; 349 350 attachCallbacks.add(callback); 351 } 352 353 public void addPageDetachedCallback(Runnable callback) 354 { 355 lifecycleListenersLock.check(); 356 357 assert callback != null; 358 359 detachCallbacks.add(callback); 360 } 361 362 private void invokeCallbacks(List<Runnable> callbacks) 363 { 364 for (Runnable callback : callbacks) 365 { 366 callback.run(); 367 } 368 } 369}