Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to choose between class or package instantiation mode for JaxbContext #2005

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}
}