| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |