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}