Skip to content

Commit 7557597

Browse files
jeremybalanjernatvelo
authored
feat(jaxb-package): possibility to choose a JAXBContext instantiation using package mode (#2005)
Co-authored-by: jernat <jernat.morbal@gmail.com> Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
1 parent 551aa8a commit 7557597

File tree

11 files changed

+369
-15
lines changed

11 files changed

+369
-15
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb;
15+
16+
/**
17+
* Encapsulate data used to build the cache key of JAXBContext.
18+
*/
19+
interface JAXBContextCacheKey {
20+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb;
15+
16+
import java.util.Objects;
17+
18+
/**
19+
* Encapsulate data used to build the cache key of JAXBContext when created using class mode.
20+
*/
21+
final class JAXBContextClassCacheKey implements JAXBContextCacheKey {
22+
23+
private final Class<?> clazz;
24+
25+
JAXBContextClassCacheKey(Class<?> clazz) {
26+
this.clazz = clazz;
27+
}
28+
29+
@Override
30+
public boolean equals(Object o) {
31+
if (this == o)
32+
return true;
33+
if (o == null || getClass() != o.getClass())
34+
return false;
35+
JAXBContextClassCacheKey that = (JAXBContextClassCacheKey) o;
36+
return clazz.equals(that.clazz);
37+
}
38+
39+
@Override
40+
public int hashCode() {
41+
return Objects.hash(clazz);
42+
}
43+
}

jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@
3131
*/
3232
public final class JAXBContextFactory {
3333

34-
private final ConcurrentHashMap<Class<?>, JAXBContext> jaxbContexts =
34+
private final ConcurrentHashMap<JAXBContextCacheKey, JAXBContext> jaxbContexts =
3535
new ConcurrentHashMap<>(64);
3636
private final Map<String, Object> properties;
37+
private final JAXBContextInstantationMode jaxbContextInstantationMode;
3738

38-
private JAXBContextFactory(Map<String, Object> properties) {
39+
private JAXBContextFactory(Map<String, Object> properties,
40+
JAXBContextInstantationMode jaxbContextInstantationMode) {
3941
this.properties = properties;
42+
this.jaxbContextInstantationMode = jaxbContextInstantationMode;
4043
}
4144

4245
/**
@@ -62,10 +65,12 @@ private void setMarshallerProperties(Marshaller marshaller) throws PropertyExcep
6265
}
6366

6467
private JAXBContext getContext(Class<?> clazz) throws JAXBException {
65-
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
68+
JAXBContextCacheKey cacheKey = jaxbContextInstantationMode.getJAXBContextCacheKey(clazz);
69+
JAXBContext jaxbContext = this.jaxbContexts.get(cacheKey);
70+
6671
if (jaxbContext == null) {
67-
jaxbContext = JAXBContext.newInstance(clazz);
68-
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
72+
jaxbContext = jaxbContextInstantationMode.getJAXBContext(clazz);
73+
this.jaxbContexts.putIfAbsent(cacheKey, jaxbContext);
6974
}
7075
return jaxbContext;
7176
}
@@ -91,6 +96,9 @@ public static class Builder {
9196

9297
private final Map<String, Object> properties = new HashMap<>(10);
9398

99+
private JAXBContextInstantationMode jaxbContextInstantationMode =
100+
JAXBContextInstantationMode.CLASS;
101+
94102
/**
95103
* Sets the jaxb.encoding property of any Marshaller created by this factory.
96104
*/
@@ -149,12 +157,31 @@ public Builder withProperty(String key, Object value) {
149157
return this;
150158
}
151159

160+
/**
161+
* Provide an instantiation mode for JAXB Contexts, can be class or package, default is class if
162+
* this method is not called.
163+
*
164+
* <p>
165+
* Example : <br>
166+
* <br>
167+
* <code>
168+
* new JAXBContextFactory.Builder()
169+
* .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE)
170+
* .build();
171+
* </code>
172+
* </p>
173+
*/
174+
public Builder withJAXBContextInstantiationMode(JAXBContextInstantationMode jaxbContextInstantiationMode) {
175+
this.jaxbContextInstantationMode = jaxbContextInstantiationMode;
176+
return this;
177+
}
178+
152179
/**
153180
* Creates a new {@link feign.jaxb.JAXBContextFactory} instance with a lazy loading cached
154181
* context
155182
*/
156183
public JAXBContextFactory build() {
157-
return new JAXBContextFactory(properties);
184+
return new JAXBContextFactory(properties, jaxbContextInstantationMode);
158185
}
159186

160187
/**
@@ -167,7 +194,7 @@ public JAXBContextFactory build() {
167194
* likely due to missing JAXB annotations
168195
*/
169196
public JAXBContextFactory build(List<Class<?>> classes) throws JAXBException {
170-
JAXBContextFactory factory = new JAXBContextFactory(properties);
197+
JAXBContextFactory factory = new JAXBContextFactory(properties, jaxbContextInstantationMode);
171198
factory.preloadContextCache(classes);
172199
return factory;
173200
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb;
15+
16+
import javax.xml.bind.JAXBContext;
17+
import javax.xml.bind.JAXBException;
18+
19+
/**
20+
* Provides differents ways to instantiate a JAXB Context.
21+
*/
22+
public enum JAXBContextInstantationMode {
23+
24+
CLASS {
25+
@Override
26+
JAXBContextCacheKey getJAXBContextCacheKey(Class<?> clazz) {
27+
return new JAXBContextClassCacheKey(clazz);
28+
}
29+
30+
@Override
31+
JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException {
32+
return JAXBContext.newInstance(clazz);
33+
}
34+
},
35+
36+
PACKAGE {
37+
@Override
38+
JAXBContextCacheKey getJAXBContextCacheKey(Class<?> clazz) {
39+
return new JAXBContextPackageCacheKey(clazz.getPackage().getName(), clazz.getClassLoader());
40+
}
41+
42+
@Override
43+
JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException {
44+
return JAXBContext.newInstance(clazz.getPackage().getName(), clazz.getClassLoader());
45+
}
46+
};
47+
48+
abstract JAXBContextCacheKey getJAXBContextCacheKey(Class<?> clazz);
49+
50+
abstract JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException;
51+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb;
15+
16+
import java.util.Objects;
17+
18+
/**
19+
* Encapsulate data used to build the cache key of JAXBContext when created using package mode.
20+
*/
21+
final class JAXBContextPackageCacheKey implements JAXBContextCacheKey {
22+
23+
private final String packageName;
24+
25+
private final ClassLoader classLoader;
26+
27+
JAXBContextPackageCacheKey(String packageName, ClassLoader classLoader) {
28+
this.packageName = packageName;
29+
this.classLoader = classLoader;
30+
}
31+
32+
@Override
33+
public boolean equals(Object o) {
34+
if (this == o)
35+
return true;
36+
if (o == null || getClass() != o.getClass())
37+
return false;
38+
JAXBContextPackageCacheKey that = (JAXBContextPackageCacheKey) o;
39+
return packageName.equals(that.packageName) && classLoader.equals(that.classLoader);
40+
}
41+
42+
@Override
43+
public int hashCode() {
44+
return Objects.hash(packageName, classLoader);
45+
}
46+
}

jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@
1313
*/
1414
package feign.jaxb;
1515

16+
import feign.jaxb.mock.onepackage.AnotherMockedJAXBObject;
17+
import feign.jaxb.mock.onepackage.MockedJAXBObject;
18+
import org.junit.Test;
19+
import javax.xml.bind.Marshaller;
1620
import java.lang.reflect.Field;
1721
import java.util.Arrays;
1822
import java.util.List;
1923
import java.util.Map;
20-
import org.junit.Test;
21-
import javax.xml.bind.Marshaller;
22-
import static org.junit.Assert.assertEquals;
23-
import static org.junit.Assert.assertFalse;
24-
import static org.junit.Assert.assertNotNull;
25-
import static org.junit.Assert.assertTrue;
24+
import static org.junit.Assert.*;
2625

2726
public class JAXBContextFactoryTest {
2827

@@ -88,9 +87,65 @@ public void testPreloadCache() throws Exception {
8887
Map internalCache = (Map) f.get(factory); // IllegalAccessException
8988
assertFalse(internalCache.isEmpty());
9089
assertTrue(internalCache.size() == classes.size());
91-
assertNotNull(internalCache.get(String.class));
92-
assertNotNull(internalCache.get(Integer.class));
90+
assertNotNull(internalCache.get(new JAXBContextClassCacheKey(String.class)));
91+
assertNotNull(internalCache.get(new JAXBContextClassCacheKey(Integer.class)));
9392

9493
}
9594

95+
@Test
96+
public void testClassModeInstantiation() throws Exception {
97+
98+
List<Class<?>> classes = Arrays.asList(String.class, Integer.class);
99+
JAXBContextFactory factory =
100+
new JAXBContextFactory.Builder()
101+
.withJAXBContextInstantiationMode(JAXBContextInstantationMode.CLASS)
102+
.build(classes);
103+
104+
Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException
105+
f.setAccessible(true);
106+
Map internalCache = (Map) f.get(factory); // IllegalAccessException
107+
assertFalse(internalCache.isEmpty());
108+
assertEquals(internalCache.size(), classes.size());
109+
assertNotNull(internalCache.get(new JAXBContextClassCacheKey(String.class)));
110+
assertNotNull(internalCache.get(new JAXBContextClassCacheKey(Integer.class)));
111+
112+
}
113+
114+
@Test
115+
public void testPackageModeInstantiationUsingSamePackage() throws Exception {
116+
117+
JAXBContextFactory factory = new JAXBContextFactory.Builder()
118+
.withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE)
119+
.build(Arrays.asList(MockedJAXBObject.class, AnotherMockedJAXBObject.class));
120+
121+
Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException
122+
f.setAccessible(true);
123+
Map internalCache = (Map) f.get(factory); // IllegalAccessException
124+
assertFalse(internalCache.isEmpty());
125+
assertEquals(1, internalCache.size());
126+
assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.onepackage",
127+
AnotherMockedJAXBObject.class.getClassLoader())));
128+
129+
}
130+
131+
@Test
132+
public void testPackageModeInstantiationUsingMultiplePackages() throws Exception {
133+
134+
JAXBContextFactory factory = new JAXBContextFactory.Builder()
135+
.withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE)
136+
.build(Arrays.asList(MockedJAXBObject.class,
137+
feign.jaxb.mock.anotherpackage.MockedJAXBObject.class));
138+
139+
Field f = factory.getClass().getDeclaredField("jaxbContexts"); // NoSuchFieldException
140+
f.setAccessible(true);
141+
Map internalCache = (Map) f.get(factory); // IllegalAccessException
142+
assertFalse(internalCache.isEmpty());
143+
assertEquals(2, internalCache.size());
144+
assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.onepackage",
145+
MockedJAXBObject.class.getClassLoader())));
146+
assertNotNull(internalCache.get(new JAXBContextPackageCacheKey("feign.jaxb.mock.anotherpackage",
147+
feign.jaxb.mock.anotherpackage.MockedJAXBObject.class.getClassLoader())));
148+
149+
150+
}
96151
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb.mock.anotherpackage;
15+
16+
import javax.xml.bind.annotation.XmlRootElement;
17+
18+
@XmlRootElement(name = "anothertest")
19+
public class MockedJAXBObject {
20+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxb.mock.anotherpackage;
15+
16+
import javax.xml.bind.annotation.XmlRegistry;
17+
18+
@XmlRegistry
19+
public class ObjectFactory {
20+
21+
public MockedJAXBObject createMockedJAXBObject() {
22+
return new MockedJAXBObject();
23+
}
24+
}

0 commit comments

Comments
 (0)