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    }