    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 java.io.File;
    018import java.util.ArrayList;
    019import java.util.Arrays;
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.List;
    023import java.util.Set;
    024
    025import org.apache.tapestry5.MarkupWriter;
    026import org.apache.tapestry5.SymbolConstants;
    027import org.apache.tapestry5.alerts.AlertManager;
    028import org.apache.tapestry5.annotations.InjectComponent;
    029import org.apache.tapestry5.annotations.Persist;
    030import org.apache.tapestry5.annotations.Property;
    031import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
    032import org.apache.tapestry5.annotations.WhitelistAccessOnly;
    033import org.apache.tapestry5.beaneditor.Validate;
    034import org.apache.tapestry5.beanmodel.BeanModel;
    035import org.apache.tapestry5.beanmodel.services.BeanModelSource;
    036import org.apache.tapestry5.commons.Messages;
    037import org.apache.tapestry5.commons.services.InvalidationEventHub;
    038import org.apache.tapestry5.commons.util.CollectionFactory;
    039import org.apache.tapestry5.corelib.components.Zone;
    040import org.apache.tapestry5.dom.Element;
    041import org.apache.tapestry5.func.F;
    042import org.apache.tapestry5.func.Flow;
    043import org.apache.tapestry5.func.Mapper;
    044import org.apache.tapestry5.func.Predicate;
    045import org.apache.tapestry5.func.Reducer;
    046import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
    047import org.apache.tapestry5.http.services.Request;
    048import org.apache.tapestry5.internal.PageCatalogTotals;
    049import org.apache.tapestry5.internal.services.ComponentDependencyGraphvizGenerator;
    050import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
    051import org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
    052import org.apache.tapestry5.internal.services.PageSource;
    053import org.apache.tapestry5.internal.services.ReloadHelper;
    054import org.apache.tapestry5.internal.structure.ComponentPageElement;
    055import org.apache.tapestry5.internal.structure.Page;
    056import org.apache.tapestry5.ioc.OperationTracker;
    057import org.apache.tapestry5.ioc.annotations.ComponentClasses;
    058import org.apache.tapestry5.ioc.annotations.Inject;
    059import org.apache.tapestry5.ioc.annotations.Symbol;
    060import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    061import org.apache.tapestry5.runtime.Component;
    062import org.apache.tapestry5.services.ComponentClassResolver;
    063import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
    064import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    065import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
    066import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
    067
    068/**
    069 * Lists out the currently loaded pages, using a {@link org.apache.tapestry5.corelib.components.Grid}.
    070 * Provides an option to force all pages to be loaded. In development mode, includes an option to clear the page cache.
    071 */
    072@UnknownActivationContextCheck(false)
    073@WhitelistAccessOnly
    074public class PageCatalog
    075{
    076
    077    @Property
    078    private PageCatalogTotals totals;
    079
    080    @Property
    081    @Inject
    082    @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
    083    private boolean productionMode;
    084
    085    @Property
    086    @Inject
    087    @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
    088    private boolean multipleClassLoaders;
    089
    090    @Inject
    091    @Symbol(SymbolConstants.COMPONENT_DEPENDENCY_FILE)
    092    private String componentDependencyFile;
    093
    094    @Inject
    095    private PageSource pageSource;
    096
    097    @Inject
    098    private ComponentResourceSelector selector;
    099
    100    @Inject
    101    private ComponentClassResolver resolver;
    102
    103    @Inject
    104    private ComponentDependencyRegistry componentDependencyRegistry;
    105
    106    @Inject
    107    private AlertManager alertManager;
    108
    109    @Property
    110    private Page page;
    111
    112    @Property
    113    private Page selectedPage;
    114
    115    @Property
    116    private String dependency;
    117
    118    @InjectComponent
    119    private Zone pagesZone;
    120
    121    @InjectComponent
    122    private Zone pageStructureZone;
    123
    124    @Persist
    125    private Set<String> failures;
    126
    127    @Property
    128    @Validate("required")
    129    @Persist
    130    private String pageName;
    131
    132    @Inject
    133    private OperationTracker operationTracker;
    134
    135    @Inject
    136    private ReloadHelper reloadHelper;
    137
    138    @Inject
    139    private BeanModelSource beanModelSource;
    140
    141    @Inject
    142    private Messages messages;
    143
    144    @Property
    145    public static BeanModel<Page> model;
    146
    147    @Inject
    148    private Request request;
    149
    150    @Inject
    151    @ComponentClasses
    152    private InvalidationEventHub classesInvalidationEventHub;
    153
    154    @Inject
    155    private JavaScriptSupport javaScriptSupport;
    156
    157    @Inject
    158    private ComponentDependencyGraphvizGenerator componentDependencyGraphvizGenerator;
    159
    160    @Inject
    161    private ComponentClassResolver componentClassResolver;
    162
    163    @Inject
    164    private AjaxResponseRenderer ajaxResponseRenderer;
    165
    166    @Inject
    167    private PageClassLoaderContextManager pageClassLoaderContextManager;
    168
    169    void pageLoaded()
    170    {
    171        model = beanModelSource.createDisplayModel(Page.class, messages);
    172
    173        model.addExpression("selector", "selector.toString()");
    174        model.addExpression("assemblyTime", "stats.assemblyTime");
    175        model.addExpression("componentCount", "stats.componentCount");
    176        model.addExpression("weight", "stats.weight");
    177        model.add("clear", null);
    178
    179        model.reorder("name", "selector", "assemblyTime", "componentCount", "weight");
    180    }
    181
    182    public void onRecomputeTotals()
    183    {
    184        totals = new PageCatalogTotals();
    185
    186        Flow<Page> pages = F.flow(getPages());
    187
    188        totals.loadedPages = pages.count();
    189        totals.definedPages = getPageNames().size();
    190        totals.uniquePageNames = pages.map(new Mapper<Page, String>()
    191        {
    192            public String map(Page element)
    193            {
    194                return element.getName();
    195            }
    196        }).toSet().size();
    197
    198        totals.components = pages.reduce(new Reducer<Integer, Page>()
    199        {
    200            public Integer reduce(Integer accumulator, Page element)
    201            {
    202                return accumulator + element.getStats().componentCount;
    203            }
    204        }, 0);
    205
    206        Set<String> selectorIds = pages.map(new Mapper<Page, String>()
    207        {
    208            public String map(Page element)
    209            {
    210                return element.getSelector().toShortString();
    211            }
    212        }).toSet();
    213
    214        totals.selectors = InternalUtils.joinSorted(selectorIds);
    215    }
    216
    217    public List<String> getPageNames()
    218    {
    219        return resolver.getPageNames();
    220    }
    221
    222    public Collection<Page> getPages()
    223    {
    224        return pageSource.getAllPages();
    225    }
    226
    227    void onActionFromPreloadPageClassLoaderContexts()
    228    {
    229        pageClassLoaderContextManager.preload();
    230        alertManager.warn("Component dependency information and page classloader contexts preloaded.");
    231    }
    232
    233    Object onClearPage(String className)
    234    {
    235        final String logicalName = resolver.getLogicalName(className);
    236        classesInvalidationEventHub.fireInvalidationEvent(Arrays.asList(className));
    237        alertManager.warn(String.format("Page %s (%s) has been cleared from the page cache",
    238                className, logicalName));
    239        return pagesZone.getBody();
    240    }
    241
    242    Object onSuccessFromSinglePageLoad()
    243    {
    244        boolean found = !F.flow(getPages()).filter(new Predicate<Page>()
    245        {
    246            public boolean accept(Page element)
    247            {
    248                return element.getName().equals(pageName) && element.getSelector().equals(selector);
    249            }
    250        }).isEmpty();
    251
    252        if (found)
    253        {
    254            alertManager.warn(String.format("Page %s has already been loaded for '%s'.",
    255                    pageName, selector.toShortString()));
    256            return null;
    257        }
    258
    259        long startTime = System.currentTimeMillis();
    260
    261
    262        // Load the page now (may cause an exception).
    263
    264        pageSource.getPage(pageName);
    265
    266
    267        alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName,
    268                selector.toShortString(), System.currentTimeMillis() - startTime));
    269
    270        return pagesZone.getBody();
    271    }
    272
    273    private class PageLoadData
    274    {
    275        int loadedCount;
    276        RuntimeException fail;
    277        boolean someFail;
    278    }
    279
    280    Object onActionFromForceLoad()
    281    {
    282        if (failures == null)
    283        {
    284            failures = CollectionFactory.newSet();
    285        }
    286
    287        long startTime = System.currentTimeMillis();
    288
    289        final Collection<Page> initialPages = getPages();
    290
    291        final PageLoadData data = new PageLoadData();
    292
    293        for (final String name : resolver.getPageNames())
    294        {
    295            if (failures.contains(name))
    296            {
    297                alertManager.warn(String.format("Skipping page %s due to prior load failure.", name));
    298                data.someFail = true;
    299                continue;
    300            }
    301
    302            operationTracker.run("Loading page " + name, new Runnable()
    303            {
    304                public void run()
    305                {
    306                    try
    307                    {
    308                        Page newPage = pageSource.getPage(name);
    309
    310                        if (!initialPages.contains(newPage))
    311                        {
    312                            data.loadedCount++;
    313                        }
    314                    } catch (RuntimeException ex)
    315                    {
    316                        alertManager.error(String.format("Page %s failed to load.", name));
    317                        failures.add(name);
    318
    319                        if (data.fail == null)
    320                        {
    321                            pageName = name;
    322                            data.fail = ex;
    323                        }
    324                    }
    325                }
    326            });
    327
    328            if (data.fail != null)
    329            {
    330                break;
    331            }
    332        }
    333
    334        alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount,
    335                selector.toShortString(), System.currentTimeMillis() - startTime));
    336
    337        if (data.someFail)
    338        {
    339            alertManager.warn("Clear the cache to reset the list of failed pages.");
    340        }
    341
    342        if (data.fail != null)
    343        {
    344            throw data.fail;
    345        }
    346
    347        return pagesZone.getBody();
    348    }
    349
    350    Object onActionFromClearCaches()
    351    {
    352        reloadHelper.forceReload();
    353
    354        failures = null;
    355
    356        return pagesZone.getBody();
    357    }
    358
    359    Object onActionFromStoreDependencyInformation()
    360    {
    361
    362        componentDependencyRegistry.writeFile();
    363
    364        alertManager.warn(String.format(
    365                "Component dependency information written to %s.",
    366                new File(componentDependencyFile).getAbsolutePath()));
    367
    368        return pagesZone.getBody();
    369
    370    }
    371
    372    Object onActionFromRunGC()
    373    {
    374        Runtime runtime = Runtime.getRuntime();
    375
    376        long initialFreeMemory = runtime.freeMemory();
    377
    378        runtime.gc();
    379
    380        long delta = runtime.freeMemory() - initialFreeMemory;
    381
    382        alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.",
    383                ((double) delta) / 1024.0d));
    384
    385        return pagesZone.getBody();
    386    }
    387
    388    public String formatElapsed(double millis)
    389    {
    390        return String.format("%,.3f ms", millis);
    391    }
    392
    393    public List<String> getDependencies()
    394    {
    395        final String selectedPageClassName = getSelectedPageClassName();
    396        List<String> dependencies = new ArrayList<>();
    397        dependencies.addAll(
    398                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.USAGE));
    399        dependencies.addAll(
    400                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.INJECT_PAGE));
    401        dependencies.addAll(
    402                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.SUPERCLASS));
    403        Collections.sort(dependencies);
    404        return dependencies;
    405    }
    406
    407    public void onPageStructure(String pageName)
    408    {
    409        selectedPage = pageSource.getPage(pageName);
    410        ajaxResponseRenderer.addRender("pageStructureZone", pageStructureZone.getBody());
    411    }
    412
    413    public String getDisplayLogicalName()
    414    {
    415        return getDisplayLogicalName(dependency);
    416    }
    417
    418    public String getPageClassName()
    419    {
    420        return getClassName(page);
    421    }
    422
    423    public String getSelectedPageClassName()
    424    {
    425        return getClassName(selectedPage);
    426    }
    427
    428    private String getClassName(Page page)
    429    {
    430        return page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName();
    431    }
    432
    433    private String getClassName(Component component)
    434    {
    435        return component.getComponentResources().getComponentModel().getComponentClassName();
    436    }
    437
    438    public void onComponentTree(MarkupWriter writer)
    439    {
    440        render(selectedPage.getRootElement(), writer);
    441    }
    442
    443    private void render(ComponentPageElement componentPageElement, MarkupWriter writer)
    444    {
    445        final Element li = writer.element("li");
    446        final String className = getClassName(componentPageElement.getComponent());
    447        final Set<String> embeddedElementIds = componentPageElement.getEmbeddedElementIds();
    448
    449        if (componentPageElement.getComponent().getComponentResources().getComponentModel().isPage())
    450        {
    451            li.text(componentPageElement.getPageName());
    452        }
    453        else {
    454            li.text(String.format("%s (%s)", getDisplayLogicalName(className), componentPageElement.getId()));
    455        }
    456
    457        if (!embeddedElementIds.isEmpty())
    458        {
    459            writer.element("ul");
    460            for (String id : embeddedElementIds)
    461            {
    462                render(componentPageElement.getEmbeddedElement(id), writer);
    463            }
    464            writer.end();
    465        }
    466
    467        writer.end();
    468    }
    469
    470    private String getDisplayLogicalName(final String className)
    471    {
    472        final String logicalName = resolver.getLogicalName(className);
    473        String displayName = logicalName;
    474        if (logicalName == null || logicalName.trim().length() == 0)
    475        {
    476            if (className.contains(".base."))
    477            {
    478                displayName = "(base class)";
    479            }
    480            if (className.contains(".mixins."))
    481            {
    482                displayName = "(mixin)";
    483            }
    484        }
    485        return displayName;
    486    }
    487
    488    public String getLogicalName(String className)
    489    {
    490        return resolver.getLogicalName(className);
    491    }
    492
    493    public String getGraphvizValue()
    494    {
    495        return componentDependencyGraphvizGenerator.generate(getClassName(selectedPage));
    496    }
    497
    498}