繁体   English   中英

处理多个版本的 XSD 生成类

[英]Handle multiple versions of XSD generated classes

我正在编写一个应用程序,该应用程序将集成到一个 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.
}

如何构建我的代码来解决这个问题? 哪些模式有用? 我是否完全走上了错误的道路?

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 元数据可以完全替换简单地增加(默认)注释提供的元数据。

试穿它的尺码,让我知道你的想法:

使用 EclipseLink MOXy 将对象映射到多个 XML 模式http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM