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

org.glassfish.jersey.server.model.ResourceMethodValidator Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.6
Show newest version
/*
 * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.server.model;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.MatrixParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.sse.SseEventSink;

import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.spi.internal.ParameterValueHelper;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;

/**
 * Validator checking resource methods and sub resource locators. The validator mainly checks the parameters of resource
 * methods and sub resource locators.
 *
 * @author Miroslav Fuksa
 */
class ResourceMethodValidator extends AbstractResourceModelVisitor {

    private final Collection valueParamProviders;

    ResourceMethodValidator(Collection valueParamProviders) {
        this.valueParamProviders = valueParamProviders;
    }

    @Override
    public void visitResourceMethod(final ResourceMethod method) {
        switch (method.getType()) {
            case RESOURCE_METHOD:
                visitJaxrsResourceMethod(method);
                break;
            case SUB_RESOURCE_LOCATOR:
                visitSubResourceLocator(method);
                break;
        }

    }

    private void visitJaxrsResourceMethod(ResourceMethod method) {
        checkMethod(method);
    }

    private void checkMethod(ResourceMethod method) {
        checkValueProviders(method);
        final Invocable invocable = method.getInvocable();

        checkParameters(method);

        if ("GET".equals(method.getHttpMethod())) {
            final long eventSinkCount = invocable.getParameters()
                    .stream()
                    .filter(parameter -> SseEventSink.class.equals(parameter.getRawType()))
                    .count();

            final boolean isSse = eventSinkCount > 0;

            if (eventSinkCount > 1) {
                Errors.warning(method, LocalizationMessages.MULTIPLE_EVENT_SINK_INJECTION(invocable.getHandlingMethod()));
            }
            // ensure GET returns non-void value if not suspendable
            if (void.class == invocable.getHandlingMethod().getReturnType() && !method.isSuspendDeclared() && !isSse) {
                Errors.hint(method, LocalizationMessages.GET_RETURNS_VOID(invocable.getHandlingMethod()));
            }

            // ensure GET does not consume an entity parameter, if not inflector-based
            if (invocable.requiresEntity() && !invocable.isInflector()) {
                Errors.warning(method, LocalizationMessages.GET_CONSUMES_ENTITY(invocable.getHandlingMethod()));
            }
            // ensure GET does not consume any @FormParam annotated parameter
            for (Parameter p : invocable.getParameters()) {
                if (p.isAnnotationPresent(FormParam.class)) {
                    Errors.fatal(method, LocalizationMessages.GET_CONSUMES_FORM_PARAM(invocable.getHandlingMethod()));
                    break;
                }
            }

            if (isSse && void.class != invocable.getHandlingMethod().getReturnType()) {
                Errors.fatal(method, LocalizationMessages.EVENT_SINK_RETURNS_TYPE(invocable.getHandlingMethod()));
            }
        }

        // ensure there is not multiple HTTP method designators specified on the method
        List httpMethodAnnotations = new LinkedList<>();
        for (Annotation a : invocable.getHandlingMethod().getDeclaredAnnotations()) {
            if (null != a.annotationType().getAnnotation(HttpMethod.class)) {
                httpMethodAnnotations.add(a.toString());
            }
        }

        if (httpMethodAnnotations.size() > 1) {
            Errors.fatal(method, LocalizationMessages.MULTIPLE_HTTP_METHOD_DESIGNATORS(invocable.getHandlingMethod(),
                    httpMethodAnnotations.toString()));
        }

        final Type responseType = invocable.getResponseType();
        if (!isConcreteType(responseType)) {
            Errors.warning(invocable.getHandlingMethod(),
                    LocalizationMessages.TYPE_OF_METHOD_NOT_RESOLVABLE_TO_CONCRETE_TYPE(responseType,
                            invocable.getHandlingMethod().toGenericString()));
        }

        final Path pathAnnotation = invocable.getHandlingMethod().getAnnotation(Path.class);
        if (pathAnnotation != null) {
            final String path = pathAnnotation.value();
            if (path == null || path.isEmpty() || "/".equals(path)) {

                Errors.warning(invocable.getHandlingMethod(),
                        LocalizationMessages.METHOD_EMPTY_PATH_ANNOTATION(
                                invocable.getHandlingMethod().getName(), invocable.getHandler().getHandlerClass().getName()));

            }
        }

        // Prevent PARAM_ANNOTATION_SET annotations on a resource method
        if (httpMethodAnnotations.size() != 0) {
            checkUnexpectedAnnotations(method);
        }
    }

    private void checkUnexpectedAnnotations(ResourceMethod resourceMethod) {
        Invocable invocable = resourceMethod.getInvocable();
        for (Annotation annotation : invocable.getHandlingMethod().getDeclaredAnnotations()) {
            if (PARAM_ANNOTATION_SET.contains(annotation.annotationType())) {
                Errors.fatal(resourceMethod, LocalizationMessages.METHOD_UNEXPECTED_ANNOTATION(
                        invocable.getHandlingMethod().getName(),
                        invocable.getHandler().getHandlerClass().getName(),
                        annotation.annotationType().getName())
                );
            }
        }
    }

