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 org.apache.tapestry5.SymbolConstants; 018import org.apache.tapestry5.alerts.AlertManager; 019import org.apache.tapestry5.annotations.*; 020import org.apache.tapestry5.beaneditor.BeanModel; 021import org.apache.tapestry5.beaneditor.Validate; 022import org.apache.tapestry5.corelib.components.Zone; 023import org.apache.tapestry5.func.*; 024import org.apache.tapestry5.internal.PageCatalogTotals; 025import org.apache.tapestry5.internal.services.PageSource; 026import org.apache.tapestry5.internal.services.ReloadHelper; 027import org.apache.tapestry5.internal.structure.Page; 028import org.apache.tapestry5.ioc.Messages; 029import org.apache.tapestry5.ioc.OperationTracker; 030import org.apache.tapestry5.ioc.annotations.Inject; 031import org.apache.tapestry5.ioc.annotations.Symbol; 032import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 033import org.apache.tapestry5.ioc.internal.util.InternalUtils; 034import org.apache.tapestry5.services.BeanModelSource; 035import org.apache.tapestry5.services.ComponentClassResolver; 036import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 037 038import java.util.Collection; 039import java.util.List; 040import 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@UnknownActivationContextCheck(false) 047@WhitelistAccessOnly 048public 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 ReloadHelper reloadHelper; 090 091 @Inject 092 private BeanModelSource beanModelSource; 093 094 @Inject 095 private Messages messages; 096 097 @Property 098 public static BeanModel<Page> model; 099 100 void pageLoaded() 101 { 102 model = beanModelSource.createDisplayModel(Page.class, messages); 103 104 model.addExpression("selector", "selector.toString()"); 105 model.addExpression("assemblyTime", "stats.assemblyTime"); 106 model.addExpression("componentCount", "stats.componentCount"); 107 model.addExpression("weight", "stats.weight"); 108 109 model.reorder("name", "selector", "assemblyTime", "componentCount", "weight"); 110 } 111 112 public void onRecomputeTotals() 113 { 114 totals = new PageCatalogTotals(); 115 116 Flow<Page> pages = F.flow(getPages()); 117 118 totals.loadedPages = pages.count(); 119 totals.definedPages = getPageNames().size(); 120 totals.uniquePageNames = pages.map(new Mapper<Page, String>() 121 { 122 public String map(Page element) 123 { 124 return element.getName(); 125 } 126 }).toSet().size(); 127 128 totals.components = pages.reduce(new Reducer<Integer, Page>() 129 { 130 public Integer reduce(Integer accumulator, Page element) 131 { 132 return accumulator + element.getStats().componentCount; 133 } 134 }, 0); 135 136 Set<String> selectorIds = pages.map(new Mapper<Page, String>() 137 { 138 public String map(Page element) 139 { 140 return element.getSelector().toShortString(); 141 } 142 }).toSet(); 143 144 totals.selectors = InternalUtils.joinSorted(selectorIds); 145 } 146 147 public List<String> getPageNames() 148 { 149 return resolver.getPageNames(); 150 } 151 152 public Collection<Page> getPages() 153 { 154 return pageSource.getAllPages(); 155 } 156 157 Object onSuccessFromSinglePageLoad() 158 { 159 boolean found = !F.flow(getPages()).filter(new Predicate<Page>() 160 { 161 public boolean accept(Page element) 162 { 163 return element.getName().equals(pageName) && element.getSelector().equals(selector); 164 } 165 }).isEmpty(); 166 167 if (found) 168 { 169 alertManager.warn(String.format("Page %s has already been loaded for '%s'.", 170 pageName, selector.toShortString())); 171 return null; 172 } 173 174 long startTime = System.currentTimeMillis(); 175 176 177 // Load the page now (may cause an exception). 178 179 pageSource.getPage(pageName); 180 181 182 alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName, 183 selector.toShortString(), System.currentTimeMillis() - startTime)); 184 185 return pagesZone.getBody(); 186 } 187 188 private class PageLoadData 189 { 190 int loadedCount; 191 RuntimeException fail; 192 boolean someFail; 193 } 194 195 Object onActionFromForceLoad() 196 { 197 if (failures == null) 198 { 199 failures = CollectionFactory.newSet(); 200 } 201 202 long startTime = System.currentTimeMillis(); 203 204 final Collection<Page> initialPages = getPages(); 205 206 final PageLoadData data = new PageLoadData(); 207 208 for (final String name : resolver.getPageNames()) 209 { 210 if (failures.contains(name)) 211 { 212 alertManager.warn(String.format("Skipping page %s due to prior load failure.", name)); 213 data.someFail = true; 214 continue; 215 } 216 217 operationTracker.run("Loading page " + name, new Runnable() 218 { 219 public void run() 220 { 221 try 222 { 223 Page newPage = pageSource.getPage(name); 224 225 if (!initialPages.contains(newPage)) 226 { 227 data.loadedCount++; 228 } 229 } catch (RuntimeException ex) 230 { 231 alertManager.error(String.format("Page %s failed to load.", name)); 232 failures.add(name); 233 234 if (data.fail == null) 235 { 236 pageName = name; 237 data.fail = ex; 238 } 239 } 240 } 241 }); 242 243 if (data.fail != null) 244 { 245 break; 246 } 247 } 248 249 alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount, 250 selector.toShortString(), System.currentTimeMillis() - startTime)); 251 252 if (data.someFail) 253 { 254 alertManager.warn("Clear the cache to reset the list of failed pages."); 255 } 256 257 if (data.fail != null) 258 { 259 throw data.fail; 260 } 261 262 return pagesZone.getBody(); 263 } 264 265 Object onActionFromClearCaches() 266 { 267 reloadHelper.forceReload(); 268 269 failures = null; 270 271 return pagesZone.getBody(); 272 } 273 274 Object onActionFromRunGC() 275 { 276 Runtime runtime = Runtime.getRuntime(); 277 278 long initialFreeMemory = runtime.freeMemory(); 279 280 runtime.gc(); 281 282 long delta = runtime.freeMemory() - initialFreeMemory; 283 284 alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.", 285 ((double) delta) / 1024.0d)); 286 287 return pagesZone.getBody(); 288 } 289 290 public String formatElapsed(double millis) 291 { 292 return String.format("%,.3f ms", millis); 293 } 294}