package io.github.benas.easybatch.xml;

import io.github.benas.easybatch.core.api.Record;
import io.github.benas.easybatch.core.api.RecordReader;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndDocument;
import javax.xml.stream.events.XMLEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A record reader that reads xml records from an xml file.
 *
 * @author benas (md.benhassine@gmail.com)
 */
public class XmlRecordReader implements RecordReader {

    private static final Logger LOGGER = Logger.getLogger(XmlRecordReader.class.getSimpleName());

    /**
     * The root element name.
     */
    private String rootElementName;

    /**
     * The xml input file.
     */
    private String xmlFile;

    /**
     * The xml reader.
     */
    private XMLEventReader xmlEventReader;

    /**
     * The current record number.
     */
    private long currentRecordNumber;

    public XmlRecordReader(final String rootElementName, final String xmlFile) {
        this.rootElementName = rootElementName;
        this.xmlFile = xmlFile;
    }

    @Override
    public void open() throws Exception {
        currentRecordNumber = 0;
        xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(new FileInputStream(new File(xmlFile)));
    }

    @Override
    public boolean hasNextRecord() {
        try {
            while (!nextTagIsRootElementStart()) {
                XMLEvent xmlEvent = xmlEventReader.nextEvent();
                if (xmlEvent instanceof EndDocument) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "An exception occurred during checking the existence of next xml record", e);
            return false;
        }
    }

    @Override
    public Record readNextRecord() {
        StringBuilder stringBuilder = new StringBuilder("");
        try {
            while (!nextTagIsRootElementEnd()) {
                stringBuilder.append(xmlEventReader.nextEvent().toString());
            }
            //append root element end tag
            stringBuilder.append(xmlEventReader.nextEvent().toString());

            return new XmlRecord(++currentRecordNumber, stringBuilder.toString());
        } catch (Exception e) {
            throw new RuntimeException("An exception occurred during reading next xml record", e);
        }
    }

    @Override
    public Long getTotalRecords() {
        long totalRecords = 0;
        XMLEventReader totalRecordsXmlEventReader = null;
        try {
            totalRecordsXmlEventReader =
                    XMLInputFactory.newInstance().createXMLEventReader(new FileInputStream(new File(xmlFile)));
            XMLEvent event;
            while (totalRecordsXmlEventReader.hasNext()) {
                event = totalRecordsXmlEventReader.nextEvent();
                if (event.isStartElement() && event.asStartElement().getName().toString().equals(rootElementName)) {
                    totalRecords++;
                }
            }
        } catch (XMLStreamException e) {
            throw new RuntimeException("Unable to read data from xml file " + xmlFile, e);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("File not found " + xmlFile, e);
        } finally {
            if (totalRecordsXmlEventReader != null) {
                try {
                    totalRecordsXmlEventReader.close();
                } catch (XMLStreamException e) {
                    LOGGER.log(Level.SEVERE, "An exception occurred during closing xml total record reader", e);
                }
            }
        }
        return totalRecords;
    }

    @Override
    public String getDataSourceName() {
        return xmlFile;
    }

    @Override
    public void close() {
        try {
            xmlEventReader.close();
        } catch (XMLStreamException e) {
            throw new RuntimeException("An exception occurred during closing xml reader", e);
        }
    }

    /**
     * Utility method to check if the next tag matches a start tag of the root element.
     * @return true if the next tag matches a start element of the root element, false else
     * @throws Exception thrown if no able to peek the next xml element
     */
    private boolean nextTagIsRootElementStart() throws Exception {
        return xmlEventReader.peek().isStartElement() &&
               xmlEventReader.peek().asStartElement().getName().toString().equalsIgnoreCase(rootElementName);
    }

    /**
     * Utility method to check if the next tag matches an end tag of the root element.
     * @return true if the next tag matches an end tag of the root element, false else
     * @throws Exception thrown if no able to peek the next xml element
     */
    private boolean nextTagIsRootElementEnd() throws Exception {
        return xmlEventReader.peek().isEndElement() &&
                xmlEventReader.peek().asEndElement().getName().toString().equalsIgnoreCase(rootElementName);
    }

}

@@ -2040,32 +2040,49 @@
 readNextRecord()
+ throws Exception
  %7B%0A        Strin
@@ -2129,34 +2129,16 @@
 er(%22%22);%0A
-        try %7B%0A    
         
@@ -2170,28 +2170,24 @@
 entEnd()) %7B%0A
-    
             
@@ -2251,34 +2251,26 @@
 ));%0A        
- 
-   %7D%0A    
+%7D%0A
         //ap
@@ -2295,20 +2295,16 @@
 end tag%0A
-    
         
@@ -2369,28 +2369,24 @@
 );%0A%0A        
-    
 return new X
@@ -2448,149 +2448,8 @@
 ));%0A
-        %7D catch (Exception e) %7B%0A            throw new RuntimeException(%22An exception occurred during reading next xml record%22, e);%0A        %7D%0A
     
@@ -3150,35 +3150,33 @@
     
-throw new RuntimeException(
+LOGGER.log(Level.SEVERE, 
 %22Una
@@ -3215,32 +3215,57 @@
  + xmlFile, e);%0A
+            return null;%0A
         %7D catch 
@@ -3308,35 +3308,33 @@
     
-throw new RuntimeException(
+LOGGER.log(Level.SEVERE, 
 %22Fil
@@ -3354,32 +3354,57 @@
  + xmlFile, e);%0A
+            return null;%0A
         %7D finall
@@ -3906,86 +3906,15 @@
 e() 
-%7B%0A        try %7B%0A            xmlEventReader.close();%0A        %7D catch (XMLStream
+throws 
 Exce
@@ -3910,35 +3910,32 @@
 throws Exception
- e)
  %7B%0A            t
@@ -3933,103 +3933,31 @@
     
-    throw new RuntimeException(%22An exception occurred during closing xml reader%22, e);%0A        %7D
+xmlEventReader.close();
 %0A   

Improve XmlRecordReader implementation : when an XMLStreamException occurs, do not throw a RuntimeException (that cannot be handled correctly by the engine). Instead, log an error message and return an appropriate result.