    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    }
    231
    232    Object onClearPage(String className)
    233    {
    234        final String logicalName = resolver.getLogicalName(className);
    235        classesInvalidationEventHub.fireInvalidationEvent(Arrays.asList(className));
    236        alertManager.warn(String.format("Page %s (%s) has been cleared from the page cache",
    237                className, logicalName));
    238        return pagesZone.getBody();
    239    }
    240
    241    Object onSuccessFromSinglePageLoad()
    242    {
    243        boolean found = !F.flow(getPages()).filter(new Predicate<Page>()
    244        {
    245            public boolean accept(Page element)
    246            {
    247                return element.getName().equals(pageName) && element.getSelector().equals(selector);
    248            }
    249        }).isEmpty();
    250
    251        if (found)
    252        {
    253            alertManager.warn(String.format("Page %s has already been loaded for '%s'.",
    254                    pageName, selector.toShortString()));
    255            return null;
    256        }
    257
    258        long startTime = System.currentTimeMillis();
    259
    260
    261        // Load the page now (may cause an exception).
    262
    263        pageSource.getPage(pageName);
    264
    265
    266        alertManager.info(String.format("Loaded page %s for selector '%s' (in %,d ms).", pageName,
    267                selector.toShortString(), System.currentTimeMillis() - startTime));
    268
    269        return pagesZone.getBody();
    270    }
    271
    272    private class PageLoadData
    273    {
    274        int loadedCount;
    275        RuntimeException fail;
    276        boolean someFail;
    277    }
    278
    279    Object onActionFromForceLoad()
    280    {
    281        if (failures == null)
    282        {
    283            failures = CollectionFactory.newSet();
    284        }
    285
    286        long startTime = System.currentTimeMillis();
    287
    288        final Collection<Page> initialPages = getPages();
    289
    290        final PageLoadData data = new PageLoadData();
    291
    292        for (final String name : resolver.getPageNames())
    293        {
    294            if (failures.contains(name))
    295            {
    296                alertManager.warn(String.format("Skipping page %s due to prior load failure.", name));
    297                data.someFail = true;
    298                continue;
    299            }
    300
    301            operationTracker.run("Loading page " + name, new Runnable()
    302            {
    303                public void run()
    304                {
    305                    try
    306                    {
    307                        Page newPage = pageSource.getPage(name);
    308
    309                        if (!initialPages.contains(newPage))
    310                        {
    311                            data.loadedCount++;
    312                        }
    313                    } catch (RuntimeException ex)
    314                    {
    315                        alertManager.error(String.format("Page %s failed to load.", name));
    316                        failures.add(name);
    317
    318                        if (data.fail == null)
    319                        {
    320                            pageName = name;
    321                            data.fail = ex;
    322                        }
    323                    }
    324                }
    325            });
    326
    327            if (data.fail != null)
    328            {
    329                break;
    330            }
    331        }
    332
    333        alertManager.info(String.format("Loaded %,d new pages for selector '%s' (in %,d ms).", data.loadedCount,
    334                selector.toShortString(), System.currentTimeMillis() - startTime));
    335
    336        if (data.someFail)
    337        {
    338            alertManager.warn("Clear the cache to reset the list of failed pages.");
    339        }
    340
    341        if (data.fail != null)
    342        {
    343            throw data.fail;
    344        }
    345
    346        return pagesZone.getBody();
    347    }
    348
    349    Object onActionFromClearCaches()
    350    {
    351        reloadHelper.forceReload();
    352
    353        failures = null;
    354
    355        return pagesZone.getBody();
    356    }
    357
    358    Object onActionFromStoreDependencyInformation()
    359    {
    360
    361        componentDependencyRegistry.writeFile();
    362
    363        alertManager.warn(String.format(
    364                "Component dependency information written to %s.",
    365                new File(componentDependencyFile).getAbsolutePath()));
    366
    367        return pagesZone.getBody();
    368
    369    }
    370
    371    Object onActionFromRunGC()
    372    {
    373        Runtime runtime = Runtime.getRuntime();
    374
    375        long initialFreeMemory = runtime.freeMemory();
    376
    377        runtime.gc();
    378
    379        long delta = runtime.freeMemory() - initialFreeMemory;
    380
    381        alertManager.info(String.format("Garbage collection freed %,.2f Kb of memory.",
    382                ((double) delta) / 1024.0d));
    383
    384        return pagesZone.getBody();
    385    }
    386
    387    public String formatElapsed(double millis)
    388    {
    389        return String.format("%,.3f ms", millis);
    390    }
    391
    392    public List<String> getDependencies()
    393    {
    394        final String selectedPageClassName = getSelectedPageClassName();
    395        List<String> dependencies = new ArrayList<>();
    396        dependencies.addAll(
    397                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.USAGE));
    398        dependencies.addAll(
    399                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.INJECT_PAGE));
    400        dependencies.addAll(
    401                componentDependencyRegistry.getDependencies(selectedPageClassName, DependencyType.SUPERCLASS));
    402        Collections.sort(dependencies);
    403        return dependencies;
    404    }
    405
    406    public void onPageStructure(String pageName)
    407    {
    408        selectedPage = pageSource.getPage(pageName);
    409        ajaxResponseRenderer.addRender("pageStructureZone", pageStructureZone.getBody());
    410    }
    411
    412    public String getDisplayLogicalName()
    413    {
    414        return getDisplayLogicalName(dependency);
    415    }
    416
    417    public String getPageClassName()
    418    {
    419        return getClassName(page);
    420    }
    421
    422    public String getSelectedPageClassName()
    423    {
    424        return getClassName(selectedPage);
    425    }
    426
    427    private String getClassName(Page page)
    428    {
    429        return page.getRootComponent().getComponentResources().getComponentModel().getComponentClassName();
    430    }
    431
    432    private String getClassName(Component component)
    433    {
    434        return component.getComponentResources().getComponentModel().getComponentClassName();
    435    }
    436
    437    public void onComponentTree(MarkupWriter writer)
    438    {
    439        render(selectedPage.getRootElement(), writer);
    440    }
    441
    442    private void render(ComponentPageElement componentPageElement, MarkupWriter writer)
    443    {
    444        final Element li = writer.element("li");
    445        final String className = getClassName(componentPageElement.getComponent());
    446        final Set<String> embeddedElementIds = componentPageElement.getEmbeddedElementIds();
    447
    448        if (componentPageElement.getComponent().getComponentResources().getComponentModel().isPage())
    449        {
    450            li.text(componentPageElement.getPageName());
    451        }
    452        else {
    453            li.text(String.format("%s (%s)", getDisplayLogicalName(className), componentPageElement.getId()));
    454        }
    455
    456        if (!embeddedElementIds.isEmpty())
    457        {
    458            writer.element("ul");
    459            for (String id : embeddedElementIds)
    460            {
    461                render(componentPageElement.getEmbeddedElement(id), writer);
    462            }
    463            writer.end();
    464        }
    465
    466        writer.end();
    467    }
    468
    469    private String getDisplayLogicalName(final String className)
    470    {
    471        final String logicalName = resolver.getLogicalName(className);
    472        String displayName = logicalName;
    473        if (logicalName == null || logicalName.trim().length() == 0)
    474        {
    475            if (className.contains(".base."))
    476            {
    477                displayName = "(base class)";
    478            }
    479            if (className.contains(".mixins."))
    480            {
    481                displayName = "(mixin)";
    482            }
    483        }
    484        return displayName;
    485    }
    486
    487    public String getLogicalName(String className)
    488    {
    489        return resolver.getLogicalName(className);
    490    }
    491
    492    public String getGraphvizValue()
    493    {
    494        return componentDependencyGraphvizGenerator.generate(getClassName(selectedPage));
    495    }
    496
    497}