001// Copyright 2009, 2024 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.io.IOException; 018import java.util.Collections; 019import java.util.regex.Matcher; 020import java.util.regex.Pattern; 021 022import org.apache.tapestry5.commons.services.InvalidationEventHub; 023import org.apache.tapestry5.ioc.annotations.ComponentClasses; 024import org.apache.tapestry5.services.ComponentEventRequestHandler; 025import org.apache.tapestry5.services.ComponentEventRequestParameters; 026import org.apache.tapestry5.services.ComponentRequestHandler; 027import org.apache.tapestry5.services.PageRenderRequestHandler; 028import org.apache.tapestry5.services.PageRenderRequestParameters; 029import org.apache.tapestry5.services.Traditional; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Terminator for the {@link org.apache.tapestry5.services.ComponentRequestHandler} pipeline, that feeds out into the 035 * {@link org.apache.tapestry5.services.ComponentEventRequestHandler} and {@link org.apache.tapestry5.services.PageRenderRequestHandler} 036 * pipelines. 037 * 038 * @since 5.1.0.0 039 */ 040public class ComponentRequestHandlerTerminator implements ComponentRequestHandler 041{ 042 private static final Logger LOGGER = LoggerFactory.getLogger(ComponentRequestHandlerTerminator.class); 043 044 private final ComponentEventRequestHandler componentEventRequestHandler; 045 046 private final PageRenderRequestHandler pageRenderRequestHandler; 047 048 private final InvalidationEventHub invalidationEventHub; 049 050 private final ComponentDependencyRegistry componentDependencyRegistry; 051 052 public ComponentRequestHandlerTerminator(@Traditional ComponentEventRequestHandler componentEventRequestHandler, 053 PageRenderRequestHandler pageRenderRequestHandler, 054 final @ComponentClasses InvalidationEventHub invalidationEventHub, 055 final ComponentDependencyRegistry componentDependencyRegistry) 056 { 057 this.componentEventRequestHandler = componentEventRequestHandler; 058 this.pageRenderRequestHandler = pageRenderRequestHandler; 059 this.invalidationEventHub = invalidationEventHub; 060 this.componentDependencyRegistry = componentDependencyRegistry; 061 } 062 063 public void handleComponentEvent(ComponentEventRequestParameters parameters) throws IOException 064 { 065 boolean retry = run(() -> componentEventRequestHandler.handle(parameters)); 066 if (retry) 067 { 068 componentEventRequestHandler.handle(parameters); 069 } 070 } 071 072 public void handlePageRender(PageRenderRequestParameters parameters) throws IOException 073 { 074 boolean retry = run(() -> pageRenderRequestHandler.handle(parameters)); 075 if (retry) 076 { 077 pageRenderRequestHandler.handle(parameters); 078 } 079 } 080 081 private static final Pattern PATTERN = Pattern.compile( 082 "class (\\S+) cannot be cast to class (\\S+).*"); 083 084 private static interface RunnableWithIOException 085 { 086 public void run() throws IOException; 087 } 088 089 private boolean run(RunnableWithIOException runnable) throws IOException 090 { 091 boolean retry = false; 092 try { 093 runnable.run(); 094 } 095 catch (RuntimeException e) 096 { 097 Throwable throwable = e; 098 while (!(throwable instanceof ClassCastException) && throwable.getCause() != null) 099 { 100 throwable = throwable.getCause(); 101 } 102 if (throwable instanceof ClassCastException && throwable != null) 103 { 104 Matcher matcher = PATTERN.matcher(throwable.getMessage()); 105 if (matcher.matches() && matcher.groupCount() >= 2 && 106 isTransformed(matcher.group(1)) && 107 isTransformed(matcher.group(2))) 108 { 109 LOGGER.warn("Caught exception and trying to recover by invalidating generated classes caches: {}", 110 throwable.getMessage()); 111 componentDependencyRegistry.disableInvalidations(); 112 invalidationEventHub.fireInvalidationEvent(Collections.emptyList()); 113 componentDependencyRegistry.enableInvalidations(); 114 retry = true; 115 } 116 } 117 else 118 { 119 throw e; 120 } 121 } 122 return retry; 123 } 124 125 /** 126 * Very simple, but fast, implementation. 127 */ 128 private boolean isTransformed(String className) 129 { 130 return className != null && ( 131 className.contains(".pages.") || 132 className.contains(".components.") || 133 className.contains(".base.")); 134 135 } 136 137}