diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java b/jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java new file mode 100644 index 0000000000..8df9c5be87 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb; + +/** + * Encapsulate data used to build the cache key of JAXBContext. + */ +interface JAXBContextCacheKey { +} diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java b/jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java new file mode 100644 index 0000000000..ba8dbcf575 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb; + +import java.util.Objects; + +/** + * Encapsulate data used to build the cache key of JAXBContext when created using class mode. + */ +final class JAXBContextClassCacheKey implements JAXBContextCacheKey { + + private final Class clazz; + + JAXBContextClassCacheKey(Class clazz) { + this.clazz = clazz; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + JAXBContextClassCacheKey that = (JAXBContextClassCacheKey) o; + return clazz.equals(that.clazz); + } + + @Override + public int hashCode() { + return Objects.hash(clazz); + } +} diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java b/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java index 07f126b72f..232b889c7c 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java @@ -31,12 +31,15 @@ */ public final class JAXBContextFactory { - private final ConcurrentHashMap, JAXBContext> jaxbContexts = + private final ConcurrentHashMap jaxbContexts = new ConcurrentHashMap<>(64); private final Map properties; + private final JAXBContextInstantationMode jaxbContextInstantationMode; - private JAXBContextFactory(Map properties) { + private JAXBContextFactory(Map properties, + JAXBContextInstantationMode jaxbContextInstantationMode) { this.properties = properties; + this.jaxbContextInstantationMode = jaxbContextInstantationMode; } /** @@ -62,10 +65,12 @@ private void setMarshallerProperties(Marshaller marshaller) throws PropertyExcep } private JAXBContext getContext(Class clazz) throws JAXBException { - JAXBContext jaxbContext = this.jaxbContexts.get(clazz); + JAXBContextCacheKey cacheKey = jaxbContextInstantationMode.getJAXBContextCacheKey(clazz); + JAXBContext jaxbContext = this.jaxbContexts.get(cacheKey); + if (jaxbContext == null) { - jaxbContext = JAXBContext.newInstance(clazz); - this.jaxbContexts.putIfAbsent(clazz, jaxbContext); + jaxbContext = jaxbContextInstantationMode.getJAXBContext(clazz); + this.jaxbContexts.putIfAbsent(cacheKey, jaxbContext); } return jaxbContext; } @@ -91,6 +96,9 @@ public static class Builder { private final Map properties = new HashMap<>(10); + private JAXBContextInstantationMode jaxbContextInstantationMode = + JAXBContextInstantationMode.CLASS; + /** * Sets the jaxb.encoding property of any Marshaller created by this factory. */ @@ -149,12 +157,31 @@ public Builder withProperty(String key, Object value) { return this; } + /** + * Provide an instantiation mode for JAXB Contexts, can be class or package, default is class if + * this method is not called. + * + *

+ * Example :
+ *
+ * + * new JAXBContextFactory.Builder() + * .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE) + * .build(); + * + *

+ */ + public Builder withJAXBContextInstantiationMode(JAXBContextInstantationMode jaxbContextInstantiationMode) { + this.jaxbContextInstantationMode = jaxbContextInstantiationMode; + return this; + } + /** * Creates a new {@link feign.jaxb.JAXBContextFactory} instance with a lazy loading cached * context */ public JAXBContextFactory build() { - return new JAXBContextFactory(properties); + return new JAXBContextFactory(properties, jaxbContextInstantationMode); } /** @@ -167,7 +194,7 @@ public JAXBContextFactory build() { * likely due to missing JAXB annotations */ public JAXBContextFactory build(List> classes) throws JAXBException { - JAXBContextFactory factory = new JAXBContextFactory(properties); + JAXBContextFactory factory = new JAXBContextFactory(properties, jaxbContextInstantationMode); factory.preloadContextCache(classes); return factory; } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java b/jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java new file mode 100644 index 0000000000..3afcc57da1 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; + +/** + * Provides differents ways to instantiate a JAXB Context. + */ +public enum JAXBContextInstantationMode { + + CLASS { + @Override + JAXBContextCacheKey getJAXBContextCacheKey(Class clazz) { + return new JAXBContextClassCacheKey(clazz); + } + + @Override + JAXBContext getJAXBContext(Class clazz) throws JAXBException { + return JAXBContext.newInstance(clazz); + } + }, + + PACKAGE { + @Override + JAXBContextCacheKey getJAXBContextCacheKey(Class clazz) { + return new JAXBContextPackageCacheKey(clazz.getPackage().getName(), clazz.getClassLoader()); + } + + @Override + JAXBContext getJAXBContext(Class clazz) throws JAXBException { + return JAXBContext.newInstance(clazz.getPackage().getName(), clazz.getClassLoader()); + } + }; + + abstract JAXBContextCacheKey getJAXBContextCacheKey(Class clazz); + + abstract JAXBContext getJAXBContext(Class clazz) throws JAXBException; +} diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java b/jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java new file mode 100644 index 0000000000..55c8d9a814 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb; + +import java.util.Objects; + +/** + * Encapsulate data used to build the cache key of JAXBContext when created using package mode. + */ +final class JAXBContextPackageCacheKey implements JAXBContextCacheKey { + + private final String packageName; + + private final ClassLoader classLoader; + + JAXBContextPackageCacheKey(String packageName, ClassLoader classLoader) { + this.packageName = packageName; + this.classLoader = classLoader; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + JAXBContextPackageCacheKey that = (JAXBContextPackageCacheKey) o; + return packageName.equals(that.packageName) && classLoader.equals(that.classLoader); + } + + @Override + public int hashCode() { + return Objects.hash(packageName, classLoader); + } +} diff --git a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java index b4d7f3eb18..d84d61cbcb 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java @@ -13,16 +13,15 @@ */ package feign.jaxb; +import feign.jaxb.mock.onepackage.AnotherMockedJAXBObject; +import feign.jaxb.mock.onepackage.MockedJAXBObject; +import org.junit.Test; +import javax.xml.bind.Marshaller; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import java.util.Map; -import org.junit.Test; -import javax.xml.bind.Marshaller; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class JAXBContextFactoryTest { @@ -88,9 +87,65 @@ public void testPreloadCache() throws Exception { Map internalCache = (Map) f.get(factory); // IllegalAccessException assertFalse(internalCache.isEmpty()); assertTrue(internalCache.size() == classes.size()); - assertNotNull(internalCache.get(String.class)); - assertNotNull(internalCache.get(Integer.class)); + assertNotNull(internalCache.get(new JAXBContextClassCacheKey(String.class))); + assertNotNull(internalCache.get(new JAXBContextClassCacheKey(Integer.class))); } + @Test + public void testClassModeInstantiation() throws Exception { + + List> classes = Arrays.asList(String.class, Integer.class); + JAXBContextFactory factory = + new JAXBContextFactory.Builder() + .withJAXBContextInstantiationMode(JAXBContextInstantationMode.CLASS) + .build(classes); + + Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException + f.setAccessible(true); + Map internalCache = (Map) f.get(factory); // IllegalAccessException + assertFalse(internalCache.isEmpty()); + assertEquals(internalCache.size(), classes.size()); + assertNotNull(internalCache.get(new JAXBContextClassCacheKey(String.class))); + assertNotNull(internalCache.get(new JAXBContextClassCacheKey(Integer.class))); + + } + + @Test + public void testPackageModeInstantiationUsingSamePackage() throws Exception { + + JAXBContextFactory factory = new JAXBContextFactory.Builder() + .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE) + .build(Arrays.asList(MockedJAXBObject.class, AnotherMockedJAXBObject.class)); + + Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException + f.setAccessible(true); + Map internalCache = (Map) f.get(factory); // IllegalAccessException + assertFalse(internalCache.isEmpty()); + assertEquals(1, internalCache.size()); + assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.onepackage", + AnotherMockedJAXBObject.class.getClassLoader()))); + + } + + @Test + public void testPackageModeInstantiationUsingMultiplePackages() throws Exception { + + JAXBContextFactory factory = new JAXBContextFactory.Builder() + .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE) + .build(Arrays.asList(MockedJAXBObject.class, + feign.jaxb.mock.anotherpackage.MockedJAXBObject.class)); + + Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException + f.setAccessible(true); + Map internalCache = (Map) f.get(factory); // IllegalAccessException + assertFalse(internalCache.isEmpty()); + assertEquals(2, internalCache.size()); + assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.onepackage", + MockedJAXBObject.class.getClassLoader()))); + assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.anotherpackage", + feign.jaxb.mock.anotherpackage.MockedJAXBObject.class.getClassLoader()))); + + + } } diff --git a/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/MockedJAXBObject.java b/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/MockedJAXBObject.java new file mode 100644 index 0000000000..a282436275 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/MockedJAXBObject.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb.mock.anotherpackage; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "anothertest") +public class MockedJAXBObject { +} diff --git a/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/ObjectFactory.java b/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/ObjectFactory.java new file mode 100644 index 0000000000..441922c818 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/ObjectFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb.mock.anotherpackage; + +import javax.xml.bind.annotation.XmlRegistry; + +@XmlRegistry +public class ObjectFactory { + + public MockedJAXBObject createMockedJAXBObject() { + return new MockedJAXBObject(); + } +} diff --git a/jaxb/src/test/java/feign/jaxb/mock/onepackage/AnotherMockedJAXBObject.java b/jaxb/src/test/java/feign/jaxb/mock/onepackage/AnotherMockedJAXBObject.java new file mode 100644 index 0000000000..cf6e7f61d0 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/onepackage/AnotherMockedJAXBObject.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb.mock.onepackage; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class AnotherMockedJAXBObject { +} diff --git a/jaxb/src/test/java/feign/jaxb/mock/onepackage/MockedJAXBObject.java b/jaxb/src/test/java/feign/jaxb/mock/onepackage/MockedJAXBObject.java new file mode 100644 index 0000000000..bd4ae97696 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/onepackage/MockedJAXBObject.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb.mock.onepackage; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "test") +public class MockedJAXBObject { +} diff --git a/jaxb/src/test/java/feign/jaxb/mock/onepackage/ObjectFactory.java b/jaxb/src/test/java/feign/jaxb/mock/onepackage/ObjectFactory.java new file mode 100644 index 0000000000..59195dce09 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/onepackage/ObjectFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2023 The Feign Authors + * + * Licensed 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 feign.jaxb.mock.onepackage; + +import javax.xml.bind.annotation.XmlRegistry; + +@XmlRegistry +public class ObjectFactory { + + public MockedJAXBObject createMockedJAXBObject() { + return new MockedJAXBObject(); + } + + public AnotherMockedJAXBObject createAnotherMockedJAXBObject() { + return new AnotherMockedJAXBObject(); + } +}