# Validierung mit JAXB2 ohne root-element



## squidy (15. Jul 2011)

Hallo, ich habe ein Problem mit der Validierung mit JAXB2 ... leider habe ich schon extrem viel Zeit damit verloren und wäre sehr froh wenn mir hier jemand helfen könnte... ;(

Und zwar habe ich folgendes xsd:

[XML]
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/Bookshop" xmlns:tns="http://www.example.org/Bookshop"
	elementFormDefault="qualified">

	<complexType name="creditCard">
		<sequence>
			<element name="number" type="string"></element>
			<element name="expiringYear">
				<simpleType>
					<restriction base="int">
						<pattern value="20[0-9][0-9]"></pattern>
					</restriction>
				</simpleType>
			</element>
			<element name="expiringMonth">
				<simpleType>
					<restriction base="int">
						<minInclusive value="1"></minInclusive>
						<maxInclusive value="12"></maxInclusive>
						<pattern value="[1-9]?[0-9]"></pattern>
					</restriction>
				</simpleType>
			</element>
			<element name="owner" type="string"></element>
		</sequence>
	</complexType>

</schema>
[/XML]

Wenn ich mit XMLBeans 2.5.0 die Klassen aus dem Schema generieren lasse, sieht der Code zur Validierung wie folgt aus:


```
import java.io.IOException;
import java.io.StringWriter;

import org.example.bookshop.CreditCard;
import org.junit.Assert;
import org.junit.Test;

public class XMLBeansValidationTest {

	@Test
	public void testValidation() {
		CreditCard creditCard = CreditCard.Factory.newInstance();
		creditCard.setExpiringMonth(1);
		creditCard.setExpiringYear(2113);
		creditCard.setNumber("1234-5678-9000-0000");
		creditCard.setOwner("Homer Simpson");

		StringWriter sw = new StringWriter();

		try {
			creditCard.save(sw);
		} catch (IOException e) {
			e.printStackTrace();
			Assert.fail();
		}

		System.out.println(sw.toString());

		boolean isCreditCardValid = creditCard.validate();
		Assert.assertTrue(isCreditCardValid);

	}
}
```

Dies ist sehr einfach und funktioniert auch einwandfrei. Die Ausgabe (d.h. so wie XMLBeans das XML intern darstellt) sieht so aus:

[XML]<xml-fragment>
<book:number xmlns:book="http://www.example.org/Bookshop">1234-5678-9000-0000</book:number>
<book:expiringYear xmlns:book="http://www.example.org/Bookshop">2113</book:expiringYear>
<book:expiringMonth xmlns:book="http://www.example.org/Bookshop">1</book:expiringMonth>
<bookwner xmlns:book="http://www.example.org/Bookshop">Homer Simpson</bookwner>
</xml-fragment>[/XML]

Es scheint also das XMLBeans hier einfach ein root-element hinzufügt und das ganze dann auch korrekt validieren kann. Der Test schlägt auch fehl weil 2113 bei expiringYear steht und nicht wie das Pattern im Schema verlangt etwas wie 2013.

Nun müssten wir das ganze noch mit JAXB2 irgendwie validieren können...und genau hier stehe ich an, d.h. es ist mir nicht möglich mit JAXB2 ein XML gegen das XSD validieren zu lassen. Egal was ich auch versuche, die Validierung schlägt immer fehl mit folgender Meldung:


```
[org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'creditCard'.]
```

Ein Beispiel wie ich die Validierung versucht habe durchzuführen sieht wie folgt aus:


```
import java.io.IOException;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.namespace.QName;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.example.bookshop.CreditCard;
import org.example.bookshop.ObjectFactory;
import org.junit.Assert;
import org.junit.Test;
import org.xml.sax.SAXException;

public class JAXB2ValidationTest {

	@Test
	public void testValidation() {
		ObjectFactory of = new ObjectFactory();

		CreditCard creditCard = of.createCreditCard();
		creditCard.setExpiringMonth(1);
		creditCard.setExpiringYear(2113);
		creditCard.setNumber("1234-5678-9000-0000");
		creditCard.setOwner("Homer Simpson");

		try {
			JAXBContext jc = JAXBContext.newInstance("org.example.bookshop");

			SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = sf.newSchema(ClassLoader.getSystemResource("bookshop.xsd"));
			Validator v = schema.newValidator();

			JAXBElement<CreditCard> jaxbElementCreditCard = new JAXBElement<CreditCard>(new QName("http://www.example.org/Bookshop",
					"creditCard"), CreditCard.class, creditCard);

			StringWriter sw = new StringWriter();
			Marshaller m = jc.createMarshaller();
			m.marshal(jaxbElementCreditCard, sw);
			System.out.println(sw.toString());

			v.validate(new JAXBSource(jc, jaxbElementCreditCard));

		} catch (JAXBException e) {
			e.printStackTrace();
			Assert.fail(e.getMessage());
		} catch (SAXException e) {
			e.printStackTrace();
			Assert.fail(e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
			Assert.fail(e.getMessage());
		}

	}
}
```

Und die Ausgabe von sw.toString() liefert folgendes Resultat:

[XML]<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<creditCard xmlns="http://www.example.org/Bookshop">
<number>1234-5678-9000-0000</number>
<expiringYear>2113</expiringYear>
<expiringMonth>1</expiringMonth>
<owner>Homer Simpson</owner>
</creditCard>
[/XML]

Kann mir jemand einen Tipp geben wie und ob es überhaupt möglich ist ein solches XML ohne wirkliches root-element mithilfe von JAXB2 zu validieren?!


----------



## Noctarius (15. Jul 2011)

Du hast doch ein Root-Element "creditCard" oder durch JAXB erstellt "xml-fragment".

Ein XML hat IMMER! ein Root-Element.


----------



## squidy (15. Jul 2011)

Ja, eigentlich ist da schon ein Root-Element - aber trotzdem funktioniert die Validierung nicht und ich hab keine Ahnung wie ich diese Validierung hinbekomme. Wie gesagt funktioniert's mit XMLBeans ohne Probleme, aber bei JAXB2?!

Das mit dem Root-Element meinte ich eigentlich das die Annotation @XMLRootElement bei der Klasse die ich validieren will, fehlt. D.h. CreditCard.java hat keine solche Annotation.


```
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.2-hudson-jaxb-ri-2.2-63- 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2011.07.15 at 12:41:35 PM MESZ 
//


package org.example.bookshop;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for creditCard complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType name="creditCard">
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="number" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *         &lt;element name="expiringYear">
 *           &lt;simpleType>
 *             &lt;restriction base="{http://www.w3.org/2001/XMLSchema}int">
 *               &lt;pattern value="20[0-9][0-9]"/>
 *             &lt;/restriction>
 *           &lt;/simpleType>
 *         &lt;/element>
 *         &lt;element name="expiringMonth">
 *           &lt;simpleType>
 *             &lt;restriction base="{http://www.w3.org/2001/XMLSchema}int">
 *               &lt;minInclusive value="1"/>
 *               &lt;maxInclusive value="12"/>
 *               &lt;pattern value="[1-9]?[0-9]"/>
 *             &lt;/restriction>
 *           &lt;/simpleType>
 *         &lt;/element>
 *         &lt;element name="owner" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "creditCard", propOrder = {
    "number",
    "expiringYear",
    "expiringMonth",
    "owner"
})
public class CreditCard {

    @XmlElement(required = true)
    protected String number;
    protected int expiringYear;
    protected int expiringMonth;
    @XmlElement(required = true)
    protected String owner;

    /**
     * Gets the value of the number property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getNumber() {
        return number;
    }

    /**
     * Sets the value of the number property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setNumber(String value) {
        this.number = value;
    }

    /**
     * Gets the value of the expiringYear property.
     * 
     */
    public int getExpiringYear() {
        return expiringYear;
    }

    /**
     * Sets the value of the expiringYear property.
     * 
     */
    public void setExpiringYear(int value) {
        this.expiringYear = value;
    }

    /**
     * Gets the value of the expiringMonth property.
     * 
     */
    public int getExpiringMonth() {
        return expiringMonth;
    }

    /**
     * Sets the value of the expiringMonth property.
     * 
     */
    public void setExpiringMonth(int value) {
        this.expiringMonth = value;
    }

    /**
     * Gets the value of the owner property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getOwner() {
        return owner;
    }

    /**
     * Sets the value of the owner property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setOwner(String value) {
        this.owner = value;
    }

}
```

Und vielleicht suche ich ja am falschen Ort und müsste eigentlich xerces das noch irgendwie beibringen?!


```
Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'creditCard'.
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1916)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:705)
	at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.startElement(ValidatorHandlerImpl.java:550)
	at org.xml.sax.helpers.XMLFilterImpl.startElement(XMLFilterImpl.java:527)
	at com.sun.xml.internal.bind.v2.runtime.output.SAXOutput.endStartTag(SAXOutput.java:113)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.endAttributes(XMLSerializer.java:274)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:663)
	at com.sun.xml.internal.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:141)
	at com.sun.xml.internal.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:116)
	at com.sun.xml.internal.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:304)
	at com.sun.xml.internal.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:311)
	at com.sun.xml.internal.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:61)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
```

Also wie validiere ich das nun...?!


----------



## sooney (15. Jul 2011)

Hi,


> Das mit dem Root-Element meinte ich eigentlich das die Annotation @XMLRootElement bei der Klasse die ich validieren will, fehlt. D.h. CreditCard.java hat keine solche Annotation.



Die Annottation fehlt weil du in deinem Schema kein root-Element deklariert hast.
siehe auch deine Fehlermeldung:
[org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'creditCard'.]

Du definierst nur das Element "creditCard".

Du musst in dein schema das Element "creditCard" noch einfügen z.b.:

[XML]<xs:element name="creditCardElement" type="creditCard"/>[/XML]


Grüße sooney


----------



## squidy (15. Jul 2011)

Ich habe das XSD und den Test angepasst:

[XML]...<element name="creditCardElement" type="tns:creditCard" />

	<complexType name="creditCard">
		<sequence>
...[/XML]


```
...
//			JAXBElement<CreditCard> jaxbElementCreditCard = new JAXBElement<CreditCard>(new QName("http://www.example.org/Bookshop",
			//					"creditCard"), CreditCard.class, creditCard);

			JAXBElement<CreditCard> jaxbElementCreditCard = of.createCreditCardElement(creditCard);

			StringWriter sw = new StringWriter();
			Marshaller m = jc.createMarshaller();
			m.setProperty(Marshaller.JAXB_FRAGMENT, true);
			m.marshal(jaxbElementCreditCard, sw);
			System.out.println(sw.toString());

			v.validate(new JAXBSource(jc, jaxbElementCreditCard));
...
```

Die Fehlermeldung die jetzt kommt ist auch korrekt, so sollte es sein;


```
[org.xml.sax.SAXParseException: cvc-pattern-valid: Value '2113' is not facet-valid with respect to pattern '20[0-9][0-9]' for type '#AnonType_expiringYearcreditCard'.]
```

So funktioniert es...aber leider ist dies nur ein Beispiel bei dem ich die Schnittstellen ohne Probleme anpassen kann. Beim richtigen Projekt bekommen wir die Schnittstellen und ich habe keine Möglichkeit die XSD's zu verändern - dort ist der ComplexType den ich validieren möchte irgendwo im Baum versteckt...

Gibt es hier irgendeine Möglichkeit das ganze auszutricksen damit er doch einfach validieren kann? Vielleicht so wie xmlbeans das eben macht, d.h. ein xml-fragment drumherum, und den ComplexType auch ohne Element einfach validiert? :bahnhof:

Ich bin dankbar für jede Hilfe!


----------



## sooney (15. Jul 2011)

Also prinzipiell wird immer das ganze Schema validiert, heißt ja auch Schemavalidierung.

Warum willst du Complex-Elemente validieren die nicht benutzt werden ? Ein Schema für ein Service / Schnittstelle sollte immer valide sein,d.h. es hat ein root-element. 

Kannst du mir ein Beispiel nennen wo das einen Sinn macht ?

Grüße sooney


----------



## squidy (15. Jul 2011)

Ich hätte auch lieber wenn wir an den Stellen den ganzen Baum hätten und wirklich alles validieren könnten. 

Es ist aber so das dies ein Webservice ist den wir ansprechen und sozusagen unsere Businesslogik stellt nur einzelne Objekte her oder nimmt diese entgegen - leider nicht den ganzen Request oder Response in welchem die Objekte eingebettet sind.

Somit erhält die Businesslogik halt nur das Objekt CreditCard, die Logik macht dann ein paar Änderungen und spricht auch Umsysteme an, d.h. man will sicherstellen dass das Objekt CreditCard auch wirklich valide ist nachdem man es verändert hat um schon in der BL zu reagieren.

Ich weiss das es sich nun streiten lässt ob die Architektur so sinnvoll ist und die Validierung von Schnittstellen auch zur Aufgabe der BL gehört...

Wir sind daran die XMLBeans abzulösen und nach JAXB2 migrieren, bei XMLBeans hat diese Art von Validierung geklappt, nur JAXB2 macht uns da jetzt Probleme.

Was wir quasi tun wollen ist eine Validierung eines subknoten...


----------



## squidy (15. Jul 2011)

Vielleicht noch als Hinweis, bei JAXB1 gab's die Möglichkeit einfach ein Validate zu machen so wie es bei den XMLBeans in etwa der Fall war;

Validator (Java Platform SE 6)

Hier steht;



> On-Demand Validation
> This form of validation enables a client application to receive information about validation errors and warnings detected in the Java content tree. At any point, client applications can call the Validator.validate method on the Java content tree (or any sub-tree of it). All JAXB 1.0 Providers are required to support this operation.



Aber dieser Weg der Validierung ist deprecated...


----------



## sooney (15. Jul 2011)

reden wir jetzt über JAXB-Elemente also Java-Objekte die du nach dem Schema validieren willst, dann hab ich dich falsch verstanden, sry. 
Versuch es in diesem Fall mal mit einem Marshall-Listener / Unmarschall-Listener, dort kannst du vor dem verabeiten deine Daten validieren und auf Fehler frühzeitig reagieren.


----------



## Wildcard (15. Jul 2011)

Muss es denn JAXB sein?
Falls nein, mit EMF brauchst du das XML gar nicht zu erzeugen bevor du es validieren kannst. Die Objekte können direkt im Speicher gegen die im XSD definierten Constraints validiert werden und daher brauchst du auch keinen künstlichen Root Knoten.


----------



## Noctarius (16. Jul 2011)

Falls Eure Businesslogik in einem ApplicationServer (JBoss oder so) läuft könnte man auch einfach einen Validator nach JSR 303 nutzen und so die Werte im Bean validieren lassen. Es gibt auch mehrere Implementierungen davon, z.B. Hibernate Validator.

Falls ihr Tomcat oder so nutzt kannst du den Hibernate Validator auch einfach in die Dependencies aufnehmen, nur dann muss man den Validator von Hand bentuzen 

Using Bean Validation - The Java EE 6 Tutorial


----------

