    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.alerts.AlertManager;
    018import org.apache.tapestry5.annotations.*;
    019import org.apache.tapestry5.beaneditor.Validate;
    020import org.apache.tapestry5.beanmodel.BeanModel;
    021import org.apache.tapestry5.beanmodel.services.BeanModelSource;
    022import org.apache.tapestry5.commons.Messages;
    023import org.apache.tapestry5.commons.util.CollectionFactory;
    024import org.apache.tapestry5.corelib.components.Zone;
    025import org.apache.tapestry5.func.*;
    026import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
    027import org.apache.tapestry5.internal.PageCatalogTotals;
    028import org.apache.tapestry5.internal.services.PageSource;
    029import org.apache.tapestry5.internal.services.ReloadHelper;
    030import org.apache.tapestry5.internal.structure.Page;
    031import org.apache.tapestry5.ioc.OperationTracker;
    032import org.apache.tapestry5.ioc.annotations.Inject;
    033import org.apache.tapestry5.ioc.annotations.Symbol;
    034import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    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(TapestryHttpSymbolConstants.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}