Validate nested object from complex object using j

2019-04-12 21:22发布

问题:

I have a xml representation of object like OrderList (has list of) Orders and each order has a list of commodities.

I want to validate my commodities and if not valid I want to remove them from order. If all commodities are invalid then I remove the order from the orderlist.

I have been able to validate Orderlist

JAXBContext jaxbContext = JAXBContext.newInstance("com.jaxb");
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File(XSD));
JAXBSource source = new JAXBSource(jaxbContext, orderList);
Validator validator = schema.newValidator();
DataFeedErrorHandler handler = new DataFeedErrorHandler();
validator.setErrorHandler(handler);
validator.validate(source);

I am not able to find a way to validate commodities.

Something like

for(Order order: orderList){
    for(Commodity commodity: order.getCommodity()){
       if(!isCommodityValid(commodity)){
         // mark for removal
       }
    }
}

Any help would be greatly appreciated.

回答1:

TL;DR

You can do a dummy marshal and leverage the JAXB validation mechanisms rather than using the javax.xml.validation mechanisms directly.

LEVERAGING Marshaller.Listener & ValidationEventHandler (CommodityValidator)

For this example we will leverage aspects of Marshaller.Listener and ValidationEventHandler to accomplish the use case.

  • Marshal.Listener - This will be called for each object being marshalled. We can use it to cache the instance of Order that we may need to remove the instance of Commodity from.
  • ValidationEventHandler this will give us access to each problem occuring with validation during the marshal operation. For each problem it will be passed a ValidationEvent. This ValidationEvent will hold a ValidationEventLocator from which we can get the object that had the problem being marshalled.
import javax.xml.bind.*;

public class CommodityValidator extends Marshaller.Listener implements ValidationEventHandler {

    private Order order;

    @Override
    public void beforeMarshal(Object source) {
        if(source instanceof Order) {
            // If we are marshalling an Order Store It
            order = (Order) source;
        }
    }

    @Override
    public boolean handleEvent(ValidationEvent event) {
        if(event.getLocator().getObject() instanceof Commodity) {
            // If the Error was Caused by a Commodity Object Remove it from the Order
            order.setCommodity(null);
            return true;
        }
        return false;
    }

}

DEMO CODE

The following code can be run to prove that everything works.

import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.validation.*;
import org.xml.sax.helpers.DefaultHandler;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Orders.class);

        // STEP 1 - Build the Object Model
        Commodity commodity1 = new Commodity();
        commodity1.setId("1");
        Order order1 = new Order();
        order1.setCommodity(commodity1);

        Commodity commodityInvalid = new Commodity();
        commodityInvalid.setId("INVALID");
        Order order2 = new Order();
        order2.setCommodity(commodityInvalid);

        Commodity commodity3 = new Commodity();
        commodity3.setId("3");
        Order order3 = new Order();
        order3.setCommodity(commodity3);

        Orders orders = new Orders();
        orders.getOrderList().add(order1);
        orders.getOrderList().add(order2);
        orders.getOrderList().add(order3);

        // STEP 2 - Check that all the Commodities are Set
        System.out.println("\nCommodities - Before Validation");
        for(Order order : orders.getOrderList()) {
            System.out.println(order.getCommodity());
        }

        // STEP 3 - Create the XML Schema
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(new File("src/forum16953248/schema.xsd"));

        // STEP 4 - Perform Validation with the Marshal Operation
        Marshaller marshaller = jc.createMarshaller();

        // STEP 4a - Set the Schema on the Marshaller
        marshaller.setSchema(schema);

        // STEP 4b - Set the CommodityValidator as the Listener and EventHandler
        CommodityValidator commodityValidator = new CommodityValidator();
        marshaller.setListener(commodityValidator);
        marshaller.setEventHandler(commodityValidator);

        // STEP 4c - Marshal to Anything
        marshaller.marshal(orders, new DefaultHandler());

        // STEP 5 - Check that the Invalid Commodity was Removed
        System.out.println("\nCommodities - After Validation");
        for(Order order : orders.getOrderList()) {
            System.out.println(order.getCommodity());
        }
    }

}

OUTPUT

Below is the output from running the demo code. Node how after the marshal operation the invalid commodity was removed.

Commodities - Before Validation
forum16953248.Commodity@3bb505fe
forum16953248.Commodity@699c8551
forum16953248.Commodity@22f4bf02

Commodities - After Validation
forum16953248.Commodity@3bb505fe
null
forum16953248.Commodity@22f4bf02

XML SCHEMA (schema.xsd)

Below is the XML schema used for this example.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
    <element name="orders">
        <complexType>
            <sequence>
                <element name="order" minOccurs="0" maxOccurs="unbounded">
                    <complexType>
                        <sequence>
                            <element name="commodity">
                                <complexType>
                                    <attribute name="id" type="int"/>
                                </complexType>
                            </element>
                        </sequence>
                    </complexType>
                </element>
            </sequence>
        </complexType>
    </element>
</schema>

JAVA MODEL

Below is the object model I used for this example.

Orders

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Orders {

    private List<Order> orderList = new ArrayList<Order>();

    @XmlElement(name="order")
    public List<Order> getOrderList() {
        return orderList;
    }

}

Order

public class Order {

    private Commodity commodity;

    public Commodity getCommodity() {
        return commodity;
    }

    public void setCommodity(Commodity commodity) {
        this.commodity = commodity;
    }

}

Commodity

import javax.xml.bind.annotation.*;

public class Commodity {

    private String id;

    @XmlAttribute
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

}