users@jersey.java.net

Re: [Jersey] Resource and XML Schema versioning...

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Fri, 27 Feb 2009 18:35:34 +0100

Hi,

You can do the following:

        public Map<String, MediaType> getMediaTypeMappings() {
            Map<String, MediaType> m = new HashMap<String, MediaType>();
            m.put("v1-xml", CnodbMediaType.APPLICATION_V1XML_TYPE);
            m.put("v1-json", CnodbMediaType.APPLICATION_V1JSON_TYPE);
            m.put("xml", CnodbMediaType.APPLICATION_V1XML_TYPE);
            m.put("json", CnodbMediaType.APPLICATION_V1JSON_TYPE);
            m.put("v2-xml", CnodbMediaType.APPLICATION_V2XML_TYPE);
            m.put("v2-json", CnodbMediaType.APPLICATION_V2JSON_TYPE);
            return m;
        }

to support a suffix of ".xml" to the v1 media type.

If you want the following to always return the latest version of the
representations then you will break backwards compatibility:

   http://host/myrest/nodes/NODEKEYVALUE.xml

which is bad, as A version 1 client that retains those URLs will be
broken when it accesses them after you upgrade the server to v2.

You should mandate that clients that utilize the so called platonic
URI, with no suffix, should use an appropriate Accept header.


Alternatively the version-based URI approach for the application is
perhaps much easier to manage.

I do not think you should support a non-versioned URI to the default
versioned URI, again for reasons of supporting backwards
compatibility, because you want your default to change.

Instead the non-versioned URI should return a 300 (Multiple Choices)
response enumerating the versions and links, with the most preferable
defined in the Location header. Such 300 support is very easy to
implement. Even if you only have one version it should support this
behaviour and thus such behaviour is consistent as you add versions.

Paul.

On Feb 27, 2009, at 5:44 PM, Rabick, Mark A (MS) wrote:

