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 alertManager.warn("Component dependency information and page classloader contexts preloaded."); 231 } 232 233 Object onClearPage(String className) 234 { 235 final String logicalName = resolver.getLogicalName(className); 236 classesInvalidationEventHub.fireInvalidationEvent(Arrays.asList(className)); 237 alertManager.warn(String.format("Page %s (%s) has been cleared from the page cache", 238 className, logicalName)); 239 return pagesZone.getBody(); 240 } 241 242 Object onSuccessFromSinglePageLoad() 243 { 244 boolean found = !F.flow(getPages()).filter(new Predicate<Page>() 245 { 246 public boolean accept(Page element) 247 { 248 return element.getName().equals(pageName) && element.getSelector().equals(selector); 249 } 250 }).isEmpty(); 251 252 if (found) 253 { 254 alertManager.warn(String.format("Page %s has already been loaded for '%s'.", 255 pageName, selector.toShortString())); 256 return null; 257 } 258 259 long startTime = System.currentTimeMillis(); 260 261 262 // Load the page now (may cause an exception). 263 264 pageSource.getPage(pageName); 265 266 267 alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName, 268 selector.toShortString(), System.currentTimeMillis() - startTime)); 269 270 return pagesZone.getBody(); 271 } 272 273 private class PageLoadData 274 { 275 int loadedCount; 276 RuntimeException fail; 277 boolean someFail; 278 } 279 280 Object onActionFromForceLoad() 281 { 282 if (failures == null) 283 { 284 failures = CollectionFactory.newSet(); 285 } 286 287 long startTime = System.currentTimeMillis(); 288 289 final Collection<Page> initialPages = getPages(); 290 291 final PageLoadData data = new PageLoadData(); 292 293 for (final String name : resolver.getPageNames()) 294 { 295 if (failures.contains(name)) 296 { 297 alertManager.warn(String.format("Skipping page %s due to prior load failure.", name)); 298 data.someFail = true; 299 continue; 300 } 301 302 operationTracker.run("Loading page " + name, new Runnable() 303 { 304 public void run() 305 { 306 try 307 { 308 Page newPage = pageSource.getPage(name); 309 310 if (!initialPages.contains(newPage)) 311 { 312 data.loadedCount++; 313 } 314 } catch (RuntimeException ex) 315 { 316 alertManager.error(String.format("Page %s failed to load.", name)); 317 failures.add(name); 318 319 if (data.fail == null) 320 { 321 pageName = name; 322 data.fail = ex; 323 } 324 } 325 } 326 }); 327 328 if (data.fail != null) 329 { 330 break; 331 } 332 } 333 334 alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount, 335 selector.toShortString(), System.currentTimeMillis() - startTime)); 336 337 if (data.someFail) 338 { 339 alertManager.warn("Clear the cache to reset the list of failed pages."); 340 } 341 342 if (data.fail != null) 343 { 344 throw data.fail; 345 } 346 347 return pagesZone.getBody(); 348 } 349 350 Object onActionFromClearCaches() 351 { 352 reloadHelper.forceReload(); 353 354 failures = null; 355 356 return pagesZone.getBody(); 357 } 358 359 Object onActionFromStoreDependencyInformation() 360 { 361 362 componentDependencyRegistry.writeFile(); 363 364 alertManager.warn(String.format( 365 "Component dependency information written to %s.", 366 new File(componentDependencyFile).getAbsolutePath())); 367 368 return pagesZone.getBody(); 369 370 } 371 372 Object onActionFromRunGC() 373 { 374 Runtime runtime = Runtime.getRuntime(); 375 376 long initialFreeMemory = runtime.freeMemory(); 377 378 runtime.gc(); 379 380 long delta = runtime.freeMemory() - initialFreeMemory; 381 382 alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.", 383 ((double) delta) / 1024.0d)); 384 385 return pagesZone.getBody(); 386 } 387 388 public String formatElapsed(double millis) 389 { 390 return String.format("%,.3f ms", millis); 391 } 392 393 public List<String> getDependencies() 394 { 395 final String selectedPageClassName = getSelectedPageClassName(); 396 List<String> dependencies = new ArrayList<>(); 397 dependencies.addAll( 398 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.USAGE)); 399 dependencies.addAll( 400 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.INJECT_PAGE)); 401 dependencies.addAll( 402 componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.SUPERCLASS)); 403 Collections.sort(dependencies); 404 return dependencies; 405 } 406 407 public void onPageStructure(String pageName) 408 { 409 selectedPage = pageSource.getPage(pageName); 410 ajaxResponseRenderer.addRender("pageStructureZone", pageStructureZone.getBody()); 411 } 412 413 public String getDisplayLogicalName() 414 { 415 return getDisplayLogicalName(dependency); 416 } 417 418 public String getPageClassName() 419 { 420 return getClassName(page); 421 } 422 423 public String getSelectedPageClassName() 424 { 425 return getClassName(selectedPage); 426 } 427 428 private String getClassName(Page page) 429 { 430 return page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName(); 431 } 432 433 private String getClassName(Component component) 434 { 435 return component.getComponentResources().getComponentModel().getComponentClassName(); 436 } 437 438 public void onComponentTree(MarkupWriter writer) 439 { 440 render(selectedPage.getRootElement(), writer); 441 } 442 443 private void render(ComponentPageElement componentPageElement, MarkupWriter writer) 444 { 445 final Element li = writer.element("li"); 446 final String className = getClassName(componentPageElement.getComponent()); 447 final Set<String> embeddedElementIds = componentPageElement.getEmbeddedElementIds(); 448 449 if (componentPageElement.getComponent().getComponentResources().getComponentModel().isPage()) 450 { 451 li.text(componentPageElement.getPageName()); 452 } 453 else { 454 li.text(String.format("%s (%s)", getDisplayLogicalName(className), componentPageElement.getId())); 455 } 456 457 if (!embeddedElementIds.isEmpty()) 458 { 459 writer.element("ul"); 460 for (String id : embeddedElementIds) 461 { 462 render(componentPageElement.getEmbeddedElement(id), writer); 463 } 464 writer.end(); 465 } 466 467 writer.end(); 468 } 469 470 private String getDisplayLogicalName(final String className) 471 { 472 final String logicalName = resolver.getLogicalName(className); 473 String displayName = logicalName; 474 if (logicalName == null || logicalName.trim().length() == 0) 475 { 476 if (className.contains(".base.")) 477 { 478 displayName = "(base class)"; 479 } 480 if (className.contains(".mixins.")) 481 { 482 displayName = "(mixin)"; 483 } 484 } 485 return displayName; 486 } 487 488 public String getLogicalName(String className) 489 { 490 return resolver.getLogicalName(className); 491 } 492 493 public String getGraphvizValue() 494 { 495 return componentDependencyGraphvizGenerator.generate(getClassName(selectedPage)); 496 } 497 498}