Skip to content
This repository was archived by the owner on Jul 7, 2023. It is now read-only.

Commit b7aea86

Browse files
committed
WINK-380 - Add support for structured syntax suffix in media types (e.g. application/vnd.custom+json)
git-svn-id: https://svn.apache.org/repos/asf/wink/trunk@1457479 13f79535-47bb-0310-9956-ffa450edef68
1 parent 159d706 commit b7aea86

File tree

2 files changed

+196
-1
lines changed

2 files changed

+196
-1
lines changed

wink-common/src/main/java/org/apache/wink/common/internal/registry/ProvidersRegistry.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ public Collection<ProviderRecord<T>> getProviderRecords() {
735735
private List<OFHolder<T>> internalGetProvidersByMediaType(MediaType mediaType, Class<?> cls) {
736736
Set<OFHolder<T>> compatible = new TreeSet<OFHolder<T>>(Collections.reverseOrder());
737737
for (Entry<MediaType, HashSet<PriorityObjectFactory<T>>> entry : entrySet) {
738-
if (entry.getKey().isCompatible(mediaType)) {
738+
if (areMediaTypesCompatible(entry.getKey(), mediaType)) {
739739
// media type is compatible, check generic type of the
740740
// subset
741741
for (PriorityObjectFactory<T> of : entry.getValue()) {
@@ -760,6 +760,52 @@ private List<OFHolder<T>> internalGetProvidersByMediaType(MediaType mediaType, C
760760
return Arrays.asList(tmp);
761761
}
762762

763+
/**
764+
* Allow providers to be registered with a structured syntax suffix (e.g. '+xml'
765+
* or '+json') to narrow (or broaden) the applicability of a provider.
766+
* <p>
767+
* The structured syntax suffix (SSS) is defined in RFC6838, section 4.2.8 and
768+
* RFC6839, section 3.<br/>
769+
* For providers, the SSS is used <em>in addition to</em> to the mimetype's main
770+
* type. For example, a mediatype of "application/vnd.custom+json" should be
771+
* considered compatible with a provider registered as either
772+
* "application/vnd.custom+json" or "application/json", but not with "text/json"
773+
* or "application/vnd.custom+xml".
774+
* </p>
775+
* @param providerMediaType the media type of the provider to check for
776+
* compatibility, cannot be <code>null</code>;
777+
* @param candidateMediaType the media type of check against, cannot be
778+
* <code>null</code>.
779+
* @return <code>true</code> if the given media types are compatible,
780+
* <code>false</code> otherwise.
781+
* @see http://tools.ietf.org/html/rfc6838#section-4.2.8
782+
* @see http://tools.ietf.org/html/rfc6839#section-3
783+
*/
784+
private boolean areMediaTypesCompatible(MediaType providerMediaType, MediaType candidateMediaType) {
785+
if (providerMediaType.isCompatible(candidateMediaType)) {
786+
// Easy case: directly compatible media types...
787+
return true;
788+
}
789+
790+
// Are main types equal? If not, we're done really quickly...
791+
if (!providerMediaType.getType().equals(candidateMediaType.getType())) {
792+
return false;
793+
}
794+
795+
// If the provider has a SSS in its subtype, it means that it is *specifically*
796+
// registered for that particular subtype + SSS, so do not strip it from the
797+
// subtype...
798+
String subtype1 = providerMediaType.getSubtype();
799+
String subtype2 = candidateMediaType.getSubtype();
800+
int idx = subtype2.indexOf('+');
801+
if (idx >= 0) {
802+
// subtype contains a structured syntax suffix...
803+
subtype2 = subtype2.substring(idx + 1);
804+
}
805+
806+
return subtype1.equals(subtype2);
807+
}
808+
763809
public Set<MediaType> getProvidersMediaTypes(Class<?> type) {
764810
Set<MediaType> mediaTypes = new LinkedHashSet<MediaType>();
765811

wink-common/src/test/java/org/apache/wink/common/internal/registry/ProvidersRegistryTest.java

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@
2121

2222
import java.io.IOException;
2323
import java.io.InputStream;
24+
import java.io.OutputStream;
2425
import java.lang.annotation.Annotation;
2526
import java.lang.reflect.Field;
2627
import java.lang.reflect.Type;
2728
import java.util.HashMap;
2829
import java.util.Set;
2930

31+
import javax.ws.rs.Consumes;
3032
import javax.ws.rs.Produces;
33+
import javax.ws.rs.WebApplicationException;
3134
import javax.ws.rs.core.MediaType;
3235
import javax.ws.rs.core.MultivaluedMap;
3336
import javax.ws.rs.ext.MessageBodyReader;
37+
import javax.ws.rs.ext.MessageBodyWriter;
3438
import javax.ws.rs.ext.Provider;
3539

3640
import junit.framework.TestCase;
@@ -110,6 +114,86 @@ public void testOverrideSystemProvider() throws Exception {
110114
// make there is only one provider in the list to conform to JAX-RS 4.1 first sentence
111115
assertEquals(1, readers.size());
112116
}
117+
118+
/**
119+
* Tests that a structured syntax suffix is handled correctly for determining a writer.
120+
*
121+
* @throws Exception
122+
*/
123+
public void testProvidesStructuredSyntaxSuffixHandledOk() throws Exception {
124+
ProvidersRegistry providersRegistry =
125+
new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
126+
providersRegistry.addProvider(GenericProvider.class);
127+
providersRegistry.addProvider(SpecificProvider.class);
128+
129+
MediaType mediaType;
130+
MessageBodyWriter<String> writer;
131+
132+
mediaType = MediaType.valueOf("application/json"); // use generic provider
133+
134+
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
135+
assertTrue(writer instanceof GenericProvider);
136+
137+
mediaType = MediaType.valueOf("application/vnd.other+json"); // use generic provider
138+
139+
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
140+
assertTrue(writer instanceof GenericProvider);
141+
142+
mediaType = MediaType.valueOf("application/subschema+json"); // use specific provider
143+
144+
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
145+
assertTrue(writer instanceof SpecificProvider);
146+
147+
mediaType = MediaType.valueOf("application/subschema"); // cannot use specific provider, nor generic
148+
149+
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
150+
assertNull(writer);
151+
152+
mediaType = MediaType.valueOf("text/json"); // cannot use specific provider, nor generic
153+
154+
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
155+
assertNull(writer);
156+
}
157+
158+
/**
159+
* Tests that a structured syntax suffix is handled correctly for determining a reader.
160+
*
161+
* @throws Exception
162+
*/
163+
public void testConsumesStructuredSyntaxSuffixHandledOk() throws Exception {
164+
ProvidersRegistry providersRegistry =
165+
new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
166+
providersRegistry.addProvider(GenericProvider.class);
167+
providersRegistry.addProvider(SpecificProvider.class);
168+
169+
MediaType mediaType;
170+
MessageBodyReader<String> reader;
171+
172+
mediaType = MediaType.valueOf("application/json"); // use generic provider
173+
174+
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
175+
assertTrue(reader instanceof GenericProvider);
176+
177+
mediaType = MediaType.valueOf("application/vnd.other+json"); // use generic provider
178+
179+
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
180+
assertTrue(reader instanceof GenericProvider);
181+
182+
mediaType = MediaType.valueOf("application/subschema+json"); // use specific provider
183+
184+
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
185+
assertTrue(reader instanceof SpecificProvider);
186+
187+
mediaType = MediaType.valueOf("application/subschema"); // cannot use specific provider, nor generic
188+
189+
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
190+
assertNull(reader);
191+
192+
mediaType = MediaType.valueOf("text/json"); // cannot use specific provider, nor generic
193+
194+
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
195+
assertNull(reader);
196+
}
113197

114198
// TODO: perhaps future tests should be added to actually exercise the providersCache code, but it would be an involved,
115199
// multi-threaded test that dynamically adds providers at just the right time to ensure no problems with
@@ -140,4 +224,69 @@ public String readFrom(Class<String> type, Type genericType,
140224
}
141225
}
142226

227+
@Provider
228+
@Produces({"application/subschema+json"})
229+
@Consumes({"application/subschema+json"})
230+
public static class SpecificProvider implements MessageBodyReader<String>, MessageBodyWriter<String> {
231+
public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
232+
{
233+
return String.class.isAssignableFrom(type);
234+
}
235+
236+
public long getSize( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
237+
{
238+
return -1L;
239+
}
240+
241+
public void writeTo( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
242+
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) throws IOException,
243+
WebApplicationException
244+
{
245+
}
246+
247+
public boolean isReadable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
248+
{
249+
return String.class.isAssignableFrom(type);
250+
}
251+
252+
public String readFrom( Class<String> type, Type genericType, Annotation[] annotations, MediaType mediaType,
253+
MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) throws IOException,
254+
WebApplicationException
255+
{
256+
return null;
257+
}
258+
}
259+
260+
@Provider
261+
@Produces({"application/json"})
262+
@Consumes({"application/json"})
263+
public static class GenericProvider implements MessageBodyReader<String>, MessageBodyWriter<String> {
264+
public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
265+
{
266+
return String.class.isAssignableFrom(type);
267+
}
268+
269+
public long getSize( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
270+
{
271+
return -1L;
272+
}
273+
274+
public void writeTo( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
275+
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) throws IOException,
276+
WebApplicationException
277+
{
278+
}
279+
280+
public boolean isReadable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
281+
{
282+
return String.class.isAssignableFrom(type);
283+
}
284+
285+
public String readFrom( Class<String> type, Type genericType, Annotation[] annotations, MediaType mediaType,
286+
MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) throws IOException,
287+
WebApplicationException
288+
{
289+
return null;
290+
}
291+
}
143292
}

0 commit comments

Comments
 (0)