web-dev-qa-db-fra.com

Obligatoire @QueryParam dans JAX-RS (et quoi faire en leur absence)

Je déploie un composant de services Web sur JBoss Application Server 7 à l'aide de l'implémentation RESTEasy JAX-RS.

Existe-t-il une annotation disponible à déclarer obligatoire, obligatoire @ QueryParam paramètres in JAX-RS? Et, si non, quelle est la manière "standard" de traiter les situations dans lesquelles de tels paramètres manquent?

Mes méthodes de service Web (ressources) renvoient des résultats sous forme de chaîne JSON lorsqu'elles sont correctement appelées avec tous les arguments obligatoires, mais je ne sais pas quel est le meilleur moyen d'indiquer à l'appelant qu'un paramètre requis était manquant.

64

Bonne question. Malheureusement (ou peut-être heureusement), JAX-RS ne dispose d'aucun mécanisme pour rendre les paramètres obligatoires. Si un paramètre n'est pas fourni, sa valeur sera NULL et votre ressource devrait le gérer en conséquence. Je recommanderais d'utiliser WebApplicationException pour informer vos utilisateurs:

@GET
@Path("/some-path")
public String read(@QueryParam("name") String name) {
  if (name == null) {
    throw new WebApplicationException(
      Response.status(HttpURLConnection.HTTP_BAD_REQUEST)
        .entity("name parameter is mandatory")
        .build()
    );
  }
  // continue with a normal flow
}
66
yegor256

Vous pouvez utiliser javax.validation annotations pour imposer que les paramètres soient obligatoires en les annotant avec @javax.validation.constraints.NotNull. Voir n exemple pour Jersey et n pour RESTeasy .

Donc, votre méthode deviendrait simplement:

@GET
@Path("/some-path")
public String read(@NotNull @QueryParam("name") String name) {
  String something = 
  // implementation
  return something;
}

Notez que l'exception est ensuite traduite par le fournisseur JAX-RS en un code d'erreur. Vous pouvez généralement le remplacer en enregistrant votre propre implémentation de javax.ws.rs.ext.ExceptionMapper<javax.validation.ValidationException>.

Cela fournit un moyen centralisé de traduire le paramètre obligatoire en réponses d'erreur et aucune duplication de code n'est nécessaire.

56
Giovanni Botta

J'ai rencontré le même problème et décidé que je ne voulais pas d'un gazillion boilerplate null checks dispersés sur mon code REST), c'est donc ce que j'ai décidé de faire:

  1. Créez une annotation qui provoque la levée d'une exception lorsqu'un paramètre requis n'est pas spécifié.
  2. Traitez l’exception levée de la même manière que toutes les autres exceptions renvoyées dans mon code REST.

Pour 1), j'ai mis en œuvre l'annotation suivante:

import Java.lang.annotation.Documented;
import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import Java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Required
{
    // This is just a marker annotation, so nothing in here.
}

... et le JAX-RS suivant ContainerRequestFilter pour l'appliquer:

import Java.lang.reflect.Parameter;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

@Provider
public class RequiredParameterFilter implements ContainerRequestFilter
{
    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        // Loop through each parameter
        for (Parameter parameter : resourceInfo.getResourceMethod().getParameters())
        {
            // Check is this parameter is a query parameter
            QueryParam queryAnnotation = parameter.getAnnotation(QueryParam.class);

            // ... and whether it is a required one
            if (queryAnnotation != null && parameter.isAnnotationPresent(Required.class))
            {
                // ... and whether it was not specified
                if (!requestContext.getUriInfo().getQueryParameters().containsKey(queryAnnotation.value()))
                {
                    // We pass the query variable name to the constructor so that the exception can generate a meaningful error message
                    throw new YourCustomRuntimeException(queryAnnotation.value());
                }
            }
        }
    }
}

Vous devez enregistrer le ContainerRequestFilter de la même manière que vous enregistrez votre autre @Provider cours avec votre bibliothèque JAX-RS. Peut-être que RESTEasy le fait pour vous automatiquement.

Pour 2), je gère toutes les exceptions d'exécution à l'aide d'un JAX-RS générique ExceptionMapper:

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MyExceptionMapper implements ExceptionMapper<RuntimeException>
{
    @Override
    public Response toResponse(RuntimeException ex)
    {
        // In this example, we just return the .toString() of the exception. 
        // You might want to wrap this in a JSON structure if this is a JSON API, for example.
        return Response
            .status(Response.Status.BAD_REQUEST)
            .entity(ex.toString())
            .build();
    }
}

Comme auparavant, n'oubliez pas d'enregistrer le cours auprès de votre bibliothèque JAX-RS.

15
Zero3

Le moyen le plus simple consiste probablement à utiliser @Nonnull À partir de javax.annotation Pour y parvenir. C'est super simple à utiliser car tout ce que vous avez à faire est de l'ajouter avant @QueryParam Comme indiqué ci-dessous.

Cependant, gardez à l'esprit que cela jettera un IllegalArgumentException lorsque le paramètre est null, ainsi la réponse que vous renverrez sera celle que vous fassiez pour une exception. Si vous ne l'interceptez pas, il s'agira d'un 500 Server Error Même si la bonne chose à renvoyer serait un 400 Bad Request. Vous pouvez intercepter IllegalArgumentException et le traiter pour renvoyer une réponse appropriée.


Exemple:

import javax.annotation.Nonnull;
...

    @GET
    @Path("/your-path")
    public Response get(@Nonnull @QueryParam("paramName") String paramName) {
        ... 
    }

Le message d'erreur par défaut renvoyé à l'appelant ressemble à ceci:

{"timestamp": 1536152114437, "status": 500, "erreur": "Erreur interne du serveur", "exception": "Java.lang.IllegalArgumentException", "message": "Argument pour le paramètre @Nonnull 'paramName' de com /example/YourClass.get ne doit pas être null "," chemin ":"/chemin/vers/votre-chemin "}

2
Vlad Schnakovszki