    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    }