2010-04-15 6 views
5

Ho creato un servizio JAX-RS (MyService) che ha un numero di sotto risorse, ognuna delle quali è un sottoclasse di MySubResource. La classe di risorsa sub essendo scelto viene scelto in base ai parametri indicati nel percorso MyService, ad esempio:Come creare un servizio JAX-RS dove la risorsa secondaria @Path non ha una barra iniziale

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

dove MyPlacesSubResource e MyPeopleSubResource sono entrambe sottoclassi di MySubResource.

MySubResource è definito come:

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

risultati vengono elaborati da MessageBodyWriters corrispondenti a seconda del tipo MIME della risposta.

Anche se questo funziona si traduce in percorsi come/persone/Bob/html o/persone/Bob/XML in cui ciò che voglio davvero è /people/Bob.html o /people/Bob.xml

Fa qualcuno sapere come realizzare ciò che voglio fare?

risposta

7

Vecchio argomento, ma questo è qualcosa che ho risolto recentemente utilizzando Jersey; forse aiuterà qualcun altro.

Supporti della maglia che specificano il tipo di contenuto accettato come estensione di file nell'URI tramite l'uso di filtri di richiesta. Jersey fornisce un oggetto UriConnegFilter (URI Content Negotiation Filter) estendibile per tradurre estensioni specifiche ai tipi di contenuto. Quindi includi quel filtro come parametro iniziale per l'applicazione Jersey.

Dal momento che tutti i suoni in modo vago, ecco un esempio concreto dal mio progetto:

volevo essere in grado di interpretare ".json" e ".xml" alla fine dell'URL nel senso che il cliente voluto contenuto formattato in JSON o in formato XML, rispettivamente. A tal fine, ho esteso UriConnegFilter in questo modo:

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

Poi, dal momento che sto usando Jersey come Servlet, ho aggiunto al mio MediaTypeFilter web.xml:

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

Con questo in luogo, Jersey traduce l'estensione sull'URI nel tipo di supporto specificato e rimuove l'estensione. Funziona per le risorse root e le risorse secondarie poiché funziona sull'intero URI. Per il tuo particolare esempio, /people/Bob.xml diventerebbe/people/Bob e l'intestazione Accept nella richiesta verrebbe modificata in "application/xml" (sostituendo qualsiasi intestazione esistente Accept).

hth,

-Peter

+0

Grazie, questo è esattamente ciò che volevo. Dovrò ricordarlo per la prossima volta che avrò bisogno di questa funzionalità (supponendo che non ci spostiamo dalla maglia: /) – Matt

1

Si potrebbe scrivere un po 'di routing servlet per risolverlo. Realisticamente, però, dovresti utilizzare i tipi di contenuto per farlo.

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

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

Il provider JAX-RS elaborerà quindi cosa chiamare in base alla richiesta del cliente. Ancora meglio, potresti farci JAXB e RestEASY a fare tutto per te!

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

Vedere http://java.dzone.com/articles/resteasy-spring per un buon esempio con Spring.

+1

Grazie per la risposta, questo è in realtà il modo in cui originariamente messa in atto. Sfortunatamente alcuni browser non inviano un'intestazione Accept compatibile e quindi viene mostrato il xml invece della rappresentazione html. L'utilizzo del routing servlet può funzionare anche se non ne è stato scritto uno prima. Venire a pensarci usando il routing probabilmente romperebbe i metodi UriBuilder.getPath che uso esclusivamente nelle viste html. Indovina meglio che continui a cercare ... – Matt

1

Un modo per risolvere questo problema è che è possibile utilizzare un'acquisizione di espressioni regolari nel proprio @ javax.ws.rs.Path.

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

Poi, nel vostro sub-risorsa:

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

stare attenti con le espressioni regolari. Ho dato un esempio, ma potresti voler stringere l'espressione per assicurarti che nulla sfugga.

Un altro modo per risolvere questo è quello di cambiare il luogo in cui viene catturato {id} e utilizzare l'espressione regolare lì. Invece di avere @Path("id") MySubResource public getPeople(@PathParam("id") String id) cattura l'id, rimuovere la cattura id da getPeople() e cambiare MySubResource in questo modo:

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

Non ci sono compromessi in entrambi i casi a seconda di come sono organizzate le vostre strutture di dati e quando si ha bisogno di conoscere la parametro "id". Non amo particolarmente le espressioni regolari, dato che sono davvero difficili da ottenere, ma in questo caso è una possibilità.

+0

Grazie per la risposta. Speravo di evitare di farlo in questo modo, perché spostare la logica per scegliere i tipi di risposta nella risorsa genitore ha due svantaggi: 1) significa che ho bisogno di copiare la funzionalità ovunque venga restituita una sub risorsa e 2) se uno dei miei la sotto risorsa può restituire un tipo extra (ad esempio application/json) quindi deve essere inserito nella risorsa padre; entrambi questi penso siano astrazioni che perdono. Purtroppo, finora, questa sembra essere l'unica soluzione e dovrò solo mettere in evidenza gli svantaggi. – Matt

1

Questo è un vecchio argomento e sono certo che l'avete capito, ma per chi arriva in questa pagina, Resteasy ha un modo semplice per ottenere ciò di cui avete bisogno. Si chiama negoziazione del contenuto basata su URL.

Vedi i dettagli qui: http://docs.jboss.org/resteasy/docs/2.0.0.GA/userguide/html_single/index.html#media_mappings

In sostanza, è necessario aggiungere un parametro di contesto nel vostro Web.file XML che dirà Resteasy per mappare il suffisso del tuo URL a uno specifico tipo di contenuto:

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

Con questo, l'accesso /people/Bob.xml è lo stesso di Acessing/persone/Bob e specificando una Accetta -Encoding: application/xml.

Problemi correlati