    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}