eclipselink / Moxy: наследование и переименование атрибута в зависимости от типа - PullRequest
6 голосов
/ 20 января 2012

Я сталкиваюсь с проблемой маршаллинга / демаршаллинга, связанной с наследованием и полиморфизмом, с использованием реализации JAXB MOXy и файла привязок внешних метаданных.

У меня нет контроля над файлами XML или классами модели.* Внутри модели есть несколько классов, которые наследуют другие классы DTO.Вот пример среды, в которой я работаю. Этот пример приведен здесь только для некоторой синтаксической цели, реальная среда включает вложенное наследование, коллекции и т. Д.:

Вот класс, который будет унаследован

  class A {

        private String name;

        public String getName(){
              return name;
        }

        public void setName(String value){
              name = value;
        }

  } 

Вот один унаследованный класс

  class B extends A {

        private String attrFromB;

        public String getAttrFromB(){
              return attrFromB;
        }

        public void setAttrFromB(String value){
              attrFromB = value;
        }
  } 

И еще один

  class C extends A {

        private String attrFromC;

        public String getAttrFromC(){
              return attrFromC;
        }

        public void setAttrFromC(String value){
              attrFromC= value;
        }
  } 

Вот контейнерный класс

  class MyContainerClass{

        private A myObject;

        public A getMyObject(){
           return myObject;
        }

        public void setMyObject(A value){
           myObject = value;
        }
  }

Вот XMLчто он должен производить в случае MyContainer, содержащего A

  <MyContainer>
        <MyObject nameA="foo" />
  </MyContainer>

MyContainer, содержащего B

  <MyContainer>
        <MyObject nameB="foo" attrFromB="bar" />
  </MyContainer>

, и MyContainer, содержащего C

  <MyContainer>
        <MyObject nameC="foo" attrFromC="bar" />
  </MyContainer>

Так что вы уже можете видетьпроблемы на горизонте ...

Вот файл сопоставления, который я написал бы:

  <?xml version="1.0"?>
     <xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        package-name="com.test.example"
        version="2.1">  

        <java-type name="A" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameA" />
           </java-attributes>
        </java-type>  

        <java-type name="B" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <xml-see-also>
              com.test.example.A
           </xml.see.also>
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameB" />
              <xml-element java-attribute="attrFromB" xml-path="@attrFromB" />
           </java-attributes>
        </java-type>

        <java-type name="C" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <xml-see-also>
              com.test.example.A
           </xml.see.also>
           <java-attributes>
              <xml-element java-attribute="name" xml-path="@nameC" />
              <xml-element java-attribute="attrFromC" xml-path="@attrFromC" />
           </java-attributes>
        </java-type>

        <java-type name="MyContainer" xml-accessor-type="NONE">
           <xml-root-element name="MyContainer" />
           <java-attributes>
              <xml-element java-attribute="myObject" type="com.test.example.A" xml-path="MyObject" />
           </java-attributes>
        </java-type>

     </xml-bindings>

Первая проблема заключается в том, что если я связываю классы таким образом, я получаюследующее исключение:

  [Exception [EclipseLink-44] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DescriptorException
  Exception Description: Missing class indicator field from database row [UnmarshalRecord()].

1-й вопрос : Я понимаю, что это нормально, Jaxb нужен какой-то способ определить тип атрибута MyContaioner.myObject.Проблема в том, что у меня нет доступа к входящим XML-файлам, поэтому я не могу добавить к ним поля типа xsi: type.Есть ли способ определить класс на основе наличия в нем определенного атрибута?независимо от его стоимости.Если исходный xml содержит атрибут @attrFromC, я знаю, что объект должен иметь тип C. Если он содержит attrFromB, это B.


Вторая проблема заключается в том, что атрибут name несуществуют внутри B и C, поэтому jaxb игнорирует их.

  --Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it.
  --Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it.

2-й вопрос : Другая проблема заключается в том, что я не знаю, способен ли Jaxb переопределять имена атрибутов xml так, как ожидаетсявнутри файла XML (@nameA, @nameB и nameC все ссылаются на A.name), есть ли способ сделать это?

Заранее спасибо за ваше время.

1 Ответ

4 голосов
/ 20 января 2012

Ниже приведены ответы на ваши вопросы. Ответ на вопрос 2 также является ответом на вопрос 1.

<Ч />

1-й вопрос: я понимаю, что это нормально, Jaxb нужен какой-то способ определить тип атрибута MyContaioner.myObject. Эта проблема является то, что у меня нет доступа к входящим XML-файлам, поэтому я не могу добавить XSI: введите поля для них. Есть ли способ определить класс на основе наличие определенного атрибута в нем? независимо от его стоимости. Если исходный xml содержит атрибут @attrFromC, я знаю объект должен иметь тип C. Если он содержит attrFromB, это B.

Вы можете использовать расширение ClassExtractor в EclipseLink JAXB (MOXy) для этого варианта использования:

MyClassExtractor

A ClassExtractor - это некоторый код, который вы можете реализовать, чтобы помочь MOXy определить, какой класс он должен создавать. XPath передает вам Record, и вы можете запросить наличие атрибутов в текущем элементе, чтобы определить, какой класс следует создать.

package com.test.example;

import org.eclipse.persistence.descriptors.ClassExtractor;
import org.eclipse.persistence.sessions.*;

public class MyClassExtractor extends ClassExtractor{

    @Override
    public Class<?> extractClassFromRow(Record record, Session session) {
        if(null != record.get("@attrFromB")) {
            return B.class;
        } else if(null != record.get("@attrFromC")) {
            return C.class;
        } else {
            return A.class;
        }
    }

}

Метаданные (oxm.xml)

Вы можете настроить ClassExtractor с помощью аннотации @XmlClassExtractor. Вы также можете сделать это через внешний файл метаданных. Я включил вопрос, включенный в ваш вопрос, так:

<?xml version="1.0"?>
<xml-bindings 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="com.test.example"
    version="2.3">
    <java-types>
        <java-type name="A" xml-accessor-type="NONE">
           <xml-class-extractor class="com.test.example.MyClassExtractor"/>
           <xml-root-element name="MyObject" />
           <java-attributes>
              <xml-attribute java-attribute="name" name="nameA" />
           </java-attributes>
        </java-type>  
        <java-type name="B" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <java-attributes>
              <xml-attribute java-attribute="name" name="nameB" />
              <xml-attribute java-attribute="attrFromB"/>
           </java-attributes>
        </java-type>
        <java-type name="C" xml-accessor-type="NONE">
           <xml-root-element name="MyObject" />
           <java-attributes>
              <xml-attribute java-attribute="name" name="nameC" />
              <xml-attribute java-attribute="attrFromC"/>
           </java-attributes>
        </java-type>
        <java-type name="MyContainerClass" xml-accessor-type="NONE">
           <xml-root-element name="MyContainer" />
           <java-attributes>
              <xml-element java-attribute="myObject" name="MyObject" />
           </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Демо

Следующий демонстрационный код отменяет маршалирование каждого XML-документа из вашего вопроса и выводит тип, поддерживаемый свойством myObject:

package com.test.example;

import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>");
        MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
        System.out.println(myContainerA.getMyObject().getClass());

        StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>");
        MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
        System.out.println(myContainerB.getMyObject().getClass());

        StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>");
        MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
        System.out.println(myContainerC.getMyObject().getClass());
    }

}

выход

[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it.
[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it.
class com.test.example.A
class com.test.example.B
class com.test.example.C
<Ч />

2-й вопрос: Другая проблема в том, что я не знаю, является ли Jaxb способен переопределять имена атрибутов XML, как это ожидается внутри XML-файл (@nameA, @nameB и nameC все ссылаются на A.name), Есть ли способ сделать это?

Вы можете использовать XmlAdapter для этого вопроса. Этот подход также может быть использован для ответа на ваш первый вопрос:

AAdapter

package com.test.example;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class AAdapter extends XmlAdapter<AAdapter.AdaptedA, A> {

    @Override
    public AdaptedA marshal(A a) throws Exception {
        if(null == a) {
            return null;
        }
        AdaptedA adaptedA = new AdaptedA();
        if(a instanceof C) {
            C c = (C) a;
            adaptedA.nameC = c.getName();
            adaptedA.attrFromC = c.getAttrFromC();
        } else if(a instanceof B) {
            B b = (B) a;
            adaptedA.nameB = b.getName();
            adaptedA.attrFromB = b.getAttrFromB();
        } else if(a instanceof A) {
            adaptedA.nameA = a.getName();
        }
        return adaptedA;
    }

    @Override
    public A unmarshal(AdaptedA adaptedA) throws Exception {
        if(null == adaptedA) {
            return null;
        }
        if(null != adaptedA.attrFromC) {
            C c = new C();
            c.setName(adaptedA.nameC);
            c.setAttrFromC(adaptedA.attrFromC);
            return c;
        } else if(null != adaptedA.attrFromB) {
            B b = new B();
            b.setName(adaptedA.nameB);
            b.setAttrFromB(adaptedA.attrFromB);
            return b;
        } 
        A a = new A();
        a.setName(adaptedA.nameA);
        return a;
    }

    public static class AdaptedA {
        @XmlAttribute public String nameA;
        @XmlAttribute public String nameB;
        @XmlAttribute public String nameC;
        @XmlAttribute public String attrFromB;
        @XmlAttribute public String attrFromC;
    }

}

Метаданные (oxm-2.xml)

<?xml version="1.0"?>
<xml-bindings 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="com.test.example"
    version="2.3">
    <java-types>
        <java-type name="MyContainerClass" xml-accessor-type="NONE">
           <xml-root-element name="MyContainer" />
           <java-attributes>
              <xml-element java-attribute="myObject" name="MyObject">
                <xml-java-type-adapter value="com.test.example.AAdapter"/>
              </xml-element>
           </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo2

package com.test.example;

import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo2 {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm-2.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        StringReader aXml = new StringReader("<MyContainer><MyObject nameA='foo'/></MyContainer>");
        MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
        System.out.println(myContainerA.getMyObject().getClass());
        marshaller.marshal(myContainerA, System.out);

        StringReader bXml = new StringReader("<MyContainer><MyObject nameB='foo' attrFromB='bar'/></MyContainer>");
        MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
        System.out.println(myContainerB.getMyObject().getClass());
        marshaller.marshal(myContainerB, System.out);

        StringReader cXml = new StringReader("<MyContainer><MyObject nameC='foo' attrFromC='bar'/></MyContainer>");
        MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
        System.out.println(myContainerC.getMyObject().getClass());
        marshaller.marshal(myContainerC, System.out);
    }

}

выход

class com.test.example.A
<?xml version="1.0" encoding="UTF-8"?>
<MyContainer>
   <MyObject nameA="foo"/>
</MyContainer>
class com.test.example.B
<?xml version="1.0" encoding="UTF-8"?>
<MyContainer>
   <MyObject nameB="foo" attrFromB="bar"/>
</MyContainer>
class com.test.example.C
<?xml version="1.0" encoding="UTF-8"?>
<MyContainer>
   <MyObject nameC="foo" attrFromC="bar"/>
</MyContainer>
...