2010-04-15 3 views
5

Ich habe einen JAX-RS-Dienst (MyService) mit einer Reihe von Unterressourcen erstellt, von denen jede eine ist Unterklasse von MySubResource. Der Teilressourcenklasse wird gewählt wird gepflückt auf der Grundlage der in dem Pfad MyService gegebenen Parametern, zum Beispiel:So erstellen Sie einen JAX-RS-Dienst, bei dem die Unterressource @Path keinen führenden Schrägstrich hat

@Path("/") @Provides({"text/html", "text/xml"}) 
public class MyResource { 
    @Path("people/{id}") public MySubResource getPeople(@PathParam("id") String id) { 
    return new MyPeopleSubResource(id); 
    } 
    @Path("places/{id}") public MySubResource getPlaces(@PathParam("id") String id) { 
    return new MyPlacesSubResource(id); 
    } 
} 

wo MyPlacesSubResource und MyPeopleSubResource sind beide Unterklassen von MySubResource.

MySubResource ist definiert als:

public abstract class MySubResource { 
    protected abstract Results getResults(); 

    @GET public Results get() { return getResults(); } 

    @GET @Path("xml") 
    public Response getXml() { 
    return Response.ok(getResults(), MediaType.TEXT_XML_TYPE).build(); 
    } 

    @GET @Path("html") 
    public Response getHtml() { 
    return Response.ok(getResults(), MediaType.TEXT_HTML_TYPE).build(); 
    } 
} 

Ergebnisse durch entsprechenden MessageBodyWriters in Abhängigkeit von dem MIME-Typ der Antwort verarbeitet wird.

Während dies funktioniert es in Pfade führt wie/people/Bob/html oder/people/Bob/xml, wo, was ich will wirklich /people/Bob.html ist oder /people/Bob.xml

Hat jemand wissen, wie ich erreichen kann, was ich tun möchte?

Antwort

7

Altes Thema, aber das habe ich kürzlich mit Jersey gelöst; vielleicht hilft es jemand anderem.

Jersey unterstützt die Angabe des akzeptierten Inhaltstyps als Dateierweiterung im URI mithilfe von Anforderungsfiltern. Jersey liefert ein UriConnegFilter (URI Content Negotiation Filter) -Objekt, das Sie erweitern können, um bestimmte Erweiterungen auf Inhaltstypen zu übersetzen. Sie fügen diesen Filter dann als Anfangsparameter in die Jersey-Anwendung ein.

Da dies alles so vage klingt, ist hier ein konkretes Beispiel aus meinem Projekt:

Ich wollte in der Lage sein zu interpretieren „.json“ und „.xml“ am Ende der URL dahin, dass der Client wollte JSON-formatierten bzw. XML-formatierten Inhalt. Zu diesem Zweck erweiterte ich UriConnegFilter wie so:

package my.filter.package; 

import com.sun.jersey.api.container.filter.UriConnegFilter; 

import java.util.HashMap; 
import java.util.Map; 

import javax.ws.rs.core.MediaType; 


public class MediaTypeFilter extends UriConnegFilter { 
    private static final Map<String, MediaType> mappedMediaTypes = new HashMap<String, MediaType>(2); 

    static { 
    mappedMediaTypes.put("json", MediaType.APPLICATION_JSON_TYPE); 
    mappedMediaTypes.put("xml", MediaType.APPLICATION_XML_TYPE); 
    } 

    public MediaTypeFilter() { 
    super(mappedMediaTypes); 
    } 
} 

Dann, da ich Jersey verwende als Servlets, ich MediaTypeFilter mein web.xml hinzugefügt:

<servlet> 
    <servlet-name>My Jersey Servlet</servlet-name> 
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 
    <init-param> 
    <param-name>com.sun.jersey.config.property.packages</param-name> 
    <param-value>my.resource.package</param-value> 
    </init-param> 
    <init-param> 
    <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name> 
    <param-value>com.sun.jersey.api.container.filter.LoggingFilter; 
       my.filter.package.MediaTypeFilter; 
       com.sun.jersey.api.container.filter.PostReplaceFilter; 
       com.sun.jersey.api.container.filter.GZIPContentEncodingFilter</param-value> 
    </init-param> 
    <init-param> 
    <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name> 
    <param-value>com.sun.jersey.api.container.filter.GZIPContentEncodingFilter; 
       com.sun.jersey.api.container.filter.LoggingFilter</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
</servlet> 

Mit dem im Ort, Jersey übersetzt die Erweiterung des URI in den angegebenen Medientyp und entfernt die Erweiterung. Dies funktioniert für Root-Ressourcen und Sub-Ressourcen, da es auf den gesamten URI angewendet wird. Für Ihr spezielles Beispiel würde /people/Bob.xml zu/people/Bob und der Header Accept in der Anfrage würde in "application/xml" geändert werden (überschreiben aller vorhandenen Header Accept).

hth,

-Peter

+0

Danke, das ist ziemlich genau das, was ich wollte. Ich muss mich nur daran erinnern, wenn ich diese Funktion das nächste Mal brauche (vorausgesetzt, wir ziehen nicht aus dem Trikot aus: /) – Matt

1

Sie könnten möglicherweise etwas Servlet Routing schreiben, um es auszuarbeiten. Realistischerweise sollten Sie dafür Content-Typen verwenden.

@GET @Path("/") @Produces(MediaType.APPLICATION_XML) 
public Response getXml() { ... } 

