All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mcollovati.quarkus.hilla.deployment.AtmospherePatches Maven / Gradle / Ivy

There is a newer version: 24.6.0
Show newest version
/*
 * Copyright 2024 Marco Collovati, Dario Götze
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.mcollovati.quarkus.hilla.deployment;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletConnection;
import java.util.Arrays;
import java.util.ServiceLoader;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiFunction;

import com.vaadin.flow.server.communication.PushRequestHandler;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.ClassTransformer;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.AtmosphereFramework;
import org.atmosphere.inject.InjectableObjectFactory;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.github.mcollovati.quarkus.hilla.graal.AtmosphereDeferredInitializer;
import com.github.mcollovati.quarkus.hilla.graal.AtmosphereServletConfig;

public class AtmospherePatches {

    static final DotName EXECUTORS_FACTORY = DotName.createSimple("org.atmosphere.util.ExecutorsFactory");
    static final DotName DELAYED_EXECUTORS_FACTORY =
            DotName.createSimple("com.github.mcollovati.quarkus.hilla.graal.DelayedSchedulerExecutorsFactory");

    private final IndexView index;

    public AtmospherePatches(IndexView index) {
        this.index = index;
    }

    void apply(BuildProducer producer) {
        producer.produce(
                new BytecodeTransformerBuildItem(EXECUTORS_FACTORY.toString(), patchAtmosphereExecutorsFactory()));
        producer.produce(new BytecodeTransformerBuildItem(
                DELAYED_EXECUTORS_FACTORY.toString(), patchDelayedSchedulerExecutorsFactory()));
        producer.produce(
                new BytecodeTransformerBuildItem(AtmosphereFramework.class.getName(), patchAtmosphereFramework()));
        producer.produce(new BytecodeTransformerBuildItem(
                InjectableObjectFactory.class.getName(), patchInjectableObjectFactory()));

        // Servlet 6 patches
        producer.produce(
                new BytecodeTransformerBuildItem("org.atmosphere.cpr.AtmosphereRequest", patchHttpServletRequest()));
        producer.produce(new BytecodeTransformerBuildItem(
                "org.atmosphere.cpr.AtmosphereRequestImpl", patchHttpServletRequest()));
        producer.produce(new BytecodeTransformerBuildItem(
                "org.atmosphere.cpr.AtmosphereRequestImpl$NoOpsRequest", patchHttpServletRequest()));
        producer.produce(
                new BytecodeTransformerBuildItem("org.atmosphere.cpr.AtmosphereResponse", patchHttpServletResponse()));
        producer.produce(new BytecodeTransformerBuildItem(
                "org.atmosphere.cpr.AtmosphereResponseImpl", patchHttpServletResponse()));
        producer.produce(new BytecodeTransformerBuildItem("org.atmosphere.util.FakeHttpSession", patchHttpSession()));

        producer.produce(
                new BytecodeTransformerBuildItem(PushRequestHandler.class.getName(), patchPushRequestHandler()));
    }

    private BiFunction patchAtmosphereExecutorsFactory() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(EXECUTORS_FACTORY);
            ClassTransformer transformer = new ClassTransformer(EXECUTORS_FACTORY.toString());

            MethodInfo getSchedulerMethod = classInfo.method(
                    "getScheduler", Type.create(DotName.createSimple(AtmosphereConfig.class), Type.Kind.CLASS));
            transformer.modifyMethod(MethodDescriptor.of(getSchedulerMethod)).rename("getScheduler_original");

            try (MethodCreator creator = transformer.addMethod(MethodDescriptor.of(getSchedulerMethod))) {

                creator.setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC);

                creator.returnValue(creator.invokeStaticMethod(
                        MethodDescriptor.ofMethod(
                                DELAYED_EXECUTORS_FACTORY.toString(),
                                "getScheduler",
                                ScheduledExecutorService.class,
                                AtmosphereConfig.class),
                        creator.getMethodParam(0)));
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private BiFunction patchDelayedSchedulerExecutorsFactory() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(DELAYED_EXECUTORS_FACTORY);
            ClassTransformer transformer = new ClassTransformer(EXECUTORS_FACTORY.toString());

            MethodInfo newSchedulerMethod = classInfo.method(
                    "newScheduler", Type.create(DotName.createSimple(AtmosphereConfig.class), Type.Kind.CLASS));

            transformer.removeMethod(MethodDescriptor.of(newSchedulerMethod));

            try (MethodCreator creator = transformer.addMethod(MethodDescriptor.of(newSchedulerMethod))) {

                creator.setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE);

                creator.returnValue(creator.invokeStaticMethod(
                        MethodDescriptor.ofMethod(
                                EXECUTORS_FACTORY.toString(),
                                "getScheduler_original",
                                ScheduledExecutorService.class,
                                AtmosphereConfig.class),
                        creator.getMethodParam(0)));
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private BiFunction patchAtmosphereFramework() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(className);
            ClassTransformer transformer = new ClassTransformer(className);

            MethodInfo info = classInfo.method("info");
            transformer.removeMethod(MethodDescriptor.of(info));

            try (MethodCreator creator = transformer.addMethod(MethodDescriptor.of(info))) {
                creator.setModifiers(Opcodes.ACC_PRIVATE);
                creator.returnValue(null);
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private BiFunction patchPushRequestHandler() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(className);
            ClassTransformer transformer = new ClassTransformer(className);

            MethodInfo initAtmosphereMethod = classInfo.method("initAtmosphere", Type.create(ServletConfig.class));
            transformer.modifyMethod(MethodDescriptor.of(initAtmosphereMethod)).rename("initAtmosphere_original");

            try (MethodCreator creator = transformer.addMethod(MethodDescriptor.of(initAtmosphereMethod))) {

                creator.setModifiers(Opcodes.ACC_STATIC);
                AssignableResultHandle configWrapper = creator.createVariable(AtmosphereServletConfig.class);
                creator.assign(
                        configWrapper,
                        creator.newInstance(
                                MethodDescriptor.ofMethod(
                                        AtmosphereServletConfig.class, "", void.class, ServletConfig.class),
                                creator.getMethodParam(0)));

                AssignableResultHandle framework = creator.createVariable(AtmosphereFramework.class);
                creator.assign(
                        framework,
                        creator.invokeStaticMethod(
                                MethodDescriptor.ofMethod(
                                        initAtmosphereMethod
                                                .declaringClass()
                                                .name()
                                                .toString(),
                                        "initAtmosphere_original",
                                        AtmosphereFramework.class,
                                        ServletConfig.class),
                                configWrapper));
                creator.invokeStaticMethod(
                        MethodDescriptor.ofMethod(
                                AtmosphereDeferredInitializer.class,
                                "register",
                                void.class,
                                ServletConfig.class,
                                AtmosphereFramework.class),
                        creator.getMethodParam(0),
                        framework);
                creator.returnValue(framework);
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private BiFunction patchInjectableObjectFactory() {
        return (className, classVisitor) -> {
            ClassTransformer transformer = new ClassTransformer(className);
            transformer.removeField("injectableServiceLoader", ServiceLoader.class);
            return new ClassVisitor(Gizmo.ASM_API_VERSION, transformer.applyTo(classVisitor)) {

                @Override
                public MethodVisitor visitMethod(
                        int access, String name, String descriptor, String signature, String[] exceptions) {
                    // Only transform the configure method
                    MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
                    if ("".equals(name) || "configure".equals(name)) {
                        return new InjectableObjectFactoryFieldUsageRemovalMethodVisitor(
                                mv, access, name, descriptor, signature, exceptions);
                    }
                    return mv;
                }
            };
        };
    }

    // Servlet 6 API
    private BiFunction patchHttpServletRequest() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(className);
            ClassTransformer transformer = new ClassTransformer(className);

            // Delete methods removed in Servlet 6 API
            removeMethod(transformer, classInfo, "getRealPath", String.class, String.class);
            removeMethod(transformer, classInfo, "isRequestedSessionIdFromUrl", boolean.class);

            // Implement missing method added in Servlet 6 API
            // Only for AtmosphereRequestImpl$NoOpsRequest, that does not extend
            // HttpServletRequestWrapper
            if (classInfo.method("getRequestId") == null) {
                try (MethodCreator creator = transformer.addMethod("getRequestId", String.class)) {
                    creator.returnNull();
                }
            }
            if (classInfo.method("getProtocolRequestId") == null) {
                try (MethodCreator creator = transformer.addMethod("getProtocolRequestId", String.class)) {
                    creator.returnNull();
                }
            }
            if (classInfo.method("getServletConnection") == null) {
                try (MethodCreator creator = transformer.addMethod("getServletConnection", ServletConnection.class)) {
                    creator.returnNull();
                }
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private static void removeMethod(
            ClassTransformer transformer,
            ClassInfo classInfo,
            String methodName,
            Class returnType,
            Class... parameterTypes) {
        if (classInfo.method(
                        methodName,
                        Arrays.stream(parameterTypes).map(Type::create).toList())
                != null) {
            transformer.removeMethod(methodName, returnType, (Object[]) parameterTypes);
        }
    }

    private BiFunction patchHttpServletResponse() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(className);
            ClassTransformer transformer = new ClassTransformer(className);

            // Delete methods removed in Servlet 6 API
            removeMethod(transformer, classInfo, "encodeUrl", String.class, String.class);
            removeMethod(transformer, classInfo, "encodeRedirectUrl", String.class, String.class);
            removeMethod(transformer, classInfo, "setStatus", void.class, int.class, String.class);

            if ("org.atmosphere.cpr.AtmosphereResponseImpl".equals(className)) {
                // Redefine setStatus(int,String) to redirect to setStatus(int)
                try (MethodCreator creator = transformer.addMethod("setStatus", void.class, int.class, String.class)) {
                    creator.invokeVirtualMethod(
                            MethodDescriptor.ofMethod(className, "setStatus", "V", "I"),
                            creator.getThis(),
                            creator.getMethodParam(0));
                    creator.returnValue(null);
                }
            }
            return transformer.applyTo(classVisitor);
        };
    }

    private BiFunction patchHttpSession() {
        return (className, classVisitor) -> {
            ClassInfo classInfo = index.getClassByName(className);
            ClassTransformer transformer = new ClassTransformer(className);

            // Delete methods removed in Servlet 6 API
            if (classInfo.method("getSessionContext") != null) {
                transformer.removeMethod("getSessionContext", "jakarta.servlet.http.HttpSessionContext");
            }
            removeMethod(transformer, classInfo, "getValue", Object.class, String.class);
            removeMethod(transformer, classInfo, "getValueNames", String[].class);
            removeMethod(transformer, classInfo, "putValue", void.class, String.class, Object.class);
            removeMethod(transformer, classInfo, "removeValue", void.class, String.class);
            return transformer.applyTo(classVisitor);
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy