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 }