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 (OpenFeign#2005)

Co-authored-by: jernat <jernat.morbal@gmail.com>
Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 6, 2023
1 parent 551aa8a commit 7557597
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 15 deletions.
20 changes: 20 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextCacheKey.java
Original file line number Diff line number Diff line change
@@ -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 {
}
43 changes: 43 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextClassCacheKey.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
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,12 +31,15 @@
*/
public final class JAXBContextFactory {

private final ConcurrentHashMap<Class<?>, JAXBContext> jaxbContexts =
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;
}

/**
Expand All @@ -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;
}
Expand All @@ -91,6 +96,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.
*/
Expand Down Expand Up @@ -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.
*
* <p>
* Example : <br>
* <br>
* <code>
* new JAXBContextFactory.Builder()
* .withJAXBContextInstantiationMode(JAXBContextInstantationMode.PACKAGE)
* .build();
* </code>
* </p>
*/
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 @@ -167,7 +194,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
51 changes: 51 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextInstantationMode.java
Original file line number Diff line number Diff line change
@@ -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;
}
46 changes: 46 additions & 0 deletions jaxb/src/main/java/feign/jaxb/JAXBContextPackageCacheKey.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
71 changes: 63 additions & 8 deletions jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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<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,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 {
}
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();
}
}
Loading

0 comments on commit 7557597

Please sign in to comment.