001// Copyright 2010-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.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/// This should have a listener too!
045{
046    private final Logger logger;
047
048    private final ComponentClassResolver resolver;
049
050    private final PageSource pageSource;
051
052    private final RequestGlobals requestGlobals;
053
054    private final Map<String, Page> cache = CollectionFactory.newMap();
055    
056    public RequestPageCacheImpl(Logger logger, ComponentClassResolver resolver, 
057            PageSource pageSource, RequestGlobals requestGlobals)
058    {
059        this.logger = logger;
060        this.resolver = resolver;
061        this.pageSource = pageSource;
062        this.requestGlobals = requestGlobals;
063    }
064
065    @PostInjection
066    public void listenForThreadCleanup(PerthreadManager perthreadManager,
067            @ComponentClasses InvalidationEventHub classesHub)
068    {
069        perthreadManager.addThreadCleanupCallback(this);
070        classesHub.addInvalidationCallback(this::listen);
071    }
072
073    public void run()
074    {
075        for (Page page : cache.values())
076        {
077            try
078            {
079                page.detached();
080            } catch (Throwable t)
081            {
082                logger.error("Error detaching page {}: {}", page, ExceptionUtils.toMessage(t), t);
083            }
084        }
085    }
086
087    public Page get(String pageName)
088    {
089        String canonical = resolver.canonicalizePageName(pageName);
090
091        Page page = cache.get(canonical);
092
093        if (page == null)
094        {
095            page = pageSource.getPage(canonical);
096
097            try
098            {
099                page.attached();
100            } catch (Throwable t)
101            {
102                throw new RuntimeException(String.format("Unable to attach page %s: %s", canonical,
103                        ExceptionUtils.toMessage(t)), t);
104            }
105
106            cache.put(canonical, page);
107        }
108
109        // A bit of a hack but whatever.
110        if (canonical.equals(requestGlobals.getActivePageName()))
111        {
112            requestGlobals.getRequest().setAttribute(InternalConstants.ACTIVE_PAGE_LOADED, true);
113        }
114
115        return page;
116    }
117    
118    private List<String> listen(List<String> resources)
119    {
120        // TODO: we probably don't need this anymore
121        for (String resource : resources) 
122        {
123            if (resolver.isPage(resource))
124            {
125                final String canonicalName = resolver.canonicalizePageName(
126                        resolver.getLogicalName(resource));
127                cache.remove(canonicalName);
128            }
129        }
130        return Collections.emptyList();
131    }
132    
133}