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}