    001// Copyright 2010-2024 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.internal.services;
    016
    017import java.util.Collections;
    018import java.util.List;
    019import java.util.Map;
    020
    021import org.apache.tapestry5.commons.services.InvalidationEventHub;
    022import org.apache.tapestry5.commons.util.CollectionFactory;
    023import org.apache.tapestry5.commons.util.ExceptionUtils;
    024import org.apache.tapestry5.http.services.RequestGlobals;
    025import org.apache.tapestry5.internal.InternalConstants;
    026import org.apache.tapestry5.internal.structure.Page;
    027import org.apache.tapestry5.ioc.ScopeConstants;
    028import org.apache.tapestry5.ioc.annotations.ComponentClasses;
    029import org.apache.tapestry5.ioc.annotations.PostInjection;
    030import org.apache.tapestry5.ioc.annotations.Scope;
    031import org.apache.tapestry5.ioc.services.PerthreadManager;
    032import org.apache.tapestry5.services.ComponentClassResolver;
    033import org.slf4j.Logger;
    034
    035/**
    036 * In Tapestry 5.1, the implementation of this worked with the page pool (a pool of page instances, reserved
    037 * to individual requests/threads). Page pooling was deprecated in 5.2 and removed in 5.3.
    038 *
    039 * @since 5.2
    040 */
    041@Scope(ScopeConstants.PERTHREAD)
    042public class RequestPageCacheImpl implements RequestPageCache, Runnable
    043{
    044    private final Logger logger;
    045
    046    private final ComponentClassResolver resolver;
    047
    048    private final PageSource pageSource;
    049
    050    private final RequestGlobals requestGlobals;
    051
    052    private final Map<String, Page> cache = CollectionFactory.newMap();
    053
    054    public RequestPageCacheImpl(Logger logger, ComponentClassResolver resolver,
    055            PageSource pageSource, RequestGlobals requestGlobals)
    056    {
    057        this.logger = logger;
    058        this.resolver = resolver;
    059        this.pageSource = pageSource;
    060        this.requestGlobals = requestGlobals;
    061    }
    062
    063    @PostInjection
    064    public void listenForThreadCleanup(PerthreadManager perthreadManager,
    065            @ComponentClasses InvalidationEventHub classesHub)
    066    {
    067        perthreadManager.addThreadCleanupCallback(this);
    068        classesHub.addInvalidationCallback(this::listen);
    069    }
    070
    071    public void run()
    072    {
    073        for (Page page : cache.values())
    074        {
    075            try
    076            {
    077                page.detached();
    078            } catch (Throwable t)
    079            {
    080                logger.error("Error detaching page {}: {}", page, ExceptionUtils.toMessage(t), t);
    081            }
    082        }
    083    }
    084
    085    public Page get(String pageName)
    086    {
    087        String canonical = resolver.canonicalizePageName(pageName);
    088
    089        Page page = cache.get(canonical);
    090
    091        if (page == null)
    092        {
    093            page = pageSource.getPage(canonical);
    094
    095            try
    096            {
    097                page.attached();
    098            } catch (Throwable t)
    099            {
    100                throw new RuntimeException(String.format("Unable to attach page %s: %s", canonical,
    101                        ExceptionUtils.toMessage(t)), t);
    102            }
    103
    104            cache.put(canonical, page);
    105        }
    106
    107        // A bit of a hack but whatever.
    108        if (canonical.equals(requestGlobals.getActivePageName()))
    109        {
    110            requestGlobals.getRequest().setAttribute(InternalConstants.ACTIVE_PAGE_LOADED, true);
    111        }
    112
    113        return page;
    114    }
    115
    116    private List<String> listen(List<String> resources)
    117    {
    118        if (resources.isEmpty())
    119        {
    120            cache.clear();
    121        }
    122        // TODO: we probably don't need this anymore
    123        for (String resource : resources)
    124        {
    125            if (resolver.isPage(resource))
    126            {
    127                final String canonicalName = resolver.canonicalizePageName(
    128                        resolver.getLogicalName(resource));
    129                cache.remove(canonicalName);
    130            }
    131        }
    132        return Collections.emptyList();
    133    }
    134
    135}