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