blob: 564a8de9ba73ae568106832ff9b4c3bccda3979a [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*******************************************************************************/
package org.apache.wink.common.internal.registry;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import junit.framework.TestCase;
import org.apache.wink.common.internal.application.ApplicationValidator;
import org.apache.wink.common.internal.lifecycle.LifecycleManagersRegistry;
import org.apache.wink.common.internal.utils.SoftConcurrentMap;
public class ProvidersRegistryTest extends TestCase {
/**
* Tests that the providersCache object is and remains instanceof ConcurrentHashMap.
*
* ProvidersRegistry.MediaTypeMap uses type ConcurrentHashMap on the providersCache object to provide some lock protection on
* the map when providers are dynamically added. However, lock protection is already built into the ProvidersRegistry methods:
* getContextResolver(), getMessageBodyReader(), and getMessageBodyWriter().
*
* However, the second protection (in the ProvidersRegistry methods) is for the cache itself which could be written to by two
* different threads even if they both were getting a single MessageBodyReader (i.e. a cache value may be dropped and then two
* threads come back later and try to write a new cache value). Due to some weird HashMap properties, this can blow up in
* weird ways.
*
* Thus, we need to ensure the providersCache continues to be instantiated with ConcurrentHashMap.
*/
public void testProtectionModel() throws Exception {
// I need the instantiated object providersCache in the abstract private nested class MediaTypeMap, so here we go!
ProvidersRegistry providersRegistry = new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
Field field = providersRegistry.getClass().getDeclaredField("messageBodyReaders");
field.setAccessible(true);
Object messageBodyReaders = field.get(providersRegistry);
Field field2 = messageBodyReaders.getClass().getSuperclass().getDeclaredField("providersCache");
field2.setAccessible(true);
Object providersCache = field2.get(messageBodyReaders);
assertTrue(providersCache instanceof SoftConcurrentMap);
}
/**
* Application subclass methods .getClasses and .getSingletons may list provider class or instance that is the same
* as a default Wink provider to override it to establish a new priority order.
*
* The order that the Wink runtime loads providers is:
* Application.getSingletons
* Application.getClasses
* system (through wink-providers file)
* extra jars (through each wink-application file)
*/
@SuppressWarnings("unchecked")
public void testOverrideSystemProvider() throws Exception {
ProvidersRegistry providersRegistry = createProvidersRegistryImpl();
assertTrue(providersRegistry.addProvider(StringReader.class, 0.5, false)); // registered as a user provider
MessageBodyReader<String> reader1 = providersRegistry.getMessageBodyReader(String.class, null, null, MediaType.TEXT_PLAIN_TYPE, null);
// registered twice as a custom user provider with higher priority
// See javadoc for Application.getSingletons to see why we ignore the attempt to add a second StringReader
assertFalse(providersRegistry.addProvider(StringReader.class, 0.6, false));
MessageBodyReader<String> reader2 = providersRegistry.getMessageBodyReader(String.class, null, null, MediaType.TEXT_PLAIN_TYPE, null);
assertTrue(reader1 == reader2); // object compare to make sure reader2 has been silently ignored
// registered as a system provider
assertFalse(providersRegistry.addProvider(StringReader.class, 0.1, false));
MessageBodyReader<String> reader3 = providersRegistry.getMessageBodyReader(String.class, null, null, MediaType.TEXT_PLAIN_TYPE, null);
assertTrue(reader1 == reader3); // object compare to make sure reader3 has been silently ignored
assertTrue(reader2 == reader3); // object compare to make sure reader3 has been silently ignored
// to confirm that the ignores are indeed happening, I need to get the private field
// "messageBodyReaders" object, then it's superclass "data" object and inspect it:
Field field = providersRegistry.getClass().getDeclaredField("messageBodyReaders");
field.setAccessible(true);
Object messageBodyReaders = field.get(providersRegistry);
Field field2 = messageBodyReaders.getClass().getSuperclass().getDeclaredField("data");
field2.setAccessible(true);
HashMap data = (HashMap)field2.get(messageBodyReaders);
Set readers = (Set)data.get(MediaType.WILDCARD_TYPE);
// make there is only one provider in the list to conform to JAX-RS 4.1 first sentence
assertEquals(1, readers.size());
}
/**
* Tests that a structured syntax suffix is handled correctly for determining a writer.
*
* @throws Exception
*/
public void testProvidesStructuredSyntaxSuffixHandledOk() throws Exception {
ProvidersRegistry providersRegistry =
new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
providersRegistry.addProvider(GenericProvider.class);
providersRegistry.addProvider(SpecificProvider.class);
MediaType mediaType;
MessageBodyWriter<String> writer;
mediaType = MediaType.valueOf("application/json"); // use generic provider
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
assertTrue(writer instanceof GenericProvider);
mediaType = MediaType.valueOf("application/vnd.other+json"); // use generic provider
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
assertTrue(writer instanceof GenericProvider);
mediaType = MediaType.valueOf("application/subschema+json"); // use specific provider
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
assertTrue(writer instanceof SpecificProvider);
mediaType = MediaType.valueOf("application/subschema"); // cannot use specific provider, nor generic
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
assertNull(writer);
mediaType = MediaType.valueOf("text/json"); // cannot use specific provider, nor generic
writer = providersRegistry.getMessageBodyWriter(String.class, null, null, mediaType, null);
assertNull(writer);
}
/**
* Tests that a structured syntax suffix is handled correctly for determining a reader.
*
* @throws Exception
*/
public void testConsumesStructuredSyntaxSuffixHandledOk() throws Exception {
ProvidersRegistry providersRegistry =
new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
providersRegistry.addProvider(GenericProvider.class);
providersRegistry.addProvider(SpecificProvider.class);
MediaType mediaType;
MessageBodyReader<String> reader;
mediaType = MediaType.valueOf("application/json"); // use generic provider
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
assertTrue(reader instanceof GenericProvider);
mediaType = MediaType.valueOf("application/vnd.other+json"); // use generic provider
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
assertTrue(reader instanceof GenericProvider);
mediaType = MediaType.valueOf("application/subschema+json"); // use specific provider
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
assertTrue(reader instanceof SpecificProvider);
mediaType = MediaType.valueOf("application/subschema"); // cannot use specific provider, nor generic
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
assertNull(reader);
mediaType = MediaType.valueOf("text/json"); // cannot use specific provider, nor generic
reader = providersRegistry.getMessageBodyReader(String.class, null, null, mediaType, null);
assertNull(reader);
}
// TODO: perhaps future tests should be added to actually exercise the providersCache code, but it would be an involved,
// multi-threaded test that dynamically adds providers at just the right time to ensure no problems with
// concurrent writes.
// Utility:
private ProvidersRegistry createProvidersRegistryImpl() {
ProvidersRegistry providers =
new ProvidersRegistry(new LifecycleManagersRegistry(), new ApplicationValidator());
;
return providers;
}
@Provider
@Produces( {MediaType.WILDCARD})
public static class StringReader implements MessageBodyReader<String> {
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
public String readFrom(Class<String> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException {
return "STRING";
}
}
@Provider
@Produces({"application/subschema+json"})
@Consumes({"application/subschema+json"})
public static class SpecificProvider implements MessageBodyReader<String>, MessageBodyWriter<String> {
public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return String.class.isAssignableFrom(type);
}
public long getSize( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return -1L;
}
public void writeTo( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) throws IOException,
WebApplicationException
{
}
public boolean isReadable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return String.class.isAssignableFrom(type);
}
public String readFrom( Class<String> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) throws IOException,
WebApplicationException
{
return null;
}
}
@Provider
@Produces({"application/json"})
@Consumes({"application/json"})
public static class GenericProvider implements MessageBodyReader<String>, MessageBodyWriter<String> {
public boolean isWriteable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return String.class.isAssignableFrom(type);
}
public long getSize( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return -1L;
}
public void writeTo( String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) throws IOException,
WebApplicationException
{
}
public boolean isReadable( Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType )
{
return String.class.isAssignableFrom(type);
}
public String readFrom( Class<String> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) throws IOException,
WebApplicationException
{
return null;
}
}
}