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}