Erforderlich @QueryParam in JAX-RS (und was in Abwesenheit zu tun ist)

74

Ich stelle eine Webdienstkomponente mithilfe der RESTEasy JAX-RS- Implementierung für JBoss Application Server 7 bereit .

Gibt es eine Anmerkung, um erforderliche, obligatorische @QueryParam- Parameter in JAX-RS zu deklarieren ? Und wenn nicht, wie geht man dann standardmäßig mit Situationen um, in denen solche Parameter fehlen?

Meine Webdienst- (Ressourcen-) Methoden geben JSON-stringifizierte Ergebnisse zurück, wenn sie ordnungsgemäß mit allen obligatorischen Argumenten aufgerufen werden. Ich bin mir jedoch nicht sicher, wie ich dem Aufrufer am besten anzeigen kann, dass ein erforderlicher Parameter fehlt.

Marcus Junius Brutus
quelle
4
Sie können eine @DefaultValueAnmerkung hinzufügen und den Parameter auf einen geeigneten Wert setzen, wenn er fehlt. Wenn Sie keinen Standardwert haben können und der Parameter wirklich wichtig ist, sollten Sie den Parameter möglicherweise überprüfen nullund einen 400 Bad requestStatuscode zurückgeben.
toniedzwiedz

Antworten:

71

Gute Frage. Leider (oder vielleicht zum Glück) gibt es in JAX-RS keinen Mechanismus, um Parameter obligatorisch zu machen. Wenn ein Parameter nicht angegeben wird, ist sein Wert NULLund Ihre Ressource sollte entsprechend damit umgehen. Ich würde empfehlen, WebApplicationExceptionIhre Benutzer zu informieren:

@GET
@Path("/some-path")
public String read(@QueryParam("name") String name) {
  if (name == null) {
    throw new WebApplicationException(
      Response.status(Response.Status.BAD_REQUEST)
        .entity("name parameter is mandatory")
        .build()
    );
  }
  // continue with a normal flow
}
yegor256
quelle
14
Die Dokumentation für JAX-RS 1.0 besagt, dass es nicht immer null sein wird. Es ist "eine leere Sammlung für List, Set oder SortedSet, null für andere Objekttypen und der von Java definierte Standard für primitive Typen."
hotshot309
3
Stringist kein primitiver Typ, daher ist es "null für andere Objekttypen"
yegor256
12
Schlagen Sie außerdem vor, nicht HttpURLConnection.HTTP_BAD_REQUEST, sondern javax.ws.rs.core.Response.Status.BAD_REQUEST zu verwenden, um die erwarteten Parameter der Methode einzuhalten.
Cmonkey
7
Anmerkung aus der fernen Zukunft: Es gibt eine, BadRequestExceptiondie ausgelöst werden kann und die mehr oder weniger das tut, was der obige Code tut, aber es Ihnen auch ermöglicht, diese Ausnahme programmgesteuert abzufangen.
Errantlinguist
63

Sie können javax.validationAnmerkungen verwenden, um zu erzwingen, dass die Parameter obligatorisch sind, indem Sie sie mit Anmerkungen versehen @javax.validation.constraints.NotNull. Sehen Sie sich ein Beispiel für Jersey und eines für RESTeasy an .

Ihre Methode würde also einfach werden:

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

Beachten Sie, dass die Ausnahme dann vom JAX-RS-Anbieter in einen Fehlercode übersetzt wird. Es kann normalerweise überschrieben werden, indem Sie Ihre eigene Implementierung von registrieren javax.ws.rs.ext.ExceptionMapper<javax.validation.ValidationException>.

Dies bietet eine zentralisierte Möglichkeit, obligatorische Parameter in Fehlerantworten zu übersetzen, und es ist keine Codeduplizierung erforderlich.

