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