    private void checkValueProviders(ResourceMethod method) {
        List> valueProviders =
                ParameterValueHelper.createValueProviders(valueParamProviders, method.getInvocable());
        if (valueProviders.contains(null)) {
            int index = valueProviders.indexOf(null);
            Errors.fatal(method, LocalizationMessages.ERROR_PARAMETER_MISSING_VALUE_PROVIDER(index, method.getInvocable()
                    .getHandlingMethod()));
        }
    }

    private void visitSubResourceLocator(ResourceMethod locator) {
        checkParameters(locator);
        checkValueProviders(locator);

        final Invocable invocable = locator.getInvocable();
        if (void.class == invocable.getRawResponseType()) {
            Errors.fatal(locator, LocalizationMessages.SUBRES_LOC_RETURNS_VOID(invocable.getHandlingMethod()));
        }

        // Prevent PARAM_ANNOTATION_SET annotations on a resource locator
        if (invocable.getHandlingMethod().getAnnotation(Path.class) != null) {
            checkUnexpectedAnnotations(locator);
        }
    }

    private void checkParameters(ResourceMethod method) {
        final Invocable invocable = method.getInvocable();
        final Method handlingMethod = invocable.getHandlingMethod();
        int paramCount = 0;
        int nonAnnotatedParameters = 0;

        for (Parameter p : invocable.getParameters()) {
            validateParameter(p, handlingMethod, handlingMethod.toGenericString(), Integer.toString(++paramCount), false);
            if (method.getType() == ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR
                    && Parameter.Source.ENTITY == p.getSource()) {
                Errors.fatal(method, LocalizationMessages.SUBRES_LOC_HAS_ENTITY_PARAM(invocable.getHandlingMethod()));
            } else if (p.getAnnotations().length == 0) {
                nonAnnotatedParameters++;
                if (nonAnnotatedParameters > 1) {
                    Errors.fatal(method, LocalizationMessages.AMBIGUOUS_NON_ANNOTATED_PARAMETER(invocable.getHandlingMethod(),
                            invocable.getHandlingMethod().getDeclaringClass()));
                }
            }
        }
    }

    private boolean isSseInjected(final Invocable invocable) {
        return invocable.getParameters().stream().anyMatch(parameter -> SseEventSink.class.equals(parameter.getRawType()));
    }

    private static final Set PARAM_ANNOTATION_SET = createParamAnnotationSet();

    private static Set createParamAnnotationSet() {
        Set set = new HashSet<>(6);
        set.add(HeaderParam.class);
        set.add(CookieParam.class);
        set.add(MatrixParam.class);
        set.add(QueryParam.class);
        set.add(PathParam.class);
        set.add(BeanParam.class);
        return Collections.unmodifiableSet(set);
    }

    /**
     * Validate a single parameter instance.
     *
     * @param parameter             parameter to be validated.
     * @param source                parameter source; used for issue reporting.
     * @param reportedSourceName    source name; used for issue reporting.
     * @param reportedParameterName parameter name; used for issue reporting.
     * @param injectionsForbidden   {@code true} if parameters cannot be injected by
     *                              parameter annotations, eg. {@link HeaderParam @HeaderParam}.
     */
    static void validateParameter(final Parameter parameter,
                                  final Object source,
                                  final String reportedSourceName,
                                  final String reportedParameterName,
                                  final boolean injectionsForbidden) {
        Errors.processWithException(new Runnable() {
            @Override
            public void run() {
                int counter = 0;
                final Annotation[] annotations = parameter.getAnnotations();
                for (Annotation a : annotations) {
                    if (PARAM_ANNOTATION_SET.contains(a.annotationType())) {
                        if (injectionsForbidden) {
                            Errors.fatal(source, LocalizationMessages.SINGLETON_INJECTS_PARAMETER(reportedSourceName,
                                    reportedParameterName));
                            break;
                        }
                        counter++;
                        if (counter > 1) {
                            Errors.warning(source, LocalizationMessages.AMBIGUOUS_PARAMETER(reportedSourceName,
                                    reportedParameterName));
                            break;
                        }
                    }
                }

                final Type paramType = parameter.getType();
                if (!isConcreteType(paramType)) {
                    Errors.warning(source, LocalizationMessages.PARAMETER_UNRESOLVABLE(reportedParameterName, paramType,
                            reportedSourceName));
                }
            }
        });
    }

    @SuppressWarnings("ChainOfInstanceofChecks")
    private static boolean isConcreteType(Type t) {
        if (t instanceof ParameterizedType) {
            return isConcreteParameterizedType((ParameterizedType) t);
        } else if (!(t instanceof Class)) {
            // GenericArrayType, WildcardType, TypeVariable
            return false;
        }

        return true;
    }

    private static boolean isConcreteParameterizedType(ParameterizedType pt) {
        boolean isConcrete = true;
        for (Type t : pt.getActualTypeArguments()) {
            isConcrete &= isConcreteType(t);
        }

        return isConcrete;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy