You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wink.apache.org by ro...@apache.org on 2010/03/24 23:25:27 UTC

svn commit: r927206 - in /incubator/wink/trunk: wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/ wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/

Author: rott
Date: Wed Mar 24 22:25:27 2010
New Revision: 927206

URL: http://svn.apache.org/viewvc?rev=927206&view=rev
Log:
cache JAXBContexts from custom context resolvers in cases where the context resolver does not itself cache it

Added:
    incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/JAXBCustomContextResolverCacheTest.java
Modified:
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/AbstractJAXBProvider.java

Modified: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/AbstractJAXBProvider.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/AbstractJAXBProvider.java?rev=927206&r1=927205&r2=927206&view=diff
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/AbstractJAXBProvider.java (original)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/entity/xml/AbstractJAXBProvider.java Wed Mar 24 22:25:27 2010
@@ -82,7 +82,74 @@ public abstract class AbstractJAXBProvid
     private Pool<JAXBContext, Unmarshaller>                upool                     =
                                                                                                 new Pool<JAXBContext, Unmarshaller>();
 
-/**
+    /**
+     * This class is the key to the JAXBContext cache.  It must be based on the ContextResolver instance who created
+     * the JAXBContext and the type passed to it.  The only way this cache becomes invalid is if the ContextResolver does
+     * something crazy like create JAXBContexts based on time -- it only creates contexts between noon and 5:00pm.  So, uhhh,
+     * don't do that.
+     */
+    private static class JAXBContextResolverKey {
+        protected ContextResolver<JAXBContext> _resolver;
+        protected Type _type;
+        private int hashCode = -1;
+        public JAXBContextResolverKey(ContextResolver<JAXBContext> resolver, Type type) {
+            // resolver may be null, which is ok; we'll protect against NPEs in equals and hashCode overrides
+            _resolver = resolver;
+            _type = type;
+        }
+        
+        @Override
+        public boolean equals(Object o) {
+            if ((o == null) || (!(o instanceof JAXBContextResolverKey))) {
+                return false;
+            }
+            JAXBContextResolverKey obj = (JAXBContextResolverKey)o;
+            // check for both null or both NOT null
+            boolean result = ((obj._resolver == null) && (_resolver == null)) || ((obj._resolver != null) && (_resolver != null));
+            // we can use hashCode() to compare _resolver
+            return result && (obj.hashCode() == hashCode()) && (obj._type.equals(_type));
+        }
+
+        @Override
+        public int hashCode() {
+            if (hashCode != -1) {
+                return hashCode;
+            }
+            if (_resolver == null) {
+                // allow the key to be based entirely on the _type object equality from equals method.  Only YOU can prevent NPEs.
+                hashCode = 0;  // don't use _type's hashCode, as the instances may differ between JAXBContextResolverKey instances
+                return hashCode;
+            }
+            // Resolver instances may be unique due to the way we proxy the call to get the instances in the ProvidersRegistry.
+            // Therefore, we'll get better performance if we calculate the hashCode from the package.classname of the ContextResolver.
+            // However, this means we need to make sure the map that uses this key is non-static, so it remains scoped at the
+            // transaction level, rather than at the application, or worse, JVM level.
+            String resolverName = _resolver.getClass().getName();
+            byte[] bytes = resolverName.getBytes();
+            for (int i = 0; i < bytes.length; i++) {
+                hashCode += bytes[i];
+            }
+            return hashCode;
+        }
+        
+    }
+    
+    /*
+     * TODO: in my small, uncontrolled test, the JVM (garbage collector?) was cleaning about 10% of the time.  It may be worth
+     * considering the use of LRU cache or something more directly under our control to gain more of that.
+     * 
+     * To observe this behavior, set the "loop" int in JAXBCustomContextResolverCacheTest.testCustomResolverCacheOn to a high
+     * number, and see the System.out for cacheMisses.  In my checking, it was about 10% of "loop".
+     */
+    // do not make static, as the key is based on the classname of the ContextResolver
+    private final SoftConcurrentMap<JAXBContextResolverKey, JAXBContext> jaxbContextCache = new SoftConcurrentMap<JAXBContextResolverKey, JAXBContext>();
+    
+    // JAXBContext cache can be turned off through system property
+    static private final String propVal = System.getProperty("org.apache.wink.jaxbcontextcache");
+    // non-final, protected only to make it unittestable
+    static protected boolean contextCacheOn = !((propVal != null) && (propVal.equalsIgnoreCase("off")));
+    
+    /**
      * Get the unmarshaller. You must call {@link #releaseJAXBUnmarshaller(JAXBContext, Unmarshaller) to put it back
      * into the pool.
      * 
@@ -242,10 +309,22 @@ public abstract class AbstractJAXBProvid
     }
 
     protected JAXBContext getContext(Class<?> type, MediaType mediaType) throws JAXBException {
+        
         ContextResolver<JAXBContext> contextResolver =
             providers.getContextResolver(JAXBContext.class, mediaType);
+        
         JAXBContext context = null;
-
+        
+        JAXBContextResolverKey key = null;
+        if (contextCacheOn) {
+            // it's ok and safe for contextResolver to be null at this point.  JAXBContextResolverKey can handle it
+            key = new JAXBContextResolverKey(contextResolver, type);
+            context = jaxbContextCache.get(key);
+            if (context != null) {
+                return context;
+            }
+        }
+        
         if (contextResolver != null) {
             context = contextResolver.getContext(type);
         }
@@ -253,6 +332,11 @@ public abstract class AbstractJAXBProvid
         if (context == null) {
             context = getDefaultContext(type);
         }
+        
+        if (contextCacheOn) {
+            jaxbContextCache.put(key, context);
+        }
+        
         return context;
     }
 

Added: incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/JAXBCustomContextResolverCacheTest.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/JAXBCustomContextResolverCacheTest.java?rev=927206&view=auto
==============================================================================
--- incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/JAXBCustomContextResolverCacheTest.java (added)
+++ incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/providers/entity/JAXBCustomContextResolverCacheTest.java Wed Mar 24 22:25:27 2010
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * 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.server.internal.providers.entity;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.wink.common.internal.providers.entity.xml.JAXBElementXmlProvider;
+import org.apache.wink.server.internal.servlet.MockServletInvocationTest;
+import org.apache.wink.test.mock.MockRequestConstructor;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+/**
+ * Intent of this class is to exercise the JAXBContext cache in the ProvidersRegistry.
+ */
+public class JAXBCustomContextResolverCacheTest extends MockServletInvocationTest {
+
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "arg0",
+        "arg1"
+    })
+    @XmlRootElement(name = "addNumbers")
+    public static class AddNumbers {
+
+        protected int arg0;
+        protected int arg1;
+
+        /**
+         * Gets the value of the arg0 property.
+         * 
+         */
+        public int getArg0() {
+            return arg0;
+        }
+
+        /**
+         * Sets the value of the arg0 property.
+         * 
+         */
+        public void setArg0(int value) {
+            this.arg0 = value;
+        }
+
+        /**
+         * Gets the value of the arg1 property.
+         * 
+         */
+        public int getArg1() {
+            return arg1;
+        }
+
+        /**
+         * Sets the value of the arg1 property.
+         * 
+         */
+        public void setArg1(int value) {
+            this.arg1 = value;
+        }
+
+    }
+    
+    public static class MyJAXBElementXmlProvider extends JAXBElementXmlProvider {
+        public static void setContextCacheOff() {
+            contextCacheOn = false;
+        }
+        public static void setContextCacheOn() {
+            contextCacheOn = true;
+        }
+    }
+    
+    static int cacheMisses = 0;
+    
+    @Provider
+    @Produces( MediaType.APPLICATION_XML)
+    public static class XMLContextResolver implements ContextResolver<JAXBContext> {
+
+        public JAXBContext getContext(Class arg0) {
+            try {
+                cacheMisses++;
+                return JAXBContext.newInstance(arg0.getPackage().getName());
+            } catch (JAXBException e) {
+                return null;
+            }
+        }
+    }
+    
+    @Override
+    protected Class<?>[] getClasses() {
+        return new Class<?>[] {Resource.class, XMLContextResolver.class, MyJAXBElementXmlProvider.class};
+    }
+
+    @Path("jaxbresource")
+    public static class Resource {
+
+        @POST
+        @Path("jaxbelement")
+        public String add(AddNumbers addNumbers) {
+            return String.valueOf(addNumbers.getArg0() + addNumbers.getArg1());
+        }
+    }
+    
+    @Override
+    protected void setUp() throws Exception {
+        cacheMisses = 0;
+        super.setUp();
+    }
+    
+    // CAUTION: don't use these methods as an accurate measure of performance.  That's not their purpose, and there's
+    // too many other things going on underneath anyway, such as junit, logging, etc.
+    
+    public void testCustomResolverCacheOff() throws Exception {
+        // users will set the "org.apache.wink.jaxbcontextcache" to "on" or "off"
+        MyJAXBElementXmlProvider.setContextCacheOff();
+        // must loop a few times to get an accurate count of cache misses with cache turned off
+        int expectedCacheMisses = 100;
+        for (int i = 0; i < expectedCacheMisses; i++) {
+            AddNumbers addNumbers = new AddNumbers();
+            addNumbers.setArg0(2);
+            addNumbers.setArg1(3);
+
+            JAXBContext context = JAXBContext.newInstance(AddNumbers.class);
+            Marshaller marshaller = context.createMarshaller();
+            Writer writer = new StringWriter();
+            marshaller.marshal(addNumbers, writer);
+
+            MockHttpServletRequest request = MockRequestConstructor
+                    .constructMockRequest("POST", "/jaxbresource/jaxbelement",
+                            MediaType.TEXT_PLAIN, MediaType.APPLICATION_XML,
+                            writer.toString().getBytes());
+            MockHttpServletResponse response = invoke(request);
+            assertEquals(200, response.getStatus());
+            assertEquals("5", response.getContentAsString());
+        }
+        assertEquals(expectedCacheMisses, cacheMisses);
+    }
+    
+    public void testCustomResolverCacheOn() throws Exception {
+     // users will set the "org.apache.wink.jaxbcontextcache" to "on" or "off"
+        MyJAXBElementXmlProvider.setContextCacheOn();
+        // must loop at least twice to initialize and exercise the underlying JAXBContext cache and get accurate count of cacheMisses
+        int loop = 100;
+        for (int i = 0; i < loop; i++) {
+            AddNumbers addNumbers = new AddNumbers();
+            addNumbers.setArg0(2);
+            addNumbers.setArg1(3);
+
+            JAXBContext context = JAXBContext.newInstance(AddNumbers.class);
+            Marshaller marshaller = context.createMarshaller();
+            Writer writer = new StringWriter();
+            marshaller.marshal(addNumbers, writer);
+
+            MockHttpServletRequest request = MockRequestConstructor
+                    .constructMockRequest("POST", "/jaxbresource/jaxbelement",
+                            MediaType.TEXT_PLAIN, MediaType.APPLICATION_XML,
+                            writer.toString().getBytes());
+            MockHttpServletResponse response = invoke(request);
+            assertEquals(200, response.getStatus());
+            assertEquals("5", response.getContentAsString());
+        }
+        // there will be some cache misses due to the garbage collector cleaning up the SoftConcurrentMap
+        // cache, but certainly the number of misses will be lower than the number of times through the loop.
+        System.out.println("loops = " + loop + ", cacheMisses = " + cacheMisses);
+        // kind of a guess, but if we're getting 20% cache misses, then we have a VERY aggressive garbage collector
+        assertTrue("expected: " + (loop/5) + " > " + cacheMisses, (loop/5) > cacheMisses);
+    }
+}