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}