Giovanni Botta
quelle
12
Ein Problem bei diesem Ansatz ist, dass in der Fehlermeldung nicht der Name des fehlenden Parameters angegeben wird, z. B. "arg1 darf nicht null sein". Glücklicherweise hat die Bean Validation-Spezifikation die Schnittstelle javax.validation.ParameterNameProvider eingeführt. Für JAX-RS können wir die Annotationen QueryParam und PathParam verwenden, um die Namen abzurufen (da die Reflektion das Abrufen von Parameternamen nicht zulässt). Ein Beispiel finden Sie hier: stackoverflow.com/q/22496527/998772
pavel_kazlou
Ja, ich habe diesen Schmerz durchgemacht und eine Frage dazu gestellt . Es ist machbar, nur ein bisschen mehr Code zum Schreiben.
Giovanni Botta
1
Ich versuche, etwas Ähnliches zu tun, aber @NotNull erkennt es nicht, selbst wenn ich diesen Abfrageparameter in der URL weglasse. Ich habe einen Thread hier Link
Bruso
1
Für alle, die 2018 lesen, führt die Annotation @NotNull jetzt zu einer javax.validation.ConstraintViolationException und nicht zu einer ValidationException. Sie benötigen daher einen anderen ExceptionMapper, wenn Sie selbst damit umgehen möchten. Siehe die akzeptierte Antwort auf diese Frage stackoverflow.com/questions/18015630/…
Martin Charlesworth
17

Ich stieß auf dasselbe Problem und entschied, dass ich keine millionenfachen Nullprüfungen auf meinem REST-Code haben wollte. Deshalb habe ich mich dazu entschlossen:

  1. Erstellen Sie eine Anmerkung, die bewirkt, dass eine Ausnahme ausgelöst wird, wenn kein erforderlicher Parameter angegeben wird.
  2. Behandeln Sie die ausgelöste Ausnahme genauso wie alle anderen in meinem REST-Code ausgelösten Ausnahmen.

Für 1) habe ich die folgende Anmerkung implementiert:

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.
}

... und das folgende JAX-RS ContainerRequestFilter, um es durchzusetzen:

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());
                }
            }
        }
    }
}

Sie müssen ContainerRequestFilterdas genauso registrieren, wie Sie Ihre anderen @ProviderKlassen bei Ihrer JAX-RS-Bibliothek registrieren würden . Vielleicht erledigt RESTEasy das automatisch für Sie.

Für 2) behandle ich alle Laufzeitausnahmen mit einem generischen JAX-RS 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();
    }
}

Denken Sie nach wie vor daran, die Klasse bei Ihrer JAX-RS-Bibliothek zu registrieren.

Null3
quelle
2
Bietet dies etwas, was @ javax.validation.constraints.NotNull nicht tun würde?
Michael Haefele
2
@MichaelHaefele Der Parametername wird beibehalten, was zum Anzeigen einer aussagekräftigen Fehlermeldung hilfreich ist. Der Parametername geht verloren, wenn Sie die NotNullAnmerkung verwenden, was unglücklich ist. Das war das Problem, das mich dazu brachte, mir eine eigene Anmerkung zu schreiben. Siehe aber auch stackoverflow.com/questions/13968261/… . Die Dinge könnten sich geändert haben, seit ich diesen Code geschrieben habe.
Zero3
Eines der Hauptmerkmale dieser Lösung ist für mich die genaue Kontrolle darüber, was in der Ausnahme angezeigt werden soll oder nicht.
Alexander
2

Der wahrscheinlich einfachste Weg ist, @Nonnullvon javax.annotationzu verwenden, um dies zu erreichen. Es ist super einfach zu bedienen, da Sie es nur vorher hinzufügen müssen, @QueryParamwie unten gezeigt.

Beachten Sie jedoch, dass dies einen IllegalArgumentExceptionFehler auslöst, wenn der Parameter null ist, sodass die Antwort, die Sie zurücksenden, für eine Ausnahme das ist, was Sie tun. Wenn Sie es nicht abfangen, wird es eine sein 500 Server Error, obwohl das richtige, was Sie zurückschicken würden, eine sein würde 400 Bad Request. Sie können es abfangen IllegalArgumentExceptionund verarbeiten, um eine ordnungsgemäße Antwort zurückzugeben.


Beispiel:

import javax.annotation.Nonnull;
...

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

Die an den Anrufer zurückgegebene Standardfehlermeldung sieht folgendermaßen aus:

{"Zeitstempel": 1536152114437, "Status": 500, "Fehler": "Interner Serverfehler", "Ausnahme": "java.lang.IllegalArgumentException", "Nachricht": "Argument für den @ Noull-Parameter 'paramName' von com /example/YourClass.get darf nicht null sein "," path ":" / path / to / your-path "}

Vlad Schnakovszki
quelle