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);
+ }
+}