@GET @Path("/") @Produces(MediaType.APPLICATION_HTML) 
public Response getHtml() { ... } 

Der JAX-RS-Anbieter wird dann anhand der Anfrage des Clients herausfinden, was anzurufen ist. Noch besser, du könntest uns JAXB und RestEASY, um alles für dich zu tun!

@GET 
@Produces(MediaType.APPLICATION_XML) 
@Path("/{id}") 
public MyObject getXml(@PathParam("typeDocument") String id) { 
myObjectService.get(id); 
} 


@XmlRootElement(name="myObject") 
public class MyObject { 
// Some properties 
} 

Siehe http://java.dzone.com/articles/resteasy-spring für ein gutes Beispiel mit Spring.

+1

Vielen Dank für die Antwort, dann ist dies tatsächlich so, wie ich es ursprünglich umgesetzt. Leider senden einige Browser keinen kompatiblen Accept-Header, und so wird anstelle der HTML-Darstellung die XML-Datei angezeigt. Die Verwendung von Servlet-Routing funktioniert möglicherweise, obwohl noch keine davon geschrieben wurde. Wenn ich daran denke, dass das Routing die UriBuilder.getPath-Methoden, die ich ausschließlich in den HTML-Ansichten verwende, zerstören würde. Raten Sie, ich sollte besser weiter suchen ... – Matt

1

Eine Möglichkeit, dies zu lösen, ist, dass Sie wahrscheinlich eine Aufnahme für reguläre Ausdrücke in Ihrem @ javax.ws.rs.Path verwenden können.

@Path("people/{id:[^/]+?}{format:(\\.[^/]*?)?}") 
@GET 
public MySubResource getPeople(@PathParam("id") String id, @PathParam("format") String format) { 
    // remove the "." from the start of "format" if it is not null 
    return new MySubResource(id, format); 
} 

Dann in Ihrer Unterressource:

public abstract class MySubResource { 
    final protected String format; 

    protected MySubResource(String id, String format) { 
     this.format = format; 
    } 

    protected abstract Results getResults(); 

    @GET 
    public Response get() { 
     return Response.ok(getResults(), this.format).build(); 
    } 
} 

Seien Sie vorsichtig mit dem regulären Ausdrücken. Ich gab ein Beispiel, aber Sie möchten vielleicht den Ausdruck straffen, um sicherzustellen, dass nichts durchrutscht.

Eine andere Möglichkeit, dies zu lösen, besteht darin, zu ändern, wo Ihre {id} erfasst wird, und dort den regulären Ausdruck zu verwenden. Stattdessen @Path("id") MySubResource public getPeople(@PathParam("id") String id) capture die ID zu haben, entfernen Sie die ID-Erfassung aus getPeople() und MySubResource ändern wie folgt:

@Path("people") 
public MySubResource getPeople() { 
    return new MyPeopleSubResource(); 
} 

public abstract class MySubResource { 
    protected abstract Results getResults(); 

    @GET 
    @Path("{id}") 
    public Results get() { return getResults(); } 

    @GET 
    @Path("{id}.xml") 
    public Response getXml() { 
    return Response.ok(getResults(), MediaType.TEXT_XML_TYPE).build(); 
    } 

    @GET 
    @Path("{id}.html") 
    public Response getHtml() { 
    return Response.ok(getResults(), MediaType.TEXT_HTML_TYPE).build(); 
    } 
} 

Es gibt Vor- und Nachteile in jedem Fall je nachdem, wie Sie Ihre Datenstrukturen organisiert sind und wenn Sie wissen müssen, um die "ID" -Parameter. Reguläre Ausdrücke mag ich nicht besonders gern, da sie wirklich schwer zu bekommen sind, aber in diesem Fall ist das eine Möglichkeit.

+0

Danke für die Antwort. Ich hatte gehofft, es so zu vermeiden, weil das Verschieben der Logik zum Auswählen von Antworttypen in die Elternressource zwei Nachteile hat: 1) es bedeutet, dass ich die Funktionalität überall dorthin kopieren muss, wo eine Unterressource zurückgegeben wird und 2) wenn einer meiner Unterressource kann einen zusätzlichen Typ zurückgeben (zB Anwendung/json), dann muss dies in die übergeordnete Ressource geschoben werden; beides denke ich sind undichte Abstraktionen. Leider scheint dies bisher die einzige Lösung zu sein und ich muss nur die Nachteile ausgleichen. – Matt

1

Dies ist ein altes Thema und ich bin mir sicher, dass Sie das herausgefunden haben, aber für diejenigen, die auf diese Seite gelangen, hat Resteasy einen einfachen Weg, um das zu erreichen, was Sie brauchen. Es heißt URL-basierte Inhaltsverhandlung.

Details sehen hier: http://docs.jboss.org/resteasy/docs/2.0.0.GA/userguide/html_single/index.html#media_mappings

Grundsätzlich müssen Sie einen Kontextparameter in Ihrem Web hinzuzufügen.xml-Datei, die Resteasy wird sagen, das Suffix Ihrer URL zu einem bestimmten Inhaltstyp zuzuordnen:

<context-param> 
    <param-name>resteasy.media.type.mappings</param-name> 
    <param-value>html : text/html, json : application/json, xml : application/xml</param-value> 
</context-param> 

Damit /people/Bob.xml Zugriff ist die gleiche wie Acessing/people/Bob und Angabe eines Accept -Encoding: Anwendung/XML.

Verwandte Themen