No @XmlRootElement generado por JAXB

209

Estoy tratando de generar clases Java a partir de FpML (Lenguaje de marcado de productos financieros) versión 4.5. Se genera una tonelada de código, pero no puedo usarlo. Tratando de serializar un documento simple me sale esto:

javax.xml.bind.MarshalException
  - with linked exception: [com.sun.istack.SAXException2: unable
  to marshal type
  "org.fpml._2008.fpml_4_5.PositionReport"
  as an element because it is missing an
  @XmlRootElement annotation]

De hecho, ninguna clase tiene la anotación @XmlRootElement, entonces, ¿qué puedo estar haciendo mal? Estoy apuntando xjc (JAXB 2.1) a fpml-main-4-5.xsd, que luego incluye todos los tipos.

robinr
fuente

Respuestas:

261

Para unir lo que otros ya han dicho o insinuado, las reglas por las cuales JAXB XJC decide si se debe colocar la @XmlRootElementanotación en una clase generada no son triviales ( vea este artículo ).

@XmlRootElementexiste porque el tiempo de ejecución JAXB requiere cierta información para ordenar / desunir un objeto dado, específicamente el nombre del elemento XML y el espacio de nombres. No puedes pasar cualquier objeto viejo al Marshaller.@XmlRootElementproporciona esta información

Sin embargo, la anotación es solo una conveniencia: JAXB no lo requiere. La alternativa a es utilizar JAXBElementobjetos de contenedor, que proporcionan la misma información que@XmlRootElement , pero en forma de objeto, en lugar de una anotación.

Sin embargo, JAXBElement objetos son difíciles de construir, ya que necesita conocer el nombre del elemento XML y el espacio de nombres, lo que la lógica empresarial generalmente no conoce.

Afortunadamente, cuando XJC genera un modelo de clase, también genera una clase llamada ObjectFactory. Esto se debe en parte a la compatibilidad con versiones anteriores de JAXB v1, pero también es un lugar para que XJC coloque métodos de fábrica generados que creen JAXBElementenvoltorios alrededor de sus propios objetos. Maneja el nombre XML y el espacio de nombres por usted, por lo que no necesita preocuparse por eso. Solo necesita mirar a través de los ObjectFactorymétodos (y para un esquema grande, puede haber cientos de ellos) para encontrar el que necesita.

skaffman
fuente
15
Solución de caso especial: cuando puede modificar el xsd utilizado para la generación de clases: después de leer el enlace proporcionado en esta respuesta, la solución en mi caso fue modificar el archivo xsd utilizado para generar las clases: cambié la definición del elemento raíz a un definición en línea en lugar de utilizar la referencia a un tipo definido por separado. Esto permite que JAXB establezca este elemento como @XmlRootElement, lo que no fue posible con el elementType que se usó anteriormente para el elemento raíz.
Arthur
2
<scowl> cambiar el elemento raíz para que sea de tipo en línea, sin embargo, hace que todas las clases sean clases internas del tipo raíz. Además, incluso si el tipo de elemento raíz se define DESPUÉS del elemento raíz en sí (aparentemente permitido por el esquema), JAXB aún no realizará anotaciones con @XmlRootElement.
Pawel Veselov
10
es decir, new ObjectFactory().createPositionReport(positionReport)regresaJAXBElement<PositionReport>
vikingsteve
17
¿Qué sucede si el método ObjectFactory generado no crea un método que envuelva el argumento en a JXBElement? En mi caso, el método de fábrica es 0-arity y solo devuelve un newobjeto. (¿Por qué algunas clases reciben ayudantes del contenedor JAXBElement y otras no?) Supongo que en ese caso debemos crear el contenedor nosotros mismos.
Carl G
1
@CarlG Estoy en la misma situación: no aparece XmlRootElement ni JAXBElement en mis clases. ¿Has encontrado una solución para este caso?
Mickael Marrache
68

Esto se menciona en la parte inferior de la publicación del blog ya vinculada anteriormente, pero esto funciona como un placer para mí:

Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(new JAXBElement<MyClass>(new QName("uri","local"), MyClass.class, myClassInstance), System.out);
Escarcho
fuente
Prefiero la respuesta marcada, pero esto también funciona para mí.
Pedro Dusso
1
¿Qué hay jcen el fragmento anterior?
Arun
3
@ArunRaj es la clase JAXBContext
Gurnard
51

