001// Copyright 2011-2013 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.corelib.pages; 016 017import java.io.File; 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.List; 023import java.util.Set; 024 025import org.apache.tapestry5.MarkupWriter; 026import org.apache.tapestry5.SymbolConstants; 027import org.apache.tapestry5.alerts.AlertManager; 028import org.apache.tapestry5.annotations.InjectComponent; 029import org.apache.tapestry5.annotations.Persist; 030import org.apache.tapestry5.annotations.Property; 031import org.apache.tapestry5.annotations.UnknownActivationContextCheck; 032import org.apache.tapestry5.annotations.WhitelistAccessOnly; 033import org.apache.tapestry5.beaneditor.Validate; 034import org.apache.tapestry5.beanmodel.BeanModel; 035import org.apache.tapestry5.beanmodel.services.BeanModelSource; 036import org.apache.tapestry5.commons.Messages; 037import org.apache.tapestry5.commons.services.InvalidationEventHub; 038import org.apache.tapestry5.commons.util.CollectionFactory; 039import org.apache.tapestry5.corelib.components.Zone; 040import org.apache.tapestry5.dom.Element; 041import org.apache.tapestry5.func.F; 042import org.apache.tapestry5.func.Flow; 043import org.apache.tapestry5.func.Mapper; 044import org.apache.tapestry5.func.Predicate; 045import org.apache.tapestry5.func.Reducer; 046import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 047import org.apache.tapestry5.http.services.Request; 048import org.apache.tapestry5.internal.PageCatalogTotals; 049import org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator; 050import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; 051import org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType; 052import org.apache.tapestry5.internal.services.PageSource; 053import org.apache.tapestry5.internal.services.ReloadHelper; 054import org.apache.tapestry5.internal.structure.ComponentPageElement; 055import org.apache.tapestry5.internal.structure.Page; 056import org.apache.tapestry5.ioc.OperationTracker; 057import org.apache.tapestry5.ioc.annotations.ComponentClasses; 058import org.apache.tapestry5.ioc.annotations.Inject; 059import org.apache.tapestry5.ioc.annotations.Symbol; 060import org.apache.tapestry5.ioc.internal.util.InternalUtils; 061import org.apache.tapestry5.runtime.Component; 062import org.apache.tapestry5.services.ComponentClassResolver; 063import org.apache.tapestry5.services.ajax.AjaxResponseRenderer; 064import org.apache.tapestry5.services.javascript.JavaScriptSupport; 065import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 066import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager; 067 068/** 069 * Lists out the currently loaded pages, using a {@link org.apache.tapestry5.corelib.components.Grid}. 070 * Provides an option to force all pages to be loaded. In development mode, includes an option to clear the page cache. 071 */ 072@UnknownActivationContextCheck(false) 073@WhitelistAccessOnly 074public class PageCatalog 075{ 076 077 @Property 078 private PageCatalogTotals totals; 079 080 @Property 081 @Inject 082 @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) 083 private boolean productionMode; 084 085 @Property 086 @Inject 087 @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) 088 private boolean multipleClassLoaders; 089 090 @Inject 091 @Symbol(SymbolConstants.COMPONENT_DEPENDENCY_FILE) 092 private String componentDependencyFile; 093 094 @Inject 095 private PageSource pageSource; 096 097 @Inject 098 private ComponentResourceSelector selector; 099 100 @Inject 101 private ComponentClassResolver resolver; 102 103 @Inject 104 private ComponentDependencyRegistry componentDependencyRegistry; 105 106 @Inject 107 private AlertManager alertManager; 108 109 @Property 110 private Page page; 111 112 @Property 113 private Page selectedPage; 114 115 @Property 116 private String dependency; 117 118 @InjectComponent 119 private Zone pagesZone; 120 121 @InjectComponent 122 private Zone pageStructureZone; 123 124 @Persist 125 private Set<String> failures; 126 127 @Property 128 @Validate("required") 129 @Persist 130 private String pageName; 131 132 @Inject 133 private OperationTracker operationTracker; 134 135 @Inject 136 private ReloadHelper reloadHelper; 137 138 @Inject 139 private BeanModelSource beanModelSource; 140 141 @Inject 142 private Messages messages; 143 144 @Property 145 public static BeanModel<Page> model; 146 147 @Inject 148 private Request request; 149 150 @Inject 151 @ComponentClasses 152 private InvalidationEventHub classesInvalidationEventHub; 153 154 @Inject 155 private JavaScriptSupport javaScriptSupport; 156 157 @Inject 158 private ComponentDependencyGraphvizGenerator componentDependencyGraphvizGenerator; 159 160 @Inject 161 private ComponentClassResolver componentClassResolver; 162 163 @Inject 164 private AjaxResponseRenderer ajaxResponseRenderer; 165 166 @Inject 167 private PageClassLoaderContextManager pageClassLoaderContextManager; 168 169 void pageLoaded() 170 { 171 model = beanModelSource.createDisplayModel(Page.class, messages); 172 173 model.addExpression("selector", "selector.toString()"); 174 model.addExpression("assemblyTime", "stats.assemblyTime"); 175 model.addExpression("componentCount", "stats.componentCount"); 176 model.addExpression("weight", "stats.weight"); 177 model.add("clear", null); 178 179 model.reorder("name", "selector", "assemblyTime", "componentCount", "weight"); 180 } 181 182 public void onRecomputeTotals() 183 { 184 totals = new PageCatalogTotals(); 185 186 Flow<Page> pages = F.flow(getPages()); 187 188 totals.loadedPages = pages.count(); 189 totals.definedPages = getPageNames().size(); 190 totals.uniquePageNames = pages.map(new Mapper<Page, String>() 191 { 192 public String map(Page element) 193 { 194 return element.getName(); 195 } 196 }).toSet().size(); 197 198 totals.components = pages.reduce(new Reducer<Integer, Page>() 199 { 200 public Integer reduce(Integer accumulator, Page element) 201 { 202 return accumulator + element.getStats().componentCount; 203 } 204 }, 0); 205 206 Set<String> selectorIds = pages.map(new Mapper<Page, String>() 207 { 208 public String map(Page element) 209 { 210 return element.getSelector().toShortString(); 211 } 212 }).toSet(); 213 214 totals.selectors = InternalUtils.joinSorted(selectorIds); 215 } 216 217 public List<String> getPageNames() 218 { 219 return resolver.getPageNames(); 220 } 221 222 public Collection<Page> getPages() 223 { 224 return pageSource.getAllPages(); 225 } 226 227 void onActionFromPreloadPageClassLoaderContexts() 228 { 229 pageClassLoaderContextManager.preload(); 230 } 231 232 Object onClearPage(String className) 233 { 234 final String logicalName = resolver.getLogicalName(className); 235 classesInvalidationEventHub.fireInvalidationEvent(Arrays.asList(className)); 236 alertManager.warn(String.format("Page %s (%s) has been cleared from the page cache", 237 className, logicalName)); 238 return pagesZone.getBody(); 239 } 240 241 Object onSuccessFromSinglePageLoad() 242 { 243 boolean found = !F.flow(getPages()).filter(new Predicate<Page>() 244 { 245 public boolean accept(Page element) 246 { 247 return element.getName().equals(pageName) && element.getSelector().equals(selector); 248 } 249 }).isEmpty(); 250 251 if (found) 252 { 253 alertManager.warn(String.format("Page %s has already been loaded for '%s'.", 254 pageName, selector.toShortString())); 255 return null; 256 } 257 258 long startTime = System.currentTimeMillis(); 259 260 261 // Load the page now (may cause an exception). 262 263 pageSource.getPage(pageName); 264 265 266 alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName, 267 selector.toShortString(), System.currentTimeMillis() - startTime)); 268 269 return pagesZone.getBody(); 270 } 271 272 private class PageLoadData 273 { 274 int loadedCount; 275 RuntimeException fail; 276 boolean someFail; 277 } 278 279 Object onActionFromForceLoad() 280 { 281 if (failures == null) 282 { 283 failures = CollectionFactory.newSet(); 284 } 285 286 long startTime = System.currentTimeMillis(); 287 288 final Collection<Page> initialPages = getPages(); 289 290 final PageLoadData data = new PageLoadData(); 291 292 for (final String name : resolver.getPageNames()) 293 { 294 if (failures.contains(name)) 295 { 296 alertManager.warn(String.format("Skipping page %s due to prior load failure.", name)); 297 data.someFail = true; 298 continue; 299 } 300 301 operationTracker.run("Loading page " + name, new Runnable() 302 { 303 public void run() 304 { 305 try 306 { 307 Page newPage = pageSource.getPage(name); 308 309 if (!initialPages.contains(newPage)) 310 { 311 data.loadedCount++; 312 } 313 } catch (RuntimeException ex) 314 { 315 alertManager.error(String.format("Page %s failed to load.", name)); 316 failures.add(name); 317 318 if (data.fail == null) 319 { 320 pageName = name; 321 data.fail = ex; 322 } 323 } 324 } 325 }); 326 327 if (data.fail != null) 328 { 329 break; 330 } 331 } 332 333 alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount, 334 selector.toShortString(), System.currentTimeMillis() - startTime)); 335 336 if (data.someFail) 337 { 338 alertManager.warn("Clear the cache to reset the list of failed pages."); 339 } 340 341 if (data.fail != null) 342 { 343 throw data.fail; 344 } 345 346 return pagesZone.getBody(); 347 } 348 349 Object onActionFromClearCaches() 350 { 351 reloadHelper.forceReload(); 352 353 failures = null; 354 355 return pagesZone.getBody(); 356 } 357 358 Object onActionFromStoreDependencyInformation() 359 { 360 361 componentDependencyRegistry.writeFile(); 362 363 alertManager.warn(String.format( 364 "Component dependency information written to %s.", 365 new File(componentDependencyFile).getAbsolutePath())); 366 367 return pagesZone.getBody(); 368 369 } 370 371 Object onActionFromRunGC() 372 { 373 Runtime runtime = Runtime.getRuntime(); 374 375 long initialFreeMemory = runtime.freeMemory(); 376 377 runtime.gc(); 378 379 long delta = runtime.freeMemory() - initialFreeMemory; 380 381 alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.", 382 ((double) delta) / 1024.0d)); 383 384 return pagesZone.getBody(); 385 } 386 387 public String formatElapsed(double millis) 388 { 389 return String.format("%,.3f ms", millis); 390 } 391 392 public List<String> getDependencies() 393 { 394 final String selectedPageClassName = getSelectedPageClassName(); 395 List<String> dependencies = new ArrayList<>(); 396 dependencies.addAll( 397 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.USAGE)); 398 dependencies.addAll( 399 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.INJECT_PAGE)); 400 dependencies.addAll( 401 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.SUPERCLASS)); 402 Collections.sort(dependencies); 403 return dependencies; 404 } 405 406 public void onPageStructure(String pageName) 407 { 408 selectedPage = pageSource.getPage(pageName); 409 ajaxResponseRenderer.addRender("pageStructureZone", pageStructureZone.getBody()); 410 } 411 412 public String getDisplayLogicalName() 413 { 414 return getDisplayLogicalName(dependency); 415 } 416 417 public String getPageClassName() 418 { 419 return getClassName(page); 420 } 421 422 public String getSelectedPageClassName() 423 { 424 return getClassName(selectedPage); 425 } 426 427 private String getClassName(Page page) 428 { 429 return page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName(); 430 } 431 432 private String getClassName(Component component) 433 { 434 return component.getComponentResources().getComponentModel().getComponentClassName(); 435 } 436 437 public void onComponentTree(MarkupWriter writer) 438 { 439 render(selectedPage.getRootElement(), writer); 440 } 441 442 private void render(ComponentPageElement componentPageElement, MarkupWriter writer) 443 { 444 final Element li = writer.element("li"); 445 final String className = getClassName(componentPageElement.getComponent()); 446 final Set<String> embeddedElementIds = componentPageElement.getEmbeddedElementIds(); 447 448 if (componentPageElement.getComponent().getComponentResources().getComponentModel().isPage()) 449 { 450 li.text(componentPageElement.getPageName()); 451 } 452 else { 453 li.text(String.format("%s (%s)", getDisplayLogicalName(className), componentPageElement.getId())); 454 } 455 456 if (!embeddedElementIds.isEmpty()) 457 { 458 writer.element("ul"); 459 for (String id : embeddedElementIds) 460 { 461 render(componentPageElement.getEmbeddedElement(id), writer); 462 } 463 writer.end(); 464 } 465 466 writer.end(); 467 } 468 469 private String getDisplayLogicalName(final String className) 470 { 471 final String logicalName = resolver.getLogicalName(className); 472 String displayName = logicalName; 473 if (logicalName == null || logicalName.trim().length() == 0) 474 { 475 if (className.contains(".base.")) 476 { 477 displayName = "(base class)"; 478 } 479 if (className.contains(".mixins.")) 480 { 481 displayName = "(mixin)"; 482 } 483 } 484 return displayName; 485 } 486 487 public String getLogicalName(String className) 488 { 489 return resolver.getLogicalName(className); 490 } 491 492 public String getGraphvizValue() 493 { 494 return componentDependencyGraphvizGenerator.generate(getClassName(selectedPage)); 495 } 496 497}