001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.OneShotLock; 021 import org.apache.tapestry5.ioc.services.PerThreadValue; 022 import org.apache.tapestry5.ioc.services.PerthreadManager; 023 import org.apache.tapestry5.runtime.Component; 024 import org.apache.tapestry5.runtime.PageLifecycleListener; 025 import org.apache.tapestry5.services.PersistentFieldBundle; 026 import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 027 import org.slf4j.Logger; 028 029 import java.util.List; 030 import java.util.Map; 031 import java.util.concurrent.atomic.AtomicInteger; 032 import java.util.regex.Pattern; 033 034 public class PageImpl implements Page 035 { 036 private final String name; 037 038 private final ComponentResourceSelector selector; 039 040 private final PersistentFieldManager persistentFieldManager; 041 042 private ComponentPageElement rootElement; 043 044 private final List<PageLifecycleListener> lifecycleListeners = CollectionFactory.newThreadSafeList(); 045 046 private final List<PageResetListener> resetListeners = CollectionFactory.newList(); 047 048 private boolean loadComplete; 049 050 private final OneShotLock lock = new OneShotLock(); 051 052 private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newCaseInsensitiveMap(); 053 054 private Stats stats; 055 056 private final AtomicInteger attachCount = new AtomicInteger(); 057 058 private List<Runnable> pageVerifyCallbacks = CollectionFactory.newList(); 059 060 /** 061 * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when 062 * first needed, 063 * discarded at the end of the request. 064 */ 065 private final PerThreadValue<PersistentFieldBundle> fieldBundle; 066 067 private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\."); 068 069 /** 070 * @param name canonicalized page name 071 * @param selector used to locate resources 072 * @param persistentFieldManager for access to cross-request persistent values 073 * @param perThreadManager for managing per-request mutable state 074 */ 075 public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager, 076 PerthreadManager perThreadManager) 077 { 078 this.name = name; 079 this.selector = selector; 080 this.persistentFieldManager = persistentFieldManager; 081 082 fieldBundle = perThreadManager.createValue(); 083 } 084 085 public void setStats(Stats stats) 086 { 087 this.stats = stats; 088 } 089 090 public Stats getStats() 091 { 092 return stats; 093 } 094 095 @Override 096 public String toString() 097 { 098 return String.format("Page[%s %s]", name, selector.toShortString()); 099 } 100 101 public synchronized ComponentPageElement getComponentElementByNestedId(String nestedId) 102 { 103 assert nestedId != null; 104 105 if (nestedId.equals("")) 106 return rootElement; 107 108 ComponentPageElement element = idToComponent.get(nestedId); 109 110 if (element == null) 111 { 112 element = rootElement; 113 114 for (String id : SPLIT_ON_DOT.split(nestedId)) 115 { 116 element = element.getEmbeddedElement(id); 117 } 118 119 idToComponent.put(nestedId, element); 120 } 121 122 return element; 123 } 124 125 public ComponentResourceSelector getSelector() 126 { 127 return selector; 128 } 129 130 public void setRootElement(ComponentPageElement component) 131 { 132 lock.check(); 133 134 rootElement = component; 135 } 136 137 public ComponentPageElement getRootElement() 138 { 139 return rootElement; 140 } 141 142 public Component getRootComponent() 143 { 144 return rootElement.getComponent(); 145 } 146 147 public void addLifecycleListener(PageLifecycleListener listener) 148 { 149 lock.check(); 150 151 lifecycleListeners.add(listener); 152 } 153 154 public void removeLifecycleListener(PageLifecycleListener listener) 155 { 156 lock.check(); 157 158 lifecycleListeners.remove(listener); 159 } 160 161 public boolean detached() 162 { 163 boolean result = false; 164 165 for (PageLifecycleListener listener : lifecycleListeners) 166 { 167 try 168 { 169 listener.containingPageDidDetach(); 170 } catch (RuntimeException ex) 171 { 172 getLogger().error(StructureMessages.detachFailure(listener, ex), ex); 173 result = true; 174 } 175 } 176 177 return result; 178 } 179 180 public void loaded() 181 { 182 lock.check(); 183 184 for (PageLifecycleListener listener : lifecycleListeners) 185 { 186 listener.containingPageDidLoad(); 187 } 188 189 lock.lock(); 190 191 192 for (Runnable callback : pageVerifyCallbacks) 193 { 194 callback.run(); 195 } 196 197 // These are never needed again, so we can get rid of them. The PageLifecycleListener interface is too complicated, 198 // we don't know what's needed when, and rely on the listeners themselves to unregister when no longer needed. A better design 199 // would be more like these pageVerifyCallbacks: just use Runnable and know when it is safe to throw them away. Something 200 // to refactor to over time. 201 202 pageVerifyCallbacks = null; 203 204 loadComplete = true; 205 } 206 207 public void attached() 208 { 209 attachCount.incrementAndGet(); 210 211 for (PageLifecycleListener listener : lifecycleListeners) 212 listener.restoreStateBeforePageAttach(); 213 214 for (PageLifecycleListener listener : lifecycleListeners) 215 listener.containingPageDidAttach(); 216 } 217 218 public Logger getLogger() 219 { 220 return rootElement.getLogger(); 221 } 222 223 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue) 224 { 225 if (!loadComplete) 226 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete()); 227 228 persistentFieldManager.postChange(name, resources, fieldName, newValue); 229 } 230 231 public Object getFieldChange(String nestedId, String fieldName) 232 { 233 if (!fieldBundle.exists()) 234 fieldBundle.set(persistentFieldManager.gatherChanges(name)); 235 236 return fieldBundle.get().getValue(nestedId, fieldName); 237 } 238 239 public void discardPersistentFieldChanges() 240 { 241 persistentFieldManager.discardChanges(name); 242 } 243 244 public String getName() 245 { 246 return name; 247 } 248 249 public void addResetListener(PageResetListener listener) 250 { 251 assert listener != null; 252 lock.check(); 253 254 resetListeners.add(listener); 255 } 256 257 public void addVerifyListener(Runnable callback) 258 { 259 assert callback != null; 260 261 lock.check(); 262 263 pageVerifyCallbacks.add(callback); 264 } 265 266 public void pageReset() 267 { 268 for (PageResetListener l : resetListeners) 269 { 270 l.containingPageDidReset(); 271 } 272 } 273 274 public boolean hasResetListeners() 275 { 276 return !resetListeners.isEmpty(); 277 } 278 279 public int getAttachCount() 280 { 281 return attachCount.get(); 282 } 283 }