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 000000000..f3c8bba2f --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java @@ -0,0 +1,17 @@ +/* + * 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 000000000..a224b9985 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java @@ -0,0 +1,39 @@ +/* + * 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 f6be822e2..dd2216eba 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java @@ -31,11 +31,15 @@ */ public final class JAXBContextFactory { - private final ConcurrentHashMap, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64); + 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; } /** Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class. */ @@ -57,10 +61,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; } @@ -84,6 +90,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. */ public Builder withMarshallerJAXBEncoding(String value) { properties.put(Marshaller.JAXB_ENCODING, value); @@ -132,12 +141,30 @@ 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); } /** @@ -150,7 +177,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 000000000..f26f90177 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java @@ -0,0 +1,48 @@ +/* + * 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 000000000..acd120a36 --- /dev/null +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java @@ -0,0 +1,42 @@ +/* + * 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 44e362146..582705569 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java @@ -13,11 +13,10 @@ */ package feign.jaxb; -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.*; +import feign.jaxb.mock.onepackage.AnotherMockedJAXBObject; +import feign.jaxb.mock.onepackage.MockedJAXBObject; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; @@ -91,7 +90,70 @@ 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 000000000..6da3403b6 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/anotherpackage/MockedJAXBObject.java @@ -0,0 +1,19 @@ +/* + * 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 000000000..441922c81 --- /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 000000000..ff2821ca1 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/onepackage/AnotherMockedJAXBObject.java @@ -0,0 +1,19 @@ +/* + * 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 000000000..703ee4006 --- /dev/null +++ b/jaxb/src/test/java/feign/jaxb/mock/onepackage/MockedJAXBObject.java @@ -0,0 +1,19 @@ +/* + * 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 000000000..59195dce0 --- /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(); + } +}