Search code examples
javajaxbjaxb2jaxb2-basics

Validate nested object from complex object using jaxb


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.


Solution

  • 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;
        }
    
    }