> The custom content-type and suffix type negotiation is pretty cool,
> once
> I got the wrinkles out. Now I'm wondering... I want to support both
> schema-type and content type negotiation. That is, I have multiple
> XML
> schema versions (jaxb annotations on 2 different packages representing
> the same set of entities. Behind the scenes, I map my JPA entity
> beans
> to objects in either schema. It seems like I can let a user specify a
> schema version in a suffix mapped to a custom media type as below if
> the
> client doesn't have the ability to modify an Accept header. I would
> also like to let a user retrieve either JSON or XML formatted data of
> the multiple schemas .
>
> Another request I have is to have a 'constant' URI that will always
> map
> to the most recent version of a schema for a given entity which
> seems to
> be working:
>
> For example. The Node entity bean has 2 XML schemas NodeV1.java and
> NodeV2.java.
>
> The URI:
>
> http://host/myrest/nodes/NODEKEYVALUE.v1-xml returns a V1 schema
> instance
> http://host/myrest/nodes/NODEKEYVALUE also returns a V1 schema
> instance
>
> The same is true for JSON.
>
> If I add the V2 schema to the equation, I'd like:
>
> http://host/myrest/nodes/NODEKEYVALUE.v1-xml to still return a V1
> schema
> instance http://host/myrest/nodes/NODEKEYVALUE.v2-xml to return a V2
> schema instance
> http://host/myrest/nodes/NODEKEYVALUE also to return a V2 schema
> instance
>
> All are implemented in the same resource class with different
> @Produces(mediatypes). Would it be simpler to use a URI fragment to
> specify the schema version and map .xml and .json to the
> MediaType.APPLICATION_XML and _JSON respectively?
>
> http://host/myrest/v1/nodes/NODEKEYVALUE.xml
> http://host/myrest/v1/nodes/NODEKEYVALUE.json
> http://host/myrest/v2/nodes/NODEKEYVALUE.xml
> http://host/myrest/v2/nodes/NODEKEYVALUE.json
>
> How could I get
>
> http://host/myrest/nodes/NODEKEYVALUE.xml
> http://host/myrest/nodes/NODEKEYVALUE.json
>
> To map to the V2 URI's?
>
> The context-root of the application is myrest.
>
> Could I use sub-resources/locators?
>
> @Path("/")
> Public class NodesResource {
>
> @Path("/v1/nodes")
> public NodesV1Resource getNodesV1Resource() {
> return new NodesV1Resourse();
> }
> @Path("/v2/nodes")
> public NodesV2Resource getNodesV2Resource() {
> return new NodesV2Resourse();
> }
> @Path("/nodes")
> public NodesV2Resource getNodesV2Resource() {
> return new NodesV2Resourse();
> }
> }
>
> Public class NodesV1Resource {
>
> @GET @Path("{key}")
> @Produces({"application/xml","application/json"})
> public NodeV1 getNodeV1ByKey(@PathParam("key") String key) {
> return convertToNodeV1( NodeDatabase.getNodeByKey(key)
> );
> }
> }
>
> Public class NodesV2Resource {
>
> @GET @Path("{key}")
> @Produces({"application/xml","application/json"})
> public NodeV2 getNodeV2ByKey(@PathParam("key") String key) {
> return convertToNodeV2( NodeDatabase.getNodeByKey(key)
> );
> }
> }
>
> --or--
>
> @Path("/v1/nodes")
> Public class NodesV1Resource {
>
> @GET @Path("{key}")
> @Produces({"application/xml","application/json"})
> public NodeV1 getNodeV1ByKey(@PathParam("key") String key) {
> return convertToNodeV1( NodeDatabase.getNodeByKey(key)
> );
> }
> }
>
> @Path("/v2/nodes")
> Public class NodesV2Resource {
>
> @GET @Path("{key}")
> @Produces({"application/xml","application/json"})
> public NodeV2 getNodeV2ByKey(@PathParam("key") String key) {
> return convertToNodeV2( NodeDatabase.getNodeByKey(key)
> );
> }
> }
>
> I don't see a way to have @Path("v2/nodes") and @Path("/nodes") both
> match to the NodesV2Resource. I think the subresource mechanism is
> the
> way to go because of its reuse ability.....
>
> -m
>
>> -----Original Message-----
>> From: Paul.Sandoz_at_Sun.COM [mailto:Paul.Sandoz_at_Sun.COM]
>> Sent: Thursday, February 26, 2009 2:50 PM
>> To: users_at_jersey.dev.java.net
>> Subject: Re: [Jersey] Resource and XML Schema versioning...
>>
>> Hi Mark,
>>
>> You have not configured your web.xml correctly to use your own
>> extension of PackagesResourceConfig.
>>
>> I do not know what the package name of your class
>> CnodbRestResourceConfig is, but for the sake of example and clarity i
>> am going to assume it is "mil.cnodb.rs". The servlet declaration
>> requires just the following init-param:
>>
>> <init-param>
>> <param-
>> name>com.sun.jersey.config.property.resourceConfigClass</param-name>
>>
>> <param-value>mil.cnodb.rs.CnodbRestResourceConfig</param-value>
>> </init-param>
>>
>> Plus in your CnodbRestResourceConfig you do not require all the WADL
>> configuration stuff as copied from the camel web example, i dunno if
>> you are using that or not. You just require the following:
>>
>> public class CnodbRestResourceConfig extends
>> PackagesResourceConfig {
>>
>> public CnodbRestResourceConfig() {
>> super("mil.cnodb.rs.resources.v1");
>> }
>>
>> public Map<String, MediaType> getMediaTypeMappings() {
>> Map<String, MediaType> m = new HashMap<String,
>> MediaType>();
>> m.put("v1-xml", CnodbMediaType.APPLICATION_V1XML_TYPE);
>> m.put("v1-json", CnodbMediaType.APPLICATION_V1JSON_TYPE);
>> return m;
>> }
>> }
>>
>> Paul.
>>
>> On Feb 26, 2009, at 9:27 PM, Rabick, Mark A (MS) wrote:
>>
>>> Thanks Marc. I just changed the media type to
>> application/v1+xml and
>>> if I enter the URL:
>>>
>>> http://host/nodes1
>>>
>>> IE asks me to open a file 'nodes1' and pick an 'open-with'
>> program for
>>> file of type 'application/v1+xml'... So far so good, but if
>> I change
>>> the URI and add the suffix mapped in the PackagesResourceConfig
>>> ("v1-xml", "v1-json"),
>>>
>>> http://host/nodes1/nodes1.v1-xml
>>>
>>> I get back an empty page??? Same with .v1-json
>>>
>>> I'm trying to use the suffix to specify both a schema version and
>>> media type to return. Is that trying too much?
>>>
>>> -m
>>>
>>>
>>>> -----Original Message-----
>>>> From: Marc.Hadley_at_Sun.COM [mailto:Marc.Hadley_at_Sun.COM]
>>>> Sent: Thursday, February 26, 2009 2:19 PM
>>>> To: users_at_jersey.dev.java.net
>>>> Subject: Re: [Jersey] Resource and XML Schema versioning...
>>>>
>>>> There are a couple of things you could try:
>>>>
>>>> (i) Simplest. Change your media type to application/v1+xml.
>>>> JAX-RS requires the JAXB message body writer to support
>>>> application/xml, text/ xml and application/*+xml but its
>> allowed to
>>>> ignore other media types.
>>>>
>>>> (ii) More complex. Write your own message body writer that
>> defers to
>>>> the built-in JAXB one. You'll need to implement
>>>> MessageBodyWriter<NodeV1>, have javax.ws.rs.ext.Providers injected
>>>> and use the getMessageBodyWriter method to get yourself a
>> reference
>>>> to the built-in JAXB writer that you can then call.
>>>>
>>>> Marc.
>>>>
>>>> On Feb 26, 2009, at 2:58 PM, Rabick, Mark A (MS) wrote:
>>>>
>>>>> I'm getting an exception trying to configure a custom media type:
>>>>>
>>>>> SEVERE: A message body writer for Java type, class
>>>>> mil.cnodb.rs.model.v1.NodeV1, and MIME media type,
>>>> application/v1-xml,
>>>>> was not found Feb 26, 2009 1:50:35 PM
>>>>> com.sun.jersey.server.impl.application.WebApplicationImpl
>>>> onException
>>>>> SEVERE: Internal server error
>>>>> javax.ws.rs.WebApplicationException
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.j
>>>>> ava:241)
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.server.impl.application.WebApplicationImpl._handleRequest
>>>>> (WebApplicationImpl.java:578)
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(
>>>>> WebApplicationImpl.java:502)
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(
>>>>> WebApplicationImpl.java:493)
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.j
>>>>> ava:308)
>>>>> at
>>>>> com
>>>>>
>>>>
>> .sun.jersey.spi.container.servlet.ServletContainer.service(ServletCon
>>>>> tainer.java:314)
>>>>>
>>>>> How can I map application/v1-xml to application/xml?
>>>> Similarly with
>>>>> application/v1-json. Is there a way to avoid writing a custom
>>>>> messageWriter for the media type?
>>>>>
>>>>> I've configured a context resolver:
>>>>>
>>>>> /**
>>>>> *
>>>>> */
>>>>> @Provider
>>>>> @Produces({"application/v1-xml","application/v1-json"})
>>>>> public final class Version1ContextResolver implements
>>>>> ContextResolver<JAXBContext> {
>>>>>
>>>>> private final JAXBContext context;
>>>>>
>>>>> private final Set<Class> types;
>>>>>
>>>>> /*
>>>>> * The list of JAXB class types (top-level) need to be
>>>> listed here.
>>>>> */
>>>>> private final Class[] cTypes = {NodeV1.class};
>>>>>
>>>>> public Version1ContextResolver() throws Exception {
>>>>>
>>>>> this.types = new HashSet(Arrays.asList(cTypes));
>>>>> this.context = new JSONJAXBContext(
>>>>> JSONConfiguration.natural().build(), cTypes);
>>>>> }
>>>>>
>>>>> public JAXBContext getContext(Class<?> objectType) {
>>>>> return (types.contains(objectType)) ? context : null;
>>>>> }
>>>>> }
>>>>>
>>>>> Created a Resource that
>>>>> Produces("application/v1-xml","application/v1-json")
>>>>>
>>>>> @GET
>>>>> @Produces({"application/v1-xml","application/v1-json"})
>>>>> public NodeV1 getNode(
>>>>> @QueryParam("id") String nodeId,
>>>>> @QueryParam("name") String nodeName,
>>>>> @QueryParam("level") String nodeLevel,
>>>>> @QueryParam("allegiance") String allegiance,
>>>>> @QueryParam("cc") String cc,
>>>>> @QueryParam("typeGeneral") String
>> nodeTypeGeneral,
>>>>> @QueryParam("typeSpecific") String
>>>>> nodeTypeSpecific) {
>>>>>
>>>>>
>>>>> NodeV1 nodeV1 = new NodeV1();
>>>>> nodeV1.setNodeName("Nodes Name is");
>>>>>
>>>>> return nodeV1;
>>>>> }
>>>>>
>>>>> Extended PackagesResourceConfig:
>>>>>
>>>>> public class CnodbRestResourceConfig extends
>>>> PackagesResourceConfig {
>>>>>
>>>>> public CnodbRestResourceConfig() {
>>>>> super(createProperties());
>>>>> }
>>>>>
>>>>> public Map<String, MediaType> getMediaTypeMappings() {
>>>>> Map<String, MediaType> m = new HashMap<String, MediaType>();
>>>>> m.put("v1-xml", CnodbMediaType.APPLICATION_V1XML_TYPE);
>>>>> m.put("v1-json", CnodbMediaType.APPLICATION_V1JSON_TYPE);
>>>>> return m;
>>>>> }
>>>>>
>>>>> protected static Map<String,Object> createProperties() {
>>>>> Map<String, Object> properties = new HashMap<String,
>>>>> Object>();
>>>>>
>>>>> properties.put(PackagesResourceConfig.PROPERTY_PACKAGES,
>>>>> "mil.cnodb.rs.resources.v1");
>>>>>
>>>>> WadlGeneratorConfig config = WadlGeneratorConfig
>>>>> .generator(WadlGeneratorApplicationDoc.class)
>>>>> .prop("applicationDocsFile",
>>>>> "classpath:/application-doc.xml")
>>>>> .generator(WadlGeneratorGrammarsSupport.class)
>>>>> .prop("grammarsFile",
>>>>> "classpath:/application-grammars.xml")
>>>>> .generator(WadlGeneratorResourceDocSupport.class)
>>>>> .prop("resourceDocFile",
>>>> "classpath:/resourcedoc.xml")
>>>>> .build();
>>>>>
>>>>>
>> properties.put(ResourceConfig.PROPERTY_WADL_GENERATOR_CONFIG,
>>>>> config);
>>>>> return properties;
>>>>> }
>>>>>
>>>>> }
>>>>>
>>>>> And edited web.xml
>>>>>
>>>>> <servlet>
>>>>> <servlet-name>CNODB Web Application</servlet-name>
>>>>>
>>>>> <servlet-
>>>>> class>com.sun.jersey.spi.container.servlet.ServletContainer</se
>>>>> rvlet-class>
>>>>> <init-param>
>>>>>
>>>>> <param-name>com.sun.jersey.config.property.resourceConfigClass</
>>>>> param-na
>>>>> me>
>>>>>
>>>>>
>> <param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-
>>>>> value
>>>>>>
>>>>> </init-param>
>>>>> <init-param>
>>>>>
>>>>> <param-name>com.sun.jersey.config.property.packages</param-name>
>>>>> <param-value>mil.cnodb.rs</param-value>
>>>>> </init-param>
>>>>> <load-on-startup>1</load-on-startup>
>>>>> </servlet>
>>>>>
>>>>>> -----Original Message-----
>>>>>> From: Paul.Sandoz_at_Sun.COM [mailto:Paul.Sandoz_at_Sun.COM]
>>>>>> Sent: Thursday, February 26, 2009 11:12 AM
>>>>>> To: users_at_jersey.dev.java.net
>>>>>> Subject: Re: [Jersey] Resource and XML Schema versioning...
>>>>>>
>>>>>>
>>>>>> On Feb 26, 2009, at 5:51 PM, Rabick, Mark A (MS) wrote:
>>>>>>
>>>>>>>> On Feb 25, 2009, at 6:08 PM, Rabick, Mark A (MS) wrote:
>>>>>
>>>>>>>> A suffix at the end of the URI path, but not part of the
>>>>>> application
>>>>>>>> and @Path.
>>>>>>>
>>>>>>> Not part of the application I assume would be not in the
>>>> 'context-
>>>>>>> root'
>>>>>>> of the web app. Sorry to belabor this but how can a suffix
>>>>>> be at the
>>>>>>> end of the URI path, but NOT Included in an @Path?
>>>>>>> Can you give an example?
>>>>>>
>>>>>> Here is a specific example from the camel web component:
>>>>>>
>>>>>> https://svn.apache.org/repos/asf/camel/trunk/components/camel-
>>>>>>
>>>>
>> web/src/main/java/org/apache/camel/web/util/CamelResourceConfig.java
>>>>>>
>>>>>> public class CamelResourceConfig extends PackagesResourceConfig {
>>>>>> public CamelResourceConfig() {
>>>>>> super(createProperties());
>>>>>> }
>>>>>>
>>>>>> protected static Map<String, Object> createProperties() {
>>>>>> Map<String, Object> properties = new HashMap<String,
>>>>>> Object>();
>>>>>>
>>>>>> properties.put(PackagesResourceConfig.PROPERTY_PACKAGES,
>>>>>> "org.apache.camel.web");
>>>>>>
>>>>>> WadlGeneratorConfig config = WadlGeneratorConfig
>>>>>> .generator(WadlGeneratorApplicationDoc.class)
>>>>>> .prop("applicationDocsFile",
>>>> "classpath:/application-
>>>>>> doc.xml")
>>>>>> .generator(WadlGeneratorGrammarsSupport.class)
>>>>>> .prop("grammarsFile", "classpath:/application-
>>>>>> grammars.xml")
>>>>>> .generator(WadlGeneratorResourceDocSupport.class)
>>>>>> .prop("resourceDocFile",
>>>>>> "classpath:/resourcedoc.xml")
>>>>>> .build();
>>>>>>
>>>>>>
>>>> properties.put(ResourceConfig.PROPERTY_WADL_GENERATOR_CONFIG,
>>>>>> config);
>>>>>> return properties;
>>>>>> }
>>>>>>
>>>>>> public Map<String, MediaType> getMediaTypeMappings() {
>>>>>> Map<String, MediaType> m = new HashMap<String,
>>>> MediaType>();
>>>>>> m.put("html", MediaType.TEXT_HTML_TYPE);
>>>>>> m.put("xml", MediaType.APPLICATION_XML_TYPE);
>>>>>> m.put("json", MediaType.APPLICATION_JSON_TYPE);
>>>>>> m.put("dot", MediaType.valueOf(Constants.DOT_MIMETYPE));
>>>>>> return m;
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> See the method "getMediaTypeMappings". Notice that it declares a
>>>>>> mapping of suffix to media type.
>>>>>>
>>>>>> Thus whenever there is say a request URI:
>>>>>>
>>>>>> http://localhost:8080/path/foo.html
>>>>>>
>>>>>> Jersey will modify the request URI to be:
>>>>>>
>>>>>> http://localhost:8080/path/foo
>>>>>>
>>>>>> and modify the Accept header to be:
>>>>>>
>>>>>> Accept: text/html
>>>>>>
>>>>>> The above modifications will occur *before* Jersey processes the
>>>>>> request and dispatches to a methods on a resource class.
>>>>>> It is essentially a request filter.
>>>>>>
>>>>>> Hope that helps,
>>>>>> Paul.
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>>> Ie. http://host:8080/fooapplication/foo.foo-v1/
>>>>>>>>>
>>>>>>>>> public class FooV1Resource {
>>>>>>>>>
>>>>>>>>> @GET @Path("/foo.foo-v1/{id}")
>>>>>>>>> @Produces("application/foo-v1") public FooV1(String
>>>>>>>>> @PathParam("id") String fooId) {
>>>>>>>>>
>>>>>>>>> FooV2 foo2 = FooDatabase.getFooById(fooId);
>>>>>>>>> FooV1 foo1 = convertFoo2ToFoo1(foo2)
>>>>>>>>> return foo1;
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> Ie. http://host:8080/fooapplication/foo.foo-v2/
>>>>>>>>>
>>>>>>>>> public class FooV2Resource {
>>>>>>>>>
>>>>>>>>> @GET @Path("/foo.foo-v2/{id}")
>>>>>>>>> @Produces("application/foo-v2") public FooV2(String
>>>>>>>>> @PathParam("id") String fooId) {
>>>>>>>>> FooV2 foo2 = FooDatabase.getFooById(fooId);
>>>>>>>>> return foo2;
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> With the appropriate ContextResolvers based on the media
>>>>>> types, of
>>>>>>>>> course...
>>>>>>>>>
>>>>>>>>> What 'existing Jersey functionality' are you
>> referring to that
>>>>>>>>> suffixes would help with?
>>>>>>>>>
>>>>>>>>
>>>>>>>> See:
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>> https://jersey.dev.java.net/source/browse/*checkout*/jersey/tags/
>>>>>> jerse
>>>>>>> y- 1.0.2/api/jersey/com/sun/jersey/api/core/
>>>>>>> ResourceConfig.html#getMediaTyp
>>>>>>> eMappings()
>>>>>>>>
>>>>>>>> Paul.
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>
>> ---------------------------------------------------------------------
>>>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>
>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>
>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>>
>>>>
>>>>
>>>>
>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>
>>>>
>>>>
>>>
>>>
>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>
>>
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>