[Batchserver-cvs] batchserver/src/org/jmonks/batch/io/xml package.html, NONE, 1.1 PrettyXMLIndentat
Brought to you by:
suresh_pragada
From: Suresh <sur...@us...> - 2006-09-15 20:08:18
|
Update of /cvsroot/batchserver/batchserver/src/org/jmonks/batch/io/xml In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv20799 Added Files: package.html PrettyXMLIndentationEngine.java sample-xml-file-spec.xml sample-xml-file.xml XMLFileReader.java XMLFileSpec.java XMLFileWriter.java XMLIndentationEngine.java XMLRecordSpec.java Log Message: no message --- NEW FILE: XMLIndentationEngine.java --- /* * XMLIndentationEngine.java * * Created on June 12, 2006, 10:01 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; /** * <p> * XMLIndentationEngine returns the indentation information to be written to the stream * based on events received. It receives the events before start and end tags being written * to the stream. * * @author Suresh Pragada * @version 1.0 * @since 1.0 * </p> */ public interface XMLIndentationEngine { /** * Returns the indentation string needs to be written to tbe stream before * the start element being written. */ public String startElement(); /** * Returns the indentation string needs to be written to tbe stream before * the element element being written. */ public String endElement(); } --- NEW FILE: XMLFileReader.java --- /* * XMLFileReader.java * * Created on June 2, 2006, 11:05 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Characters; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.apache.log4j.Logger; import org.jmonks.batch.io.FileReader; import org.jmonks.batch.io.RecordType; import org.jmonks.batch.io.FileParseException; import org.jmonks.batch.io.FileSpec; import org.jmonks.batch.io.ReaderRecord; /** * <p> * XMLFileReader reads the specified xml file according to the given file spec * and returns the recrods on the needed basis. It finds the record location * using the xpath given in each xml record spec and loads all the child elements * as a fields. Field values should be accessed using the element names. * If element is a complex element, returned value will be a reader record and * it again follow the same convention. If element is repeated more than once, * the value will be loaded as a list. ReaderRecord returned by this reader will * have additional methods used to read the data accurately. To find out how to read * each record from the file and to read the each field from the record, refer to the * package javadoc. * </p> * * @author Suresh Pragada * @version 1.0 * @since 1.0 */ public class XMLFileReader extends FileReader { /** * File spec to read the file. */ protected XMLFileSpec fileSpec=null; /** * XML Event reader reads and returns everything as events. */ private XMLEventReader reader=null; /** * Tracks where are we in xml file. */ private String xpath=""; private static Logger logger=Logger.getLogger(XMLFileReader.class); /** * Constructs and initializes the XML File reader. * * @param fileInputStream Input stream to the file. * @param fileSpec File spec to be used to read the file. * * @throws org.jmonks.batch.io.FileParseException If root element doesnt match * with the element specified in file spec and problems to initializes the reader. */ public XMLFileReader(InputStream fileInputStream,FileSpec fileSpec) { this(new InputStreamReader(fileInputStream),fileSpec); } /** * Constructs and initializes the XML File reader. * * @param reader Reader to the file. * @param fileSpec File spec to be used to read the file. * * @throws org.jmonks.batch.io.FileParseException If root element doesnt match * with the element specified in file spec and problems to initializes the reader. */ public XMLFileReader(Reader reader,FileSpec fileSpec) { logger.trace("Entering XMLFileReader constructor"); this.fileSpec=(XMLFileSpec)fileSpec; try { XMLInputFactory inputFactory=XMLInputFactory.newInstance(); this.reader=inputFactory.createXMLEventReader(reader); logger.debug("Created the XML Event reader"); if(this.validateRootElement()) xpath="/"+this.fileSpec.getRootElement(); else { this.reader.close(); throw new FileParseException("Unexpected root element found. Expecting the root element " + this.fileSpec.getRootElement()); } logger.debug("Validate the root tag of the file"); } catch(XMLStreamException exception) { exception.printStackTrace(); this.reader=null; logger.fatal("Exception while initializing the xml stream reader. Message = " + exception.getMessage(),exception); throw new FileParseException("Exception while initializing the xml stream reader. Message = " + exception.getMessage()); } logger.trace("Exiting XMLFileReader constructor"); } /** * Gets the next available record from the file. If file doesnt have any more * records, it returns null. * * @return Returns the next available record. * * @throws org.jmonks.batch.io.FileParseException Problems getting the next record. * */ public ReaderRecord getNextRecord() { logger.trace("Entering getNextRecord"); XMLReaderRecord readerRecord=null; if(this.reader==null) return readerRecord; else { try { /** * Read events until the xpath matches with any of the record specs xpath. */ while(reader.hasNext()) { XMLEvent event=reader.nextEvent(); if(event.isStartElement()) { StartElement startElement=(StartElement)event; String startElementName=startElement.getName().getLocalPart(); xpath=xpath+"/"+startElementName; XMLRecordSpec recordSpec=this.getRecordSpec(); if(recordSpec!=null) { logger.trace("Found configured " + xpath); readerRecord=retrieveRecord(recordSpec,startElementName); int index=xpath.lastIndexOf("/"+startElementName); if(index!=-1) xpath=xpath.substring(0, index); break; } } else if(event.isEndElement()) { EndElement endElement=(EndElement)event; String endElementName=endElement.getName().getLocalPart(); int index=xpath.lastIndexOf("/"+endElementName); if(index!=-1) xpath=xpath.substring(0, index); } } } catch(XMLStreamException exception) { exception.printStackTrace(); logger.fatal("Exception while reading the record. Message = " + exception.getMessage(),exception); throw new FileParseException("Exception while reading the record. Message = " + exception.getMessage()); } } logger.trace("Exiting getNextRecord"); return readerRecord; } /** * Closes the reader. */ public void close() { logger.trace("Entering close"); if(this.reader!=null) { try { this.reader.close(); logger.debug("Closed the reader"); } catch(XMLStreamException exception) { logger.debug("Streamexception while closing the reader. Message = " + exception.getMessage(),exception); } finally { this.reader=null; } } } /** * Validates the root element. Read all the events until it finds first start * element and validate its name against the configured element name. * * @return Returns true if the first start element name matches with configured element name, * false otherwise. */ private boolean validateRootElement() { boolean valid=false; try { while(reader.hasNext()) { XMLEvent event=reader.nextEvent(); if(event.isStartElement()) { StartElement startElement=(StartElement)event; if(this.fileSpec.getRootElement().equalsIgnoreCase(startElement.getName().getLocalPart())) { valid=true; break; } } } } catch(XMLStreamException exception) { exception.printStackTrace(); logger.fatal("Exception while validating the root element. Message = " + exception.getMessage(),exception); throw new FileParseException("Exception while validating the root element. Message = " + exception.getMessage()); } return valid; } /** * Looks for a record spec whose xpath matches with the current xpath. * * @return Returns the record spec matches the current xpath. */ private XMLRecordSpec getRecordSpec() { Collection recordSpecs=this.fileSpec.getRecordSpecs(); for(Iterator iterator=recordSpecs.iterator();iterator.hasNext();) { XMLRecordSpec recordSpec=(XMLRecordSpec)iterator.next(); if(recordSpec.isMatch(this.xpath)) return recordSpec; } return null; } /** * <p> * Loads all the elements available in the stream from now onwards as a fields * into the record until we reach an end element matches the recordElement name. * </p> * * @param recordSpec */ private XMLReaderRecord retrieveRecord(XMLRecordSpec recordSpec,String recordElement) { XMLReaderRecord readerRecord=new XMLReaderRecord(recordSpec.getRecordType()); Stack elementStack=new Stack(); boolean isPreviousElementStart=false; try { while(reader.hasNext()) { XMLEvent nextEvent=reader.peek(); if(nextEvent.isStartElement()) { /** * If the previous element is also a start element, retrieve the * nested element and returns it as a nested record. */ if(isPreviousElementStart) { StartElement previousStartElement=(StartElement)elementStack.pop(); XMLReaderRecord nestedRecord=this.retrieveRecord(recordSpec, previousStartElement.getName().getLocalPart()); readerRecord.writeElement(previousStartElement.getName().getLocalPart(),nestedRecord); isPreviousElementStart=false; } else { StartElement startElement=(StartElement)reader.nextEvent(); isPreviousElementStart=true; elementStack.push(startElement); } } else if(nextEvent.isEndElement()) { EndElement endElement=(EndElement)reader.nextEvent(); isPreviousElementStart=false; if(recordElement.equalsIgnoreCase(endElement.getName().getLocalPart()) && elementStack.empty()) { /** * End element name matches the starting element name of this record and stack is empty. */ break; } else { /** * Check the value on top of the stack. If it is start element, no value has * been provided for this element in the file, otherwise pop the values and start element. */ Object topValue=elementStack.peek(); if(topValue instanceof StartElement) { StartElement startElement=(StartElement)elementStack.pop(); readerRecord.writeElement(startElement.getName().getLocalPart(),""); } else { Object fieldValue=elementStack.pop(); StartElement startElement=(StartElement)elementStack.pop(); readerRecord.writeElement(startElement.getName().getLocalPart(), fieldValue); } } } else if(nextEvent.isCharacters()) { Characters chars=(Characters)reader.nextEvent(); if(!chars.isWhiteSpace()) { isPreviousElementStart=false; /** * Loop through all the next available character events. If there are * any escape characters, stax is returning them as individual character * events. So, loop through all the events and save them as a value. */ StringBuffer elementData=new StringBuffer(chars.getData()); while(reader.peek().isCharacters()) { elementData.append(((Characters)reader.nextEvent()).getData()); } elementStack.push(elementData.toString()); } } else { /** * Ignore the other events for now. */ reader.nextEvent(); } } } catch(XMLStreamException exception) { exception.printStackTrace(); logger.fatal("XMLStream exception while retrieving the record. Message = " + exception.getMessage(), exception); throw new FileParseException("XMLStream exception while retrieving the record. Message = " + exception.getMessage()); } return readerRecord; } /** * XMLReaderRecord implements ReaderRecord by maintaing the * field names and values as a map and provides the methods with proper * access privileges to read into and write from the record. It provides * the additional method to conveniently read the data from the record. * * @author Suresh Pragada */ public class XMLReaderRecord extends ReaderRecord { /** * Map holds the field names and values. */ private Map fieldMap=null; /** * Constructs XML Reader record. */ private XMLReaderRecord(RecordType recordType) { super(recordType); fieldMap=new HashMap(); } /** * Reads the values associated with the given field name and returns. * The return value will vary based on the element type in file. If it is * a simple element it would be string, if it is a nested element it would * reader record, it it is an element repeats multiple times it will be * list. Use the other methods available on XMLReaderRecord for * convenience. * * @param fieldName Name of the element. * * @return Returns the field value as object. */ public Object readField(String fieldName) { return this.fieldMap.get(fieldName); } /** * Expectes the element as a simple element and returns it as a string. * * @param elementName Name of the simple element. * * @return Returns the value associated with this element. */ public String readSimpleElement(String elementName) { return (String)fieldMap.get(elementName); } /** * Expectes the element as a nested/composite element and returns it as a reader record. * * @param elementName Name of the nested/composite element. * * @return Returns the value associated with this element. */ public ReaderRecord readComplexElement(String elementName) { Object complexElement=fieldMap.get(elementName); if(complexElement!=null) { if(complexElement instanceof ReaderRecord) return (ReaderRecord)complexElement; else if(complexElement instanceof String) return new XMLReaderRecord(super.getRecordType()); else return null; } else return null; } /** * Expectes the element as repeated element and returns it as a list. * * @param elementName Name of the repeated element. * * @return Returns the value associated with this element. */ public List readRepeatElement(String elementName) { Object fieldValue=this.fieldMap.get(elementName); if(fieldValue==null) return null; else if(fieldValue instanceof List) return (List)fieldValue; else { List fieldValueList=new ArrayList(); fieldValueList.add(fieldValue); return fieldValueList; } } /** * Writes the field name and values to the map. It will take care * of converting the values into a list, if they are repeated. */ private void writeElement(String fieldName, Object fieldValue) { if(fieldMap.containsKey(fieldName)) { Object existingFieldValue=fieldMap.remove(fieldName); if(existingFieldValue instanceof List) { List existingFieldValueList=(List)existingFieldValue; existingFieldValueList.add(fieldValue); fieldMap.put(fieldName, existingFieldValueList); } else { List fieldValueList=new ArrayList(); fieldValueList.add(existingFieldValue); fieldValueList.add(fieldValue); fieldMap.put(fieldName,fieldValueList); } } else fieldMap.put(fieldName,fieldValue); } /** * @see java.lang.Object#toString() */ public String toString() { return this.fieldMap.toString(); } } } --- NEW FILE: sample-xml-file.xml --- <?xml version='1.0' encoding='ISO-8859-1'?> <sample-root> <sample-header> <file-type>Employee Records</file-type> </sample-header> <sample-detail> <first-name>Suresh</first-name> <last-name>Pragada</last-name> <dept-info> <dept-name>IT</dept-name> <dept-location>LOC1</dept-location> </dept-info> <addresses> <address> <address-type>home</address-type> <city>Menomonee Falls</city> <zip-code>53051</zip-code> </address> <address> <address-type>office</address-type> <city>Menomonee Falls</city> <zip-code>53051</zip-code> </address> <address>Unidentified</address> </addresses> </sample-detail> <sample-trailer> <transaction-count>1</transaction-count> </sample-trailer> </sample-root> --- NEW FILE: XMLFileWriter.java --- /* * XMLFileWriter.java * * Created on June 5, 2006, 10:36 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.apache.log4j.Logger; import org.jmonks.batch.io.FileParseException; import org.jmonks.batch.io.FileSpec; import org.jmonks.batch.io.FileWriter; import org.jmonks.batch.io.RecordType; import org.jmonks.batch.io.WriterRecord; /** * <p> * XMLFileWriter writes the specified xml file according to the given file spec * with the the recrods submitted to write into the file. It generates the root tag * from the attribute given in file-spec. It uses the last part in record xpath to * write the record element. If nested elements or repeated elements needs to be written, * writer record provides the methods to write these elements. * </p> * <p> * XMLFileWriter looks for two attributes "indentation-engine" and "encoding" in the * file spec to format the generated xml and use the encoding value in generated * xml processing instruction. The value to the "indentation-engine" should be * the class name implements XMLIndentationEngine interface. If it doesnt find this * attribute or not a valid value in this attribute, it uses the default indentation * engine. The value specified in "encoding" attribute will be used in the processing * instruction. * </p> * * @author Suresh Pragada * @version 1.0 * @since 1.0 */ public class XMLFileWriter extends FileWriter { /** * File spec to be adhered with. */ protected XMLFileSpec fileSpec=null; private XMLStreamWriter writer=null; /** * Egnine to be used for the indentation. */ private XMLIndentationEngine indentationEngine=null; private static Logger logger=Logger.getLogger(XMLFileWriter.class); /** * Constructs and initializes the writer with the given values. * * @param outputStream Output stream to write the records. * @param fileSpec File spec to be used to generate the file. */ public XMLFileWriter(OutputStream outputStream,FileSpec fileSpec) { this(new OutputStreamWriter(outputStream), fileSpec); } /** * Constructs and initializes the writer with the given values. * * @param writer Writer to write the records. * @param fileSpec File spec to be used to generate the file. */ public XMLFileWriter(Writer writer,FileSpec fileSpec) { logger.trace("Entering XMLFileWriter constructor"); try { this.fileSpec=(XMLFileSpec)fileSpec; XMLOutputFactory outputFactory=XMLOutputFactory.newInstance(); this.writer=outputFactory.createXMLStreamWriter(writer); logger.debug("Writer has been created."); this.indentationEngine=this.getXMLIndentationEngine(this.fileSpec.getXMLIndentationEngineClassName()); String encoding=this.fileSpec.getEncoding(); if(encoding!=null && !"".equals(encoding)) this.writer.writeStartDocument(encoding, "1.0"); else this.writer.writeStartDocument("UTF-8", "1.0"); this.writer.writeCharacters(this.indentationEngine.startElement()); this.writer.writeStartElement(this.fileSpec.rootElement); } catch(XMLStreamException exception) { exception.printStackTrace(); logger.fatal("XMLStream exception while creating the writer. Message = " + exception.getMessage(), exception); throw new FileParseException("XMLStream exception while creating the writer. Message = " + exception.getMessage()); } logger.trace("Exiting XMLFileWriter constructor"); } /** * Writes the record into the file. This record should be obtained from * this file writer only by using the createWriterRecord method. * * @param writerRecord Record to be written into the file. * * @throws IllegalStateException If writer is closed and trying to write the recrod. * @throws IllegalArgumentException If writer record is null. * @throws org.jmonks.batch.io.FileParseException Problems while trying to write the record. */ public void writeRecord(WriterRecord writerRecord) { logger.trace("Entering writeRecord"); if(writerRecord!=null) { try { XMLWriterRecord record=(XMLWriterRecord)writerRecord; if(record.isNestedElementRecord()) throw new IllegalArgumentException("Writer record should be obtained from the file writer to write into the file."); XMLRecordSpec recordSpec=(XMLRecordSpec)this.fileSpec.getRecordSpec(record.getRecordType()); String recordXpath=recordSpec.getRecordXPath(); int index=recordXpath.lastIndexOf('/'); String recordElement=recordXpath.substring(((index!=-1)?(index+1):0)); writeComplexElement(recordElement, record); writer.flush(); } catch(XMLStreamException exception) { exception.printStackTrace(); logger.fatal("XMLStream exception while writing the record into the writer. Message = " + exception.getMessage(), exception); throw new FileParseException("XMLStream exception while writing the record into the writer. Message = " + exception.getMessage()); } } else throw new IllegalArgumentException("Writer record cannot be null to write record into the file."); logger.trace("Exiting writeRecord"); } /** * Writes the simple element into the file. * * @param fieldName Name of the element. * @param fieldValue Value of the element. */ protected void writeSimpleElement(String fieldName,Object fieldValue) throws XMLStreamException { writer.writeCharacters(indentationEngine.startElement()); writer.writeStartElement(fieldName); writer.writeCharacters((fieldValue!=null)?fieldValue.toString():""); writer.writeCharacters(indentationEngine.endElement()); writer.writeEndElement(); } /** * Writes the nested/composite element into the file. * * @param recordElementName Name of the nested element. * @param fieldValueAsRecord Value of the nested element. */ protected void writeComplexElement(String recordElementName,XMLWriterRecord fieldValueAsRecord) throws XMLStreamException { writer.writeCharacters(indentationEngine.startElement()); writer.writeStartElement(recordElementName); for(Iterator iterator=fieldValueAsRecord.getFieldNameIterator();iterator.hasNext();) { String fieldName=(String)iterator.next(); Object fieldValue=fieldValueAsRecord.readField(fieldName); if(fieldValue instanceof XMLWriterRecord) { writeComplexElement(fieldName, (XMLWriterRecord)fieldValue); } else if(fieldValue instanceof List) { writeRepeatElement(fieldName, (List)fieldValue); } else { writeSimpleElement(fieldName, fieldValue); } } writer.writeCharacters(indentationEngine.endElement()); writer.writeEndElement(); } /** * Writes the repeated element into the file. * * @param multiElementName Name of the repeated element. * @param fieldValueAsList Value of the repeated element. */ protected void writeRepeatElement(String multiElementName,List fieldValueAsList) throws XMLStreamException { List multiElementList=(List)fieldValueAsList; for(Iterator iterator=multiElementList.iterator();iterator.hasNext();) { Object fieldValue=iterator.next(); if(fieldValue instanceof XMLWriterRecord) { writeComplexElement(multiElementName, (XMLWriterRecord)fieldValue); } else if(fieldValue instanceof List) { writeRepeatElement(multiElementName, (List)fieldValue); } else { writeSimpleElement(multiElementName, fieldValue); } } } /** * Creates the writer record assocites with the given record type. * IllegalArgumentException will be thrown, if there is no record * spec is found with this record type. * * @param recordType Type fo the record to be created. * * @return Returns the requested writer record. * * @throws IllegalArgumentException No record spec is found with this record type. */ public WriterRecord createWriterRecord(RecordType recordType) { if(this.fileSpec.isValidRecordType(recordType)) return new XMLWriterRecord(recordType,false); else throw new IllegalArgumentException("Record type " + recordType + " doesnt match with any record specs."); } /** * Closes the writer. */ public void close() { logger.trace("Entering close"); if(this.writer!=null) { try { this.writer.writeCharacters(indentationEngine.endElement()); this.writer.writeEndElement(); this.writer.writeEndDocument(); this.writer.flush(); this.writer.close(); logger.debug("Writer has been closed"); } catch(XMLStreamException exception) { logger.debug("XML Stream Exception while closing the writer. Message = " + exception.getMessage(), exception); } finally { this.writer=null; } } logger.trace("Exiting close"); } /** * Instantiates and returns the instance of the given class name. If it couldnt be instantiated, * it returns default XMLIndentationEngine. * * @param xmlIndentationEngineClassName Class name of engine defined in the file spec. * * @return Returns the instance of xml indentation engine. */ private XMLIndentationEngine getXMLIndentationEngine(String xmlIndentationEngineClassName) { logger.trace("Entering getXMLindentationEngine = " + xmlIndentationEngineClassName); XMLIndentationEngine engine=null; if(xmlIndentationEngineClassName!=null && !"".equals(xmlIndentationEngineClassName)) { try { engine=(XMLIndentationEngine)Class.forName(xmlIndentationEngineClassName).newInstance(); logger.debug("Successfully created engine from the configured class"); } catch(Exception exception) { exception.printStackTrace(); logger.error(exception.getMessage(),exception); logger.info("Unable to instantiate the configured xml indentation engine class. Creating the default xml indentation engine."); engine=new PrettyXMLIndentationEngine(); } } else { logger.info("No xml indentation engine class has been configured in file spec. Creating the default xml indentation engine."); engine=new PrettyXMLIndentationEngine(); } logger.trace("Exiting getXMLIndentationEngine = " + (engine!=null)); return engine; } /** * XMLWriterRecord implements WriterRecord by maintaing the * field names and values as a map and provides the methods with proper * access privileges to read into and write from the record. It provides * the additional method to conveniently write the data into the record. * * @author Suresh Pragada */ public class XMLWriterRecord extends WriterRecord { /** * Map holds the field names and values. */ private Map fieldMap=null; private boolean isNestedElementRecord=false; /** * Constructs the XML writer record. */ private XMLWriterRecord(RecordType recordType, boolean isNestedElementRecord) { super(recordType); this.isNestedElementRecord=isNestedElementRecord; fieldMap=new LinkedHashMap(); } /** * Tells whether this record has been created to represent the nested element. * * @return Returns true if this has been created for the nested element, false otherwise. */ private boolean isNestedElementRecord() { return this.isNestedElementRecord; } /** * Writes the given field name and value into the record. * * @param fieldName Name of the field. * @param fieldValue Value of the field. */ public void writeField(String fieldName, Object fieldValue) { if(fieldName==null) throw new IllegalArgumentException("Field name cannot be null to write the field."); this.fieldMap.put(fieldName, fieldValue); } /** * Write simple element into the record. */ public void writeSimpleElement(String fieldName, String fieldValue) { if(fieldName==null) throw new IllegalArgumentException("Field name cannot be null to write simple element."); this.fieldMap.put(fieldName, fieldValue); } /** * Create complex element and add it to the record and returns the writer * record instance to which all the elements can be adde. */ public WriterRecord createComplexElement(String fieldName) { if(fieldName==null) throw new IllegalArgumentException("Field name cannot be null to create the complex element."); WriterRecord complexElement=new XMLWriterRecord(super.getRecordType(), true); this.fieldMap.put(fieldName, complexElement); return complexElement; } /** * Creates an orphan record that can be added to the record or repeated * element later. Once you are done working with this record, * you should add this one to either the list or any another element, * but you can not submit to the writer for writing this (orphan) record into the file. * This will not be associated to any record by default. * * @return Returns an XMLWriterRecord. */ public WriterRecord createComplexElement() { return new XMLWriterRecord(super.getRecordType() , true); } /** * Creates the list and add it to the record with the given field name * and returns the list to which values can be added. * * @param fieldName Name of the field repated more than once. * * @return Returns the list. */ public List createRepeatElement(String fieldName) { if(fieldName==null) throw new IllegalArgumentException("Field name cannot be null to create the repeat element."); List repeatElement=new ArrayList(); this.fieldMap.put(fieldName, repeatElement); return repeatElement; } /** * Reads the field from the record. */ private Object readField(String fieldName) { return this.fieldMap.get(fieldName); } /** * Gets the iterator for the field names key set. */ private Iterator getFieldNameIterator() { return this.fieldMap.keySet().iterator(); } } } --- NEW FILE: package.html --- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>XML File Implementation Overview</title> </head> <body> XML file implementation for the IO API. <p> XML files are becoming popular choice by the enterprises to exchange the data. This package provides the implementation to work with the XML files. Implementation assumes that file consists of set of records and each can be identified using the xpath. </p> <p> <h3>XML File Implementation</h3> XML files consists of set of different records where each record can be identified by using xpath expression and all the elements beneath the element identified by xpath are treated as fields of that record. <h5>Defining the file spec for xml files</h5> File spec which descibes the xml file expects the <code>file-type</code> attribute value should be "xml". It requires one additional attribute along with the <code>file-type</code> attribute, which is <code>root-element</code> which will be used highly while generating the xml file from the set of records. The value in the attribute specifies the root element of the xml document. This file spec optionally requires 2 more attributes "indentation-engine" and "encoding". "indentation-engine" attribute value tells the class to be used for the indentation purposes. This class should implement the XMLIndentationEngine interface. If this attribute is not specified and the given class is not valid, it uses default indentation engine. The value specified in "encoding" attribute will be used in generated xml processing instruction. <pre> <file-spec file-type="xml" root-element="sample-root" indentation-engine="org.jmonks.batch.io.xml.PrettyXMLIndentationEngine" encoding="ISO-8859-1"> </file-spec> </pre> There could be multiple record specs exists in a file spec. Along with the record-type attribute, it requires an additional attribute <code>record-xpath</code> which tells the xpath in the document to identify the record. <pre> <file-spec file-type="xml" root-element="sample-root" indentation-engine="org.jmonks.batch.io.xml.PrettyXMLIndentationEngine" encoding="ISO-8859-1"> <record-spec record-type="DETAIL" record-xpath="/sample-root/detail-record"/> </file-spec> </pre> Since all the elements beneath the element represented by record spec will be exposed as a field values, record spec doesnt require any field specs. All the simple elements in the record element will be exposed as field name and values where element name represents the field name and element value represents the field value. All the complex elements(nested elements or elements contains child elements) will be exposed as a records again. All the repeat elements(elements which can repeat more than once) values will be exposed as list. <br> Here onwards will try to show how to process/read and generates/write the following xml file. Lets assume we need to read and write and following xml file. <br> <pre style="color:red"> <b> <?xml version='1.0' encoding='ISO-8859-1'?> <sample-root> <sample-header> <file-type>Employee Records</file-type> </sample-header> <sample-detail> <first-name>Suresh</first-name> <last-name>Pragada</last-name> <dept-info> <dept-name>IT</dept-name> <dept-location>LOC1</dept-location> </dept-info> <addresses> <address> <address-type>home</address-type> <city>Menomonee Falls</city> <zip-code>53051</zip-code> </address> <address> <address-type>office</address-type> <city>Menomonee Falls</city> <zip-code>53051</zip-code> </address> <address>Unidentified</address> </addresses> </sample-detail> <sample-trailer> <transaction-count>1</transaction-count> </sample-trailer> </sample-root> </b> </pre> <br> Following is the file spec to read and write the above given file. <br> <pre style="color:red"> <b> <?xml version="1.0" encoding="UTF-8"?> <file-spec file-type="xml" root-element="sample-root" indentation-engine="org.jmonks.batch.io.xml.PrettyXMLIndentationEngine" encoding="ISO-8859-1"> <record-spec record-type="header" record-xpath="/sample-root/sample-header"/> <record-spec record-type="detail" record-xpath="/sample-root/sample-detail"/> <record-spec record-type="trailer" record-xpath="/sample-root/sample-trailer"/> </file-spec> </b> </pre> <h5>Code to read the records from xml files</h5> <pre style="color:green"> <i> FileReader fileReader=FileReader.getFileReader(new FileInputStream("C:\\sample-xml-file_2.xml"),this.getClass().getResourceAsStream("sample-xml-file-spec.xml")); ReaderRecord readerRecord=null; while((readerRecord=fileReader.getNextRecord())!=null) { if(readerRecord.getRecordType().equals(RecordType.HEADER)) { // Simple elements in the records can be read using either readFiled or readSimpleElement. String fileType=(String)readerRecord.readField("file-type"); System.out.println("File type in header record = " + fileType); } else if(readerRecord.getRecordType().equals(RecordType.TRAILER)) { // Trying to show that simple elements can be read using readSimpleElement. String transactionCount=((XMLReaderRecord)readerRecord).readSimpleElement("transaction-count"); System.out.println(transactionCount); } else if(readerRecord.getRecordType().equals(RecordType.DETAIL)) { XMLReaderRecord xmlDetailRecord=(XMLReaderRecord)readerRecord; // Simple elements can be read using readSimpleElement method. String firstName=xmlDetailRecord.readSimpleElement("first-name"); String lastName=xmlDetailRecord.readSimpleElement("last-name"); System.out.println(firstName + " " + lastName); // Nested elements can be read using readComplexElement method. XMLReaderRecord deptInfoComplexRecord=(XMLReaderRecord)xmlDetailRecord.readComplexElement("dept-info"); String deptName=deptInfoComplexRecord.readSimpleElement("dept-name"); String deptLocation=deptInfoComplexRecord.readSimpleElement("dept-location"); System.out.println(deptName + " " + deptLocation); XMLReaderRecord addressesComplexRecord=(XMLReaderRecord)xmlDetailRecord.readComplexElement("addresses"); List addressesRepeatList=addressesComplexRecord.readRepeatElement("address"); for(Iterator iterator=addressesRepeatList.iterator();iterator.hasNext();) { Object addressRecord=iterator.next(); if(addressRecord instanceof XMLReaderRecord) { XMLReaderRecord addressComplexRecord=(XMLReaderRecord)addressRecord; String addressType=addressComplexRecord.readSimpleElement("address-type"); String city=addressComplexRecord.readSimpleElement("city"); String zipCode=addressComplexRecord.readSimpleElement("zip-code"); System.out.println(addressType + " " + city + " " + zipCode); } else if(addressRecord instanceof String) { System.out.println((String)addressRecord); } else System.out.println("Unknown type."); } } else System.out.println("Unknown record type = " + readerRecord.getRecordType().toString()); } fileReader.close(); </i> </pre> <h5>Code to write the records into xml files</h5> <pre style="color:green"> <i> // Get the file writer by providing the output stream to write the xml file and input stream to file spec. FileWriter fileWriter=FileWriter.getFileWriter(new FileOutputStream("C:\\sample-xml-file_2.xml"), this.getClass().getResourceAsStream("sample-xml-file-spec.xml")); // Create and write the header record. XMLWriterRecord headerRecord=(XMLWriterRecord)fileWriter.createWriterRecord(RecordType.HEADER); headerRecord.writeSimpleElement("file-type", "Employee Records"); fileWriter.writeRecord(headerRecord); // Get the empty record you want to create by passing the record type you mentioned in file spec. XMLWriterRecord detailRecord=(XMLWriterRecord)fileWriter.createWriterRecord(RecordType.DETAIL); // Write the simple elements using either writeField or writeSimpleElement methods. detailRecord.writeSimpleElement("first-name", "Suresh"); detailRecord.writeField("last-name", "Pragada"); // Create the nested record by passing the nested element name. This automatically attached to detail record. No need to write it back to detail record. XMLWriterRecord deptComplexRecord=(XMLWriterRecord)detailRecord.createComplexElement("dept-info"); deptComplexRecord.writeSimpleElement("dept-name", "IT"); deptComplexRecord.writeSimpleElement("dept-location", "LOC1"); XMLWriterRecord addressesComplexRecord=(XMLWriterRecord)detailRecord.createComplexElement("addresses"); // Get the list to add all the elements needs to be written with the given name. List addressRepeatList=addressesComplexRecord.createRepeatElement("address"); // Empty nested element record can be created using any XMLWriterRecord instance. XMLWriterRecord homeAddressComplexRecord=(XMLWriterRecord)addressesComplexRecord.createComplexElement(); homeAddressComplexRecord.writeSimpleElement("address-type", "home"); homeAddressComplexRecord.writeSimpleElement("city", "Menomonee Falls"); homeAddressComplexRecord.writeSimpleElement("zip-code", "53051"); addressRepeatList.add(homeAddressComplexRecord); // Empty nested element record can be created using any XMLWriterRecord instance. XMLWriterRecord officeAddressComplexRecord=(XMLWriterRecord)addressesComplexRecord.createComplexElement(); officeAddressComplexRecord.writeSimpleElement("address-type", "office"); officeAddressComplexRecord.writeSimpleElement("city", "Menomonee Falls"); officeAddressComplexRecord.writeSimpleElement("zip-code", "53051"); addressRepeatList.add(officeAddressComplexRecord); // Feel free to drop simple elements value as well. addressRepeatList.add("Unidentified"); // Write the finished record into the file. fileWriter.writeRecord(detailRecord); // Create and write the trailer record. XMLWriterRecord trailerRecord=(XMLWriterRecord)fileWriter.createWriterRecord(RecordType.TRAILER); trailerRecord.writeSimpleElement("transaction-count", "1"); fileWriter.writeRecord(trailerRecord); fileWriter.close(); </i> </pre> </p> </body> </html> --- NEW FILE: sample-xml-file-spec.xml --- <?xml version="1.0" encoding="UTF-8"?> <file-spec file-type="xml" root-element="sample-root" indentation-engine="org.jmonks.batch.io.xml.PrettyXMLIndentationEngine" encoding="ISO-8859-1"> <record-spec record-type="header" record-xpath="/sample-root/sample-header"/> <record-spec record-type="detail" record-xpath="/sample-root/sample-detail"/> <record-spec record-type="trailer" record-xpath="/sample-root/sample-trailer"/> </file-spec> --- NEW FILE: XMLFileSpec.java --- /* * XMLFileSpec.java * * Created on June 2, 2006, 10:42 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; import java.util.Iterator; import org.apache.log4j.Logger; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.jmonks.batch.io.FileType; import org.jmonks.batch.io.RecordSpec; import org.jmonks.batch.io.FileSpecException; import org.jmonks.batch.io.FileSpec; /** * <p> * XMLFileSpec represents the file spec defines the xml file * where each different kind of record identified by the xpath. * This file-spec requires additional attribute root-element which specifies * root element of the xml file along with the file-type attribute, which is supposed * to be "xml". Here is a sample file spec... * </p> * <p> * <pre> * <file-spec file-type="xml" root-element="consumer-request"> * <!-- record specs will follow here --> * </file-spec> * </pre> * </p> * * @author Suresh Pragada * @version 1.0 * @since 1.0 */ public class XMLFileSpec extends FileSpec { /** * Holds the root element. */ protected String rootElement=null; /** * Holds the class name of XMLIndentationEngine. */ protected String xmlIndentationEngineClassName=null; /** * Holds the encoding value to be placed in processing instruction. */ protected String encoding=null; /** * Constant defines the root element attribute name. */ public static final String ROOT_ELEMENT_ATTRIB_NAME = "root-element"; /** * Constant defines the xml indentation engine attribute name. */ public static final String XML_INDENTATION_ENGINE_ATTRIB_NAME = "indentation-engine"; /** * Constant defines the encoding attribute name. */ public static final String ENCODING_ATTRIB_NAME = "encoding"; private static Logger logger=Logger.getLogger(XMLFileSpec.class); /** * Constructs the XMLFileSpec instance. */ protected XMLFileSpec() { super(FileType.XML_FILE); } /** * Gets the root element. */ public String getRootElement() { return this.rootElement; } /** * Returns the XMLIndentationEngine class name to be used * to format the output. This is optional in configuration. If * none is specified default xml indentation engine will be used. * * @return Returns the class name of XMLIndentationEngine, null if not configured. */ public String getXMLIndentationEngineClassName() { return this.xmlIndentationEngineClassName; } /** * Get the encoding value to be placed in processing instruction. This will be * conifgured in file spec. * * @return Returns the file spec. */ public String getEncoding() { return this.encoding; } /** * Factory method to create the xml file spec object from the given * DOM Element representing the file-spec element. * * @param fileSpecElement DOM Element representing the file-spec element. * * @return Returns the instance of XMLFileSpec. * * @throws org.jmonks.batch.io.FileSpecException If two record specs * has the same value for the record-xpath and record-type attributes. */ public static FileSpec createXMLFileSpec(final Element fileSpecElement) { XMLFileSpec fileSpec=new XMLFileSpec(); String rootElement=fileSpecElement.getAttribute(XMLFileSpec.ROOT_ELEMENT_ATTRIB_NAME); if(rootElement!=null && !"".equals(rootElement.trim())) fileSpec.rootElement=rootElement; else throw new FileSpecException("XML FileSpec requires attribute root-element in element file-spec."); fileSpec.xmlIndentationEngineClassName=fileSpecElement.getAttribute(XMLFileSpec.XML_INDENTATION_ENGINE_ATTRIB_NAME); fileSpec.encoding=fileSpecElement.getAttribute(XMLFileSpec.ENCODING_ATTRIB_NAME); NodeList recordSpecNodeList=fileSpecElement.getElementsByTagName(RecordSpec.RECORD_SPEC_TAG_NAME); for(int i=0;i<recordSpecNodeList.getLength();i++) { XMLRecordSpec recordSpec=(XMLRecordSpec)XMLRecordSpec.createXMLRecordSpec((Element)recordSpecNodeList.item(i)); /** * Check for the duplicate record-xpath value on the record specs. */ for(Iterator iterator=fileSpec.getRecordSpecs().iterator();iterator.hasNext();) { XMLRecordSpec existingRecordSpec=(XMLRecordSpec)iterator.next(); if(existingRecordSpec.getRecordXPath().equalsIgnoreCase(recordSpec.getRecordXPath())) { throw new FileSpecException("Two record specs in the same file spec cannot have same values for record-xpath attribute."); } } logger.debug("Adding the record spec = " + recordSpec.toString()); fileSpec.addRecordSpec(recordSpec); } return fileSpec; } public String toString() { StringBuffer stringValue=new StringBuffer("{XMLFileSpec "); stringValue.append("[fileType = " + super.fileType.toString() + "]"); stringValue.append("[rootElement = " + this.rootElement + "]"); stringValue.append("[xmlIndentationEngine = " + this.xmlIndentationEngineClassName + "]"); stringValue.append("[encoding = " + this.encoding + "]"); stringValue.append("[recordSpecList = "); for(Iterator iterator=recordSpecMap.values().iterator();iterator.hasNext();) stringValue.append(((XMLRecordSpec)iterator.next()).toString()); stringValue.append("]}"); return stringValue.toString(); } } --- NEW FILE: PrettyXMLIndentationEngine.java --- /* * PrettyXMLIndentationEngine.java * * Created on June 12, 2006, 10:06 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; /** * <p> * PrettyXMLIndentationEngine writes the the elements in XML in the folloing manner. * <br> * <pre> * <root> * <element1>element-data1</element1> * <element2> * <element3>element-data3</element> * <element4>element-data4</element> * </element2> * </root> * </pre> * </p> * * @author Suresh Pragada * @version 1.0 * @since 1.0 */ public class PrettyXMLIndentationEngine implements XMLIndentationEngine { /** * Remembers previous element is start element. */ private boolean isPrevStartElement=false; /** * Remembers previous element is end element. */ private boolean isPrevEndElement=false; private String indentationString="\n"; public PrettyXMLIndentationEngine() { } /** * @see org.jmonks.batch.io.xml.XMLIndentationEngine#startElement */ public String startElement() { isPrevEndElement=false; if(isPrevStartElement) { this.indentationString+=" "; return this.indentationString; } else { isPrevStartElement=true; return this.indentationString; } } /** * @see org.jmonks.batch.io.xml.XMLIndentationEngine#endElement */ public String endElement() { isPrevStartElement=false; if(isPrevEndElement) { this.indentationString=this.indentationString.substring(0, this.indentationString.length()-4); return this.indentationString; } else { isPrevEndElement=true; return ""; } } } --- NEW FILE: XMLRecordSpec.java --- /* * XMLRecordSpec.java * * Created on June 2, 2006, 10:54 PM * * To change this template, choose Tools | Options and locate the template under * the Source Creation and Management node. Right-click the template and choose * Open. You can then make changes to the template in the Source Editor. */ package org.jmonks.batch.io.xml; import org.apache.log4j.Logger; import org.w3c.dom.Element; import org.jmonks.batch.io.RecordType; import org.jmonks.batch.io.RecordSpec; import org.jmonks.batch.io.FileSpecException; /** * <p> * XMLRecordSpec represents record-spec element in the file spec * belongs to the xml file type. Along with the record-type attri... [truncated message content] |