Skip to content

Commit

Permalink
feat(jaxb-package): possibility to choose a JAXBContext instantiation…
Browse files Browse the repository at this point in the history
… using package mode (#2005)

Co-authored-by: jernat <[email protected]>
Co-authored-by: Marvin Froeder <[email protected]>
  • Loading branch information
3 people authored Apr 6, 2023
1 parent 02f6eaa commit 042a587
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 13 deletions.
17 changes: 17 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java
Original file line number Diff line number Diff line change
@@ -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 {}
39 changes: 39 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 34 additions & 7 deletions jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@
*/
public final class JAXBContextFactory {

private final ConcurrentHashMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64);
private final ConcurrentHashMap<JAXBContextCacheKey, JAXBContext> jaxbContexts =
new ConcurrentHashMap<>(64);
private final Map<String, Object> properties;
private final JAXBContextInstantationMode jaxbContextInstantationMode;

private JAXBContextFactory(Map<String, Object> properties) {
private JAXBContextFactory(
Map<String, Object> properties, JAXBContextInstantationMode jaxbContextInstantationMode) {
this.properties = properties;
this.jaxbContextInstantationMode = jaxbContextInstantationMode;
}

/** Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class. */
Expand All @@ -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;
}
Expand All @@ -84,6 +90,9 @@ public static class Builder {

private final Map<String, Object> 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);
Expand Down Expand Up @@ -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.
*
* <p>Example : <br>
* <br>
* <code>
* new JAXBContextFactory.Builder()
* .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE)
* .build();
* </code>
*/
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);
}

/**
Expand All @@ -150,7 +177,7 @@ public JAXBContextFactory build() {
* likely due to missing JAXB annotations
*/
public JAXBContextFactory build(List<Class<?>> classes) throws JAXBException {
JAXBContextFactory factory = new JAXBContextFactory(properties);
JAXBContextFactory factory = new JAXBContextFactory(properties, jaxbContextInstantationMode);
factory.preloadContextCache(classes);
return factory;
}
Expand Down
48 changes: 48 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java
Original file line number Diff line number Diff line change
@@ -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;
}
42 changes: 42 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
74 changes: 68 additions & 6 deletions jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Class<?>> 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())));
}
}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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 {}
Loading

0 comments on commit 042a587

Please sign in to comment.