我正在编写一个将集成到API的应用程序,该API的端点返回XSD支持的XML。我的应用程序最初必须针对此应用程序的两个不同版本(可能在将来的版本中提供更多版本),而不必是动态的。启动应用程序时,用户将必须告诉应用程序要支持哪个API主版本。 XSD不在我的控制下,我不想编辑它们。
XSD生成相同名称的类,并且我已经在这里遇到了问题。我无法将XJC生成的两个ObjectFactory
类都加载到JAXBContext中。我的解决方案现在是JAXBContexts的映射:
private static Map<Integer, Pair<Class<?>, JAXBContext>> contexts = new HashMap<>();
static {
try {
contexts.put(4, Pair.of(com.api.v4_2_0.ObjectFactory.class, JAXBContext.newInstance(com.api.v4_2_0.ObjectFactory.class)));
contexts.put(3, Pair.of(com.api.v3_9_4.ObjectFactory.class, JAXBContext.newInstance(com.api.v3_9_4.ObjectFactory.class)));
} catch (JAXBException e) {
LOGGER.error("Failed to initialize JAXBContext", e);
}
}
该对用于了解JAXBContext基于哪个类,因为我无法在运行时恢复该类。然后,要序列化对象,我使用了很多魔术反射,这些反射有效但感觉不正确:
public static String objectToString(final Object object, final Integer majorVersion) {
try {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
getMarshallerForMajorVersion(majorVersion).marshal(createJaxbElementFromObject(object, getObjectFactoryForMajorVersion(majorVersion)), os);
return os.toString(UTF_8);
} catch (JAXBException e) {
throw SesameException.from("Failed to serialize object", e);
}
}
private static Marshaller getMarshallerForMajorVersion(final Integer majorVersion) throws JAXBException {
return getContextForMajorVersion(majorVersion).getRight().createMarshaller();
}
private static Class<?> getObjectFactoryForMajorVersion(final Integer majorVersion) {
return getContextForMajorVersion(majorVersion).getLeft();
}
private static Pair<Class<?>, JAXBContext> getContextForMajorVersion(final Integer majorVersion) {
if (contexts.containsKey(majorVersion)) {
return contexts.get(majorVersion);
}
throw illegalArgument("No JAXBContext for API with major version %d", majorVersion);
}
private static JAXBElement<?> createJaxbElementFromObject(final Object object, final Class<?> objectFactory) {
try {
LOGGER.info("Attempting to find a JAXBElement producer for class {}", object.getClass());
final Method method = findElementMethodInObjectFactory(object, objectFactory);
return (JAXBElement<?>) method.invoke(objectFactory.getConstructor().newInstance(), object);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
throw illegalArgument("Failed to construct JAXBElement for class %s", object.getClass().getName());
}
}
private static Method findElementMethodInObjectFactory(final Object object, final Class<?> left) {
return Arrays.stream(left.getMethods())
.filter(m -> m.getReturnType().equals(JAXBElement.class))
.filter(m -> m.getName().endsWith(object.getClass().getSimpleName()))
.findFirst()
.orElseThrow(() -> illegalArgument("Failed to find JAXBElement constructor for class %s", object.getClass().getName()));
}
这很好,但感觉很脆弱。
问题
更糟糕的是,必须使用泛型将XML反序列化为对象:
public static <T> T stringToObject(final String xml, final Class<T> toClass, final Integer majorVersion) {
try {
final Unmarshaller unmarshaller = getUnmarshallerForVersion(majorVersion);
final JAXBElement<T> unmarshalledElement = (JAXBElement<T>) unmarshaller.unmarshal(new StringReader(xml));
return toClass.cast(unmarshalledElement.getValue());
} catch (JAXBException e) {
throw SesameException.from(format("Failed to deserialize XML into %s", toClass.getCanonicalName()), e);
}
}
// And calling this from another class
private com.api.v4_2_0.SomeClass.class toSomeClass(final HttpResponse<String> response) {
return XmlUtil.stringToObject(response.body(), com.api.v4_2_0.SomeClass.class, apiMajorVersion); // <--- I can't beforehand use this package since major version might be 3.
}
现在(据我所知),我无法使用泛型并将其根据所使用的API的主要版本映射到正确包中的正确类。
我也尝试过使用抽象基类,并且只为每个版本提供一个单独的ObjectFactory
,但这仍然给我在问题部分中描述的问题。我不知道如何返回该类的正确版本:
private com.api.v4_2_0.SomeClass.class toSomeClass(final HttpResponse<String> response) {
return version4XmlUtil.stringToObject(response.body(), com.api.v4_2_0.SomeClass.class); // <--- I can't beforehand use this package since major version might be 3.
}
如何构造代码来解决此问题?哪些模式有用?我会完全走错路了吗?
答案 0 :(得分:-1)
EclipseLink JAXB(MOXy)的@XmlPath和外部绑定文件扩展名可以提供帮助。
我在下面引用的博客将单个对象模型映射到两个不同的XML模式。它通过使用标准JAXB和MOXy扩展注释的组合来映射第一个API来实现此目的。
package blog.weather;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.eclipse.persistence.oxm.annotations.XmlPath;
@XmlRootElement(name="xml_api_reply")
@XmlType(propOrder={"location", "currentCondition", "currentTemperature", "forecast"})
@XmlAccessorType(XmlAccessType.FIELD)
public class WeatherReport {
@XmlPath("weather/forecast_information/city/@data")
private String location;
@XmlPath("weather/current_conditions/temp_f/@data")
private int currentTemperature;
@XmlPath("weather/current_conditions/condition/@data")
private String currentCondition;
@XmlPath("weather/forecast_conditions")
private List<Forecast> forecast;
}
然后...
package blog.weather;
import org.eclipse.persistence.oxm.annotations.XmlPath;
public class Forecast {
@XmlPath("day_of_week/@data")
private String dayOfTheWeek;
@XmlPath("low/@data")
private int low;
@XmlPath("high/@data")
private int high;
@XmlPath("condition/@data")
private String condition;
}
您不能通过注释创建到对象模型的辅助映射集,因此其他的可以利用MOXy的XML元数据,从而覆盖了第二个API。
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="blog.weather"
xml-mapping-metadata-complete="true">
<xml-schema element-form-default="QUALIFIED">
<xml-ns prefix="yweather" namespace-uri="http://xml.weather.yahoo.com/ns/rss/1.0"/>
</xml-schema>
<java-types>
<java-type name="WeatherReport" xml-accessor-type="FIELD">
<xml-root-element name="rss"/>
<xml-type prop-order="location currentTemperature currentCondition forecast"/>
<java-attributes>
<xml-attribute java-attribute="location" xml-path="channel/yweather:location/@city"/>
<xml-attribute java-attribute="currentTemperature" name="channel/item/yweather:condition/@temp"/>
<xml-attribute java-attribute="currentCondition" name="channel/item/yweather:condition/@text"/>
<xml-element java-attribute="forecast" name="channel/item/yweather:forecast"/>
</java-attributes>
</java-type>
<java-type name="Forecast" xml-accessor-type="FIELD">
<java-attributes>
<xml-attribute java-attribute="dayOfTheWeek" name="day"/>
<xml-attribute java-attribute="low"/>
<xml-attribute java-attribute="high"/>
<xml-attribute java-attribute="condition" name="text"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
默认行为是MOXy的映射文档用于扩充模型上指定的所有注释。但是,取决于设置xml-mapping-metadata-complete
标志的方式,XML元数据可以完全替换,或简单扩充(默认),即注释提供的元数据。
试穿以获取尺寸,让我知道您的想法:
Mapping Objects to Multiple XML Schemas Using EclipseLink MOXy http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html