001 // Copyright 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.corelib.pages; 016 017 import org.apache.tapestry5.SymbolConstants; 018 import org.apache.tapestry5.alerts.AlertManager; 019 import org.apache.tapestry5.annotations.*; 020 import org.apache.tapestry5.beaneditor.BeanModel; 021 import org.apache.tapestry5.beaneditor.Validate; 022 import org.apache.tapestry5.corelib.components.Zone; 023 import org.apache.tapestry5.func.*; 024 import org.apache.tapestry5.internal.PageCatalogTotals; 025 import org.apache.tapestry5.internal.services.ComponentInstantiatorSource; 026 import org.apache.tapestry5.internal.services.PageSource; 027 import org.apache.tapestry5.internal.structure.Page; 028 import org.apache.tapestry5.ioc.Messages; 029 import org.apache.tapestry5.ioc.OperationTracker; 030 import org.apache.tapestry5.ioc.annotations.Inject; 031 import org.apache.tapestry5.ioc.annotations.Symbol; 032 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 033 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 034 import org.apache.tapestry5.services.BeanModelSource; 035 import org.apache.tapestry5.services.ComponentClassResolver; 036 import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 037 038 import java.util.Collection; 039 import java.util.List; 040 import java.util.Set; 041 042 /** 043 * Lists out the currently loaded pages, using a {@link org.apache.tapestry5.corelib.components.Grid}. 044 * Provides an option to force all pages to be loaded. In development mode, includes an option to clear the page cache. 045 */ 046 @ContentType("text/html") 047 @WhitelistAccessOnly 048 public class PageCatalog 049 { 050 051 @Property 052 private PageCatalogTotals totals; 053 054 @Property 055 @Inject 056 @Symbol(SymbolConstants.PRODUCTION_MODE) 057 private boolean productionMode; 058 059 @Inject 060 private PageSource pageSource; 061 062 @Inject 063 private ComponentResourceSelector selector; 064 065 @Inject 066 private ComponentClassResolver resolver; 067 068 @Inject 069 private AlertManager alertManager; 070 071 @Property 072 private Page page; 073 074 @InjectComponent 075 private Zone pagesZone; 076 077 @Persist 078 private Set<String> failures; 079 080 @Property 081 @Validate("required") 082 @Persist 083 private String pageName; 084 085 @Inject 086 private OperationTracker operationTracker; 087 088 @Inject 089 private ComponentInstantiatorSource componentInstantiatorSource; 090 091 @Inject 092 private BeanModelSource beanModelSource; 093 094 @Inject 095 private Messages messages; 096 097 @Property 098 @Retain 099 private BeanModel<Page> model; 100 101 void pageLoaded() 102 { 103 model = beanModelSource.createDisplayModel(Page.class, messages); 104 105 model.addExpression("selector", "selector.toString()"); 106 model.addExpression("assemblyTime", "stats.assemblyTime"); 107 model.addExpression("componentCount", "stats.componentCount"); 108 model.addExpression("weight", "stats.weight"); 109 110 model.reorder("name", "selector", "assemblyTime", "componentCount", "weight"); 111 } 112 113 public void onRecomputeTotals() 114 { 115 totals = new PageCatalogTotals(); 116 117 Flow<Page> pages = F.flow(getPages()); 118 119 totals.loadedPages = pages.count(); 120 totals.definedPages = getPageNames().size(); 121 totals.uniquePageNames = pages.map(new Mapper<Page, String>() 122 { 123 public String map(Page element) 124 { 125 return element.getName(); 126 } 127 }).toSet().size(); 128 129 totals.components = pages.reduce(new Reducer<Integer, Page>() 130 { 131 public Integer reduce(Integer accumulator, Page element) 132 { 133 return accumulator + element.getStats().componentCount; 134 } 135 }, 0); 136 137 Set<String> selectorIds = pages.map(new Mapper<Page, String>() 138 { 139 public String map(Page element) 140 { 141 return element.getSelector().toShortString(); 142 } 143 }).toSet(); 144 145 totals.selectors = InternalUtils.joinSorted(selectorIds); 146 } 147 148 public List<String> getPageNames() 149 { 150 return resolver.getPageNames(); 151 } 152 153 public Collection<Page> getPages() 154 { 155 return pageSource.getAllPages(); 156 } 157 158 Object onActionFromReloadClasses() 159 { 160 if (productionMode) 161 { 162 alertManager.error("Forcing a class reload is only allowed when executing in development mode."); 163 return null; 164 } 165 166 pageName = null; 167 168 componentInstantiatorSource.forceComponentInvalidation(); 169 170 alertManager.info("Forced a component class reload."); 171 172 return pagesZone.getBody(); 173 } 174 175 Object onSuccessFromSinglePageLoad() 176 { 177 boolean found = !F.flow(getPages()).filter(new Predicate<Page>() 178 { 179 public boolean accept(Page element) 180 { 181 return element.getName().equals(pageName) && element.getSelector().equals(selector); 182 } 183 }).isEmpty(); 184 185 if (found) 186 { 187 alertManager.warn(String.format("Page %s has already been loaded for '%s'.", 188 pageName, selector.toShortString())); 189 return null; 190 } 191 192 long startTime = System.currentTimeMillis(); 193 194 195 // Load the page now (may cause an exception). 196 197 pageSource.getPage(pageName); 198 199 200 alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName, 201 selector.toShortString(), System.currentTimeMillis() - startTime)); 202 203 return pagesZone.getBody(); 204 } 205 206 private class PageLoadData 207 { 208 int loadedCount; 209 RuntimeException fail; 210 boolean someFail; 211 } 212 213 Object onActionFromForceLoad() 214 { 215 if (failures == null) 216 { 217 failures = CollectionFactory.newSet(); 218 } 219 220 long startTime = System.currentTimeMillis(); 221 222 final Collection<Page> initialPages = getPages(); 223 224 final PageLoadData data = new PageLoadData(); 225 226 for (final String name : resolver.getPageNames()) 227 { 228 if (failures.contains(name)) 229 { 230 alertManager.warn(String.format("Skipping page %s due to prior load failure.", name)); 231 data.someFail = true; 232 continue; 233 } 234 235 operationTracker.run("Loading page " + name, new Runnable() 236 { 237 public void run() 238 { 239 try 240 { 241 Page newPage = pageSource.getPage(name); 242 243 if (!initialPages.contains(newPage)) 244 { 245 data.loadedCount++; 246 } 247 } catch (RuntimeException ex) 248 { 249 alertManager.error(String.format("Page %s failed to load.", name)); 250 failures.add(name); 251 252 if (data.fail == null) 253 { 254 pageName = name; 255 data.fail = ex; 256 } 257 } 258 } 259 }); 260 261 if (data.fail != null) 262 { 263 break; 264 } 265 } 266 267 alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount, 268 selector.toShortString(), System.currentTimeMillis() - startTime)); 269 270 if (data.someFail) 271 { 272 alertManager.warn("Clear the cache to reset the list of failed pages."); 273 } 274 275 if (data.fail != null) 276 { 277 throw data.fail; 278 } 279 280 return pagesZone.getBody(); 281 } 282 283 Object onActionFromClearCache() 284 { 285 if (productionMode) 286 { 287 alertManager.error("Clearing the cache is only allowed in development mode."); 288 return null; 289 } 290 291 pageSource.clearCache(); 292 293 failures = null; 294 295 alertManager.info("Page cache cleared."); 296 297 return pagesZone.getBody(); 298 } 299 300 Object onActionFromRunGC() 301 { 302 if (productionMode) 303 { 304 alertManager.error("Executing a garbage collection is only allowed in development mode."); 305 return null; 306 } 307 308 Runtime runtime = Runtime.getRuntime(); 309 310 long initialFreeMemory = runtime.freeMemory(); 311 312 runtime.gc(); 313 314 long delta = runtime.freeMemory() - initialFreeMemory; 315 316 alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.", 317 ((double) delta) / 1024.0d)); 318 319 return pagesZone.getBody(); 320 } 321 322 public String formatElapsed(long millis) 323 { 324 return String.format("%,d ms", millis); 325 } 326 }