1

我刚刚开始使用 JAXB 将传入的 SOAP 文档解组到我们的域类的 Web 服务上。我遇到了一个技术挑战,这是由丹麦政府机构中使用的 OIO XML 格式决定的。除其他事项外,该格式声明不允许将 xml 模式属性 nillable 用于 xml 元素声明。因此,我必须为我的挑战找到另一种解决方案。

描述 我们有一些数字和日期,可以由 Web 服务客户端发送以更新应用程序。这些数字和日期映射到等效类型的 POJO 字段。挑战在于如何通过构造和发送正确的 XML 来重置此类 POJO 字段的值。

发送 12:31:34T01-01-2010..... 会将 POJO 字段更新为指定值。

但是,我不能通过发送来重置该字段,因为它不允许用于日期时间元素。

我也不能发送,因为 OIO XML 标准不允许这样做。

因此,我作为一个严峻的解决方法计划发送,因为它不应该被 OIO XML 标准禁止。

这带来了一个挑战,如果 startTime 元素包含 delete="true" 属性,那么相应的 POJO 字段是否应该设置为 null;如果 a 没有删除属性,则将有效元素值传输到 POJO 字段。

@XMLElement 注释只允许我映射 startTime 值,例如

class MyClass{
   @XMLElement
   private Date startTime;
}

如何强制删除属性也影响 MyClass.startTime 字段的值?

最好的问候,杰斯珀

4

5 回答 5

1

非常感谢您的回答。它似乎适用于一个非常简单的场景,但对于稍微复杂的场景我没有成功。

我试图在我自己的域中模仿你的代码,只是我在编组数据而不是编组。

适配器类:

public class DeleteStringAdapter extends XmlAdapter<DeletableString, String> {

@Override
public DeletableString marshal(String value) throws Exception {
    System.out.println("MARSHALL: " + value);
    return new DeletableString(value);
}

@Override
public String unmarshal(DeletableString v) throws Exception {
    System.out.println("UNMARSHALL: "  + v);

    if(v.isDelete() != null && v.isDelete()){
        return null;
    }

    return v.getValue();    
}

}

适配器类型:

public class DeletableString {

public DeletableString() {
}

public DeletableString(String value) {
    this.value = value;
}

private Boolean delete;

private String value;

@XmlAttribute
public Boolean isDelete() {
    return delete;
}

public void setDelete(Boolean delete) {
    this.delete = delete;
}   

@XmlValue
public String getValue() {
    return value;
}

public void setValue(String value) {
    this.value = value;
}

@Override
public String toString() {
    return DeletableString.class.getSimpleName() + "[delete=" + isDelete() + ", value=" + value + "]";
}
}

带注释的工作域类:

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "xml-fragment")
public class SimpleNavne implements Serializable{

private String forNavn = "";

private String fornavneMrkKode = "";

@XmlElement(name="PersonGivenName")
@XmlJavaTypeAdapter(value = DeleteStringAdapter.class)
public String getForNavn() {
    return forNavn;
}

public void setForNavn(String forNavn) {
    this.forNavn = forNavn;
}

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(SimpleNavne.class.getSimpleName() + "[");
    builder.append("forNavn=");
    builder.append(forNavn);
    builder.append("]");
    return builder.toString();
}

}

演示

public class AppTest {

@Test
public void testApp() throws Exception {

    System.setProperty("jaxb.debug", "true");

    try{
        JAXBContext jaxbContext = JAXBContext.newInstance(SimpleNavne.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Object ps =  unmarshaller.unmarshal(new File("./personname-test4.xml"));

        System.out.println(ps);

    }catch(Exception e){
        e.printStackTrace();
    }
}
}

文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
<ns:PersonGivenName delete="false">010</ns:PersonGivenName>
</ns:xml-fragment>

如果从输出中得出结论,以上似乎工作正常

SimpleNavne[forNavn=010]

但是,在 SimpleName 域类中引入使用 @XMLPath 注释的字段时遇到问题。

修改域类

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "xml-fragment")
public class SimpleNavne implements Serializable{

private String forNavn = "";

private String fornavneMrkKode = "";

@XmlElement(name="PersonGivenName")
@XmlJavaTypeAdapter(value = DeleteStringAdapter.class)
public String getForNavn() {
    return forNavn;
}

public void setForNavn(String forNavn) {
    this.forNavn = forNavn;
}

@XmlPath("/PersonGivenNameMarkingStructure/MarkingCode/text()")
public String getFornavneMrkKode() {
    return fornavneMrkKode;
}

public void setFornavneMrkKode(String forNavnMrk) {
    this.fornavneMrkKode = forNavnMrk;
}

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(SimpleNavne.class.getSimpleName() + "[");
    builder.append(", forNavn=");
    builder.append(forNavn);
    builder.append(", fornavneMrkKode=");
    builder.append(fornavneMrkKode);
    builder.append("]");
    return builder.toString();
}

}

修改文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
<ns:PersonGivenName delete="true">010</ns:PersonGivenName>
<ns:PersonGivenNameMarkingStructure>
    <ns:MarkingCode>011</ns:MarkingCode>
</ns:PersonGivenNameMarkingStructure>
</ns:xml-fragment>

输出是:

SimpleNavne[forNavn=010, fornavneMrkKode=]

但应该是:

SimpleNavne[forNavn=010, fornavneMrkKode=011]

我是在做完全错误的事情还是 MOXy 不支持这种情况?

PS。我曾尝试使用 MOXy 2.1.1 和 2.2.0-M3,结果相同

于 2010-10-20T15:25:05.553 回答
1

您可以为此使用 XmlAdapter。适配器将转换为日期对象/从日期对象转换。适应的对象将具有两个属性。第一个使用@XmlValue 注释的日期属性和使用@XmlAttribute 注释的布尔属性。明天我会发布一个完整的例子。

我的课

我们将使用 @XmlJavaTypeAdapter 注释 startTime 属性。

import java.util.Date;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
class MyClass{

    private Date startTime;

    @XmlJavaTypeAdapter(DateAdapter.class)
    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

}

日期适配器

在 DateAdapter 类中,我们将在 Date 的实例和具有两个属性(日期和布尔值)的类之间进行转换。

import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<AdaptedDate, Date> {

    @Override
    public AdaptedDate marshal(Date date) throws Exception {
        AdaptedDate adaptedDate = new AdaptedDate();
        adaptedDate.setDate(date);
        return adaptedDate;
    }

    @Override
    public Date unmarshal(AdaptedDate adaptedDate) throws Exception {
        return adaptedDate.getDate();
    }

}

适应日期

这是我们日期信息的两个属性表示。它由 XmlAdapter 构造。

import java.util.Date;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

public class AdaptedDate {

    private Date date;

    @XmlValue
    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    @XmlAttribute
    public Boolean isDelete() {
        if(null == date) {
            return true;
        }
        return null;
    }

}

演示

以下演示代码可用于演示此代码:

import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(MyClass.class);
        System.out.println(jc);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        MyClass myClass1 = new MyClass();
        marshaller.marshal(myClass1, System.out);

        MyClass myClass2 = new MyClass();
        myClass2.setStartTime(new Date());
        marshaller.marshal(myClass2, System.out);
    }

}

进一步扩展:

  • 您可以通过添加 @XmlElement(name="foo") 注释来控制 startTime 属性的元素名称。
  • 您可以使用 MOXy 的 @XmlPath("foo/bar") 注释进一步控制 XML 表示,例如 @XmlPath()

有关更多信息,请参阅:

于 2010-10-19T21:59:45.047 回答
1

非常感谢您花时间在这方面。

作为对您上次回复的回应:JAXB/MOXy 可以将元素值和元素属性映射到同一个 POJO 字段吗?

我正在使用带有工厂配置的 jaxb.properties 文件。除了缺失值外,我的输出看起来几乎与您的相同:

21-10-2010 09:47:08 javax.xml.bind.ContextFinder find
FINE: Trying to locate com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 09:47:08 javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Ajour/ajourworkspace/jaxb-test-with-schema/target/classes/com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 09:47:08 javax.xml.bind.ContextFinder find
FINE:   found
21-10-2010 09:47:08 javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
21-10-2010 09:47:08 javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Users/jpedersen22/.m2/repository/org/eclipse/persistence/eclipselink/2.1.1/eclipselink-2.1.1.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
UNMARSHALL: DeletableString[delete=true, value=010]
SimpleNavne[forNavn=null, fornavneMrkKode=]

如您所见,MOXy 是使用中的 JAXB 实现。当不使用 DeleteStringAdapter 时,我还成功使用了 @XMLPath 注释。即,如果我从 getFornavn 中删除 @XmlJavaTypeAdapter(value = DeleteStringAdapter.class),那么我会得到以下信息:

21-10-2010 10:00:02 javax.xml.bind.ContextFinder find
FINE: Trying to locate com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 10:00:02 javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Ajour/ajourworkspace/jaxb-test-with-schema/target/classes/com/csc/cpr/ws/domain/jaxb.properties
21-10-2010 10:00:02 javax.xml.bind.ContextFinder find
FINE:   found
21-10-2010 10:00:02 javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
21-10-2010 10:00:02 javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Users/jpedersen22/.m2/repository/org/eclipse/persistence/eclipselink/2.1.1/eclipselink-2.1.1.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
SimpleNavne[forNavn=010, fornavneMrkKode=011]

关于 XML 010,在测试不同的场景(有或没有属性)时,它让我的生活更轻松。无论如何,然后我的 DeleteStringAdapter 让 delete 属性占据主导地位,这正是我想要的。

于 2010-10-21T08:02:34.713 回答
1

回答你的第二个问题:

当我运行您的代码(使用 EclipseLink 2.1.1)时,我得到以下输出:

20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder find
FINE: Trying to locate forum80/jaxb.properties
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder loadJAXBProperties
FINE: loading props from file:/C:/Workspaces/EclipseLink-Trunk/SCRATCH/bin/forum80/jaxb.properties
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder find
FINE:   found
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder safeLoadClass
FINE: Trying to load org.eclipse.persistence.jaxb.JAXBContextFactory
20-Oct-2010 2:50:46 PM javax.xml.bind.ContextFinder newInstance
FINE: loaded org.eclipse.persistence.jaxb.JAXBContextFactory from jar:file:/C:/Workspaces/EclipseLink-2.1/eclipselink.jar!/org/eclipse/persistence/jaxb/JAXBContextFactory.class
UNMARSHALL: DeletableString[delete=true, value=010]
SimpleNavne[, forNavn=null, fornavneMrkKode=011]

此结果与您所看到的不同:

SimpleNavne[forNavn=010, fornavneMrkKode=]

我们可能需要调整 XML 适配器中的逻辑来处理您的特定 XML 样本。在您的 XML 片段中,您指定 delete="true" 和映射到“forNavn”属性的“PersonGivenName”元素的值,在这种情况下,null 或 value 应该获胜吗?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:xml-fragment xmlns:ns="http://cpr.csc.com/navne">
    <ns:PersonGivenName delete="true">010</ns:PersonGivenName>
    <ns:PersonGivenNameMarkingStructure>
        <ns:MarkingCode>011</ns:MarkingCode>
    </ns:PersonGivenNameMarkingStructure>
</ns:xml-fragment>

如果使用 JAXB RI,我可以让“fornavneMrkKode”属性返回空字符串。您是否有可能没有正确指定 jaxb.properties 以使用 EclipseLink MOXy?

jaxb.properties 文件应与模型类位于同一包中,并具有以下条目:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

更新:

我已经通过电子邮件向您发送了我用来调试您的问题的 Java 项目,您介意运行它以查看是否得到相同的结果吗?希望您能得到相同的输出,我们可以找出导致错误行为的增量。

于 2010-10-20T19:04:05.427 回答
0

我成功地运行了你的项目!我还通过比较我们的项目找到了自己的问题。我已将 DeletableString 类放在单独的包中,但忘记添加 package-info.java 文件。添加具有以下内容的文件使整个工作正常:

@XmlSchema(namespace = "http://cpr.csc.com/navne", elementFormDefault = XmlNsForm.QUALIFIED)
package com.csc.cpr.ws.domain.customtypes;

import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

非常感谢您的帮助。它非常有价值

最好的问候,杰斯珀

于 2010-10-21T14:53:20.320 回答