Como se insinuó en una de las respuestas anteriores, no obtendrá un XMLRootElement en su elemento raíz si en el XSD su tipo se define como un tipo con nombre, ya que ese tipo con nombre podría usarse en cualquier otro lugar de su XSD. Intente convertirlo en un tipo anónimo, es decir, en lugar de:

<xsd:element name="myRootElement" type="MyRootElementType" />

<xsd:complexType name="MyRootElementType">
...
</xsd:complexType>

tu tendrías:

<xsd:element name="myRootElement">
    <xsd:complexType>
    ...
    <xsd:complexType>
</xsd:element>
Matthew Wise
fuente
1
Eso no es cierto para mí. Mi tipo es anónimo (incrustado dentro de mi elemento raíz) y no se genera ninguna anotación XmlRootElement. ¿Alguna idea?
Mickael Marrache
38

@XmlRootElement no es necesario para desmarcar, si uno usa la forma de 2 parámetros de Unmarshaller # unmarshall.

Entonces, si en lugar de hacer:

UserType user = (UserType) unmarshaller.unmarshal(new StringReader(responseString));

uno debe hacer:

JAXBElement<UserType> userElement = unmarshaller.unmarshal(someSource, UserType.class);
UserType user = userElement.getValue();

El último código no requerirá la anotación @XmlRootElement en el nivel de clase UserType.

Sayantam
fuente
2
¿Conoces una forma igualmente elegante de ordenar un objeto que no tiene XmlRootElement, sin envolverlo en un JAXBElement como lo mencionan skaffman, Gurnard et al?
Chris
44
¡+1 funciona perfectamente! Una edición para mayor claridad ... En su solución, 'someSource' es un término muy vago. Para elaborar: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (nuevo StreamSource (nuevo archivo ("some.xml")), TargetClazz.class);
supernova
44
Elaboración adicional de 'someSource':String pathname = "file.xml"; InputStream stream = new FileInputStream(pathname); JAXBContext jaxbContext = JAXBContext.newInstance(UserType.class); Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLEventReader someSource = factory.createXMLEventReader(stream); JAXBElement<UserType> userElement = jaxbUnmarshaller.unmarshal(someSource, UserType.class); UserType user = userElement.getValue();
Steve Pitchers
21

La respuesta de Joe (Joe 26 de junio de 2009 a las 17:26) lo hace por mí. La respuesta simple es que la ausencia de una anotación @XmlRootElement no es un problema si reúne un JAXBElement. Lo que me confundió es que ObjectFactory generado tiene 2 métodos createMyRootElement: el primero no toma parámetros y proporciona el objeto sin envolver, el segundo toma el objeto sin envolver y lo devuelve envuelto en un JAXBElement, y calcula que JAXBElement funciona bien. Aquí está el código básico que utilicé (soy nuevo en esto, así que disculpas si el código no está formateado correctamente en esta respuesta), en gran parte del texto del enlace :

ObjectFactory objFactory = new ObjectFactory();
MyRootElement root = objFactory.createMyRootElement();
...
// Set root properties
...
if (!writeDocument(objFactory.createMyRootElement(root), output)) {
    System.err.println("Failed to marshal XML document");
}
...

private boolean writeDocument(JAXBElement document, OutputStream output) {

  Class<?> clazz = document.getValue().getClass();
  try {
    JAXBContext context =
        JAXBContext.newInstance(clazz.getPackage().getName());
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.marshal(document, output);
    return true;

  } catch (JAXBException e) {
    e.printStackTrace(System.err);
    return false;
  }
}
Yaqoob
fuente
2
Tengo un caso en el que mi clase ObjectFactory solo define métodos que devuelven instancias regulares y no instancias JAXBElement ...
Mickael Marrache
20

Puede solucionar este problema utilizando el enlace de ¿Cómo generar las clases @XmlRootElement para los tipos base en XSD? .

Aquí hay un ejemplo con Maven

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxb2-maven-plugin</artifactId>
            <version>1.3.1</version>
            <executions>
                <execution>
                    <id>xjc</id>
                    <goals>
                        <goal>xjc</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <schemaDirectory>src/main/resources/xsd</schemaDirectory>
                <packageName>com.mycompany.schemas</packageName>
                <bindingFiles>bindings.xjb</bindingFiles>
                <extension>true</extension>
            </configuration>
        </plugin>

Aquí está el binding.xjbcontenido del archivo

<?xml version="1.0"?>
<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
              xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc"
              jxb:extensionBindingPrefixes="xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <jxb:bindings schemaLocation="path/to/myschema.xsd" node="/xs:schema">
        <jxb:globalBindings>
            <xjc:simple/>
        </jxb:globalBindings>
    </jxb:bindings>
</jxb:bindings>
Olivier.Roger
fuente
3
De hecho, usar <xjc: simple> en el archivo binding.xjb fue el truco. Impresionante solución si no quieres cambiar tu código de clasificación o tu WSDL. Tenga en cuenta que xjc: simple genera diferentes nombres de métodos (plural) para los captadores de colección (getOrders en lugar de getOrder, por ejemplo)
dvtoever
10

Como sabes, la respuesta es usar ObjectFactory (). Aquí hay una muestra del código que funcionó para mí :)

ObjectFactory myRootFactory = new ObjectFactory();

MyRootType myRootType = myRootFactory.createMyRootType();

try {

        File file = new File("./file.xml");
        JAXBContext jaxbContext = JAXBContext.newInstance(MyRoot.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

        //output pretty printed
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        JABXElement<MyRootType> myRootElement = myRootFactory.createMyRoot(myRootType);

        jaxbMarshaller.marshal(myRootElement, file);
        jaxbMarshaller.marshal(myRootElement, System.out);

    } catch (JAXBException e) {
        e.printStackTrace();
    }
Shehaaz
fuente
a su punto ... ¿cómo uso los métodos JAXBElement <?> create ... () de ObjectFactory para elementos anidados? es decir: <SOAP-ENV: Encabezado> <wsse: Seguridad> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Security> </ SOAP-ENV: Header> Obtengo: "no se puede ordenar el tipo" UsernameTokenType " como elemento porque le falta una anotación @XmlRootElement "
Angelina
6

Tampoco funciona para nosotros. Pero sí encontramos un artículo ampliamente citado que agrega ALGUNOS antecedentes ... Lo vincularé aquí por el bien de la siguiente persona: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html

mcherm
fuente
Esto funcionó bien para mí, gracias. También descubrí que estaba ordenando el objeto JAXB incorrecto (no la raíz como pensaba) en el proceso de pasar por esto. Olvidé crear un JAXBElement e intentaba ordenar solo el objeto devuelto de la clase ObjectFactory que había obtenido del enlace. Esto básicamente se ocupó del problema por completo (en caso de que alguien más se encuentre con el mismo problema).
Joe Bane
1
404: "Lamentamos que el sitio java.net se haya cerrado. La mayoría de los proyectos de código abierto alojados anteriormente en java.net han sido reubicados".
Tristan
6

Después de buscar durante dos días, encontré la solución para el problema. Puede usar la clase ObjectFactory para solucionar las clases que no tienen @XmlRootElement . ObjectFactory ha sobrecargado los métodos para envolverlo alrededor de JAXBElement.

Método 1 realiza la creación simple del objeto.

Método: 2 envolverá el objeto con @JAXBElement .

Siempre use Método: 2 para evitar javax.xml.bind.MarshalException: a la excepción vinculada le falta una anotación @XmlRootElement.

Encuentre el código de muestra a continuación

Método: 1 realiza la creación simple del objeto

public GetCountry createGetCountry() {
        return new GetCountry();
    }

Método: 2 envolverá el objeto con @JAXBElement .

 @XmlElementDecl(namespace = "my/name/space", name = "getCountry")
 public JAXBElement<GetCountry> createGetCountry(GetCountry value) {
        return new JAXBElement<GetCountry>(_GetCountry_QNAME, GetCountry.class, null, value);
    }

Código de trabajo de muestra:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
WebServiceTemplate springWSTemplate = context.getBean(WebServiceTemplate.class);

GetCountry request = new GetCountry();
request.setGuid("test_guid");

JAXBElement<GetCountryResponse> jaxbResponse = (JAXBElement<GetCountryResponse>)springWSTemplate .marshalSendAndReceive(new ObjectFactory().createGetCountry(request));

GetCountryResponse response = jaxbResponse.getValue();
prasadg
fuente
¡Gracias por dar el código de referencia con la plantilla de servicio web de Spring, ya que estaba luchando por resolverlo durante bastante tiempo!
RRR_J
5

¡En caso de que mi experiencia con este problema le dé a alguien un Eureka! momento .. Agregaré lo siguiente:

También estaba teniendo este problema, cuando usaba un archivo xsd que había generado usando la opción de menú "Generar xsd desde el documento de instancia" de IntelliJ.

Cuando acepté todos los valores predeterminados de esta herramienta, generó un archivo xsd que cuando se usaba con jaxb, generaba archivos java sin @XmlRootElement . En el tiempo de ejecución, cuando intenté reunir, obtuve la misma excepción que se discutió en esta pregunta.

Regresé a la herramienta IntellJ y vi que la opción predeterminada en el menú desplegable "Desgin Type" (que por supuesto no entendí ... y aún no lo sé si soy sincero) fue:

Tipo de diseño:

"elementos locales / tipos complejos globales"

Cambié esto a

"elementos / tipos locales"

, ahora generó un (sustancialmente) xsd diferente, que produjo el @XmlRootElementcuando se usa con jaxb. No puedo decir que entiendo los entresijos, pero funcionó para mí.

johnm
fuente
4

Los contenedores JAXBElement funcionan para casos en los que @XmlRootElementJAXB no genera ninguno . Estos contenedores están disponibles en la ObjectFactoryclase generada por maven-jaxb2-plugin. Por ejemplo:

     public class HelloWorldEndpoint {
        @PayloadRoot(namespace = NAMESPACE_URI, localPart = "person")
        @ResponsePayload
        public JAXBElement<Greeting> sayHello(@RequestPayload JAXBElement<Person> request) {

        Person person = request.getValue();

        String greeting = "Hello " + person.getFirstName() + " " + person.getLastName() + "!";

        Greeting greet = new Greeting();
        greet.setGreeting(greeting);

        ObjectFactory factory = new ObjectFactory();
        JAXBElement<Greeting> response = factory.createGreeting(greet);
        return response;
      }
 }
zer0Id0l
fuente
3

¿Intentaste cambiar tu xsd así?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>
Tony
fuente
Esto funcionó para mí con JDK 1.7u71. Xjc asigna un elemento de nivel superior a @XmlRootElement. Inicialmente solo tenía un tipo complejo de nivel superior. Tener que envolver en un JAXBElement es simplemente feo.
Serge Merzliakov
1

Para solucionarlo, debe configurar un enlace xml antes de compilar con wsimport, estableciendo generateElementProperty como falso.

     <jaxws:bindings wsdlLocation="LOCATION_OF_WSDL"
      xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
      xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
         <jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
    <jaxws:bindings  node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='NAMESPACE_OF_WSDL']">
      <jxb:globalBindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xjc:generateElementProperty>false</xjc:generateElementProperty> 
      </jxb:globalBindings>
  </jaxws:bindings>
</jaxws:bindings>
Leandruol
fuente
la etiqueta de envoltura debería ser<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi
0

El tema es bastante antiguo pero aún relevante en contextos empresariales empresariales. Traté de evitar tocar los xsds para actualizarlos fácilmente en el futuro. Aquí están mis soluciones.

1. Principalmente xjc:simplees suficiente

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jxb:bindings version="2.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc">

    <jxb:globalBindings>
        <xjc:simple/> <!-- adds @XmlRootElement annotations -->
    </jxb:globalBindings>

</jxb:bindings>

En su mayoría, creará XmlRootElements para importar definiciones xsd.

2. Divide tus jaxb2-maven-pluginejecuciones

He encontrado que hace una gran diferencia si intenta generar clases a partir de múltiples definiciones xsd en lugar de una definición de ejecución por xsd.

Entonces, si tiene una definición con múltiples <source>, intente dividirlas:

          <execution>
            <id>xjc-schema-1</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition1/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

          <execution>
            <id>xjc-schema-2</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition2/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

El generador no detectará el hecho de que una clase podría ser suficiente y, por lo tanto, creará clases personalizadas por ejecución. Y eso es exactamente lo que necesito;).

judomu
fuente