All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.beanio.config.xml.XmlMappingParser Maven / Gradle / Ivy

Go to download

A Java un/marshalling library for CSV, XML, delimited and fixed length stream formats.

There is a newer version: 2.1.0
Show newest version
/*
 * Copyright 2011 Kevin Seim
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.beanio.config.xml;

import java.io.*;
import java.net.*;
import java.util.*;

import org.beanio.BeanIOConfigurationException;
import org.beanio.config.*;
import org.beanio.util.IOUtil;
import org.w3c.dom.*;

/**
 * Parses a mapping file into a {@link BeanIOConfig} object.  A BeanIOConfig
 * is also produced for each mapping file imported by the mapping file being parsed,
 * and the entire collection is returned from {@link #loadConfiguration(InputStream)}.
 * 
 * 

This class is not thread safe and a new instance must be created for parsing * each input stream. * * @author Kevin Seim * @since 1.2.1 */ public class XmlMappingParser { /* used to read XML into a DOM object */ private XmlMappingReader reader; /* the mapping currently being parsed */ private XmlMapping mapping; /* a Map of all loaded mappings (except the root) */ private Map mappings; private LinkedList includeStack = new LinkedList(); /** * Constructs a new XmlMappingParser. * @param reader the XML mapping reader for reading XML mapping files * into a DOM object */ public XmlMappingParser(XmlMappingReader reader) { if (reader == null) { throw new IllegalArgumentException("loader is null"); } this.reader = reader; } /** * Reads a mapping file input stream and returns a collection of BeanIO * configurations, one for the input stream and one for each imported * mapping file (if specified). * @param in the input stream to read * @return the collection of parsed BeanIO configuration objects * @throws IOException if an I/O error occurs * @throws BeanIOConfigurationException if the configuration is invalid */ public Collection loadConfiguration(InputStream in) throws IOException, BeanIOConfigurationException { mapping = new XmlMapping(); mappings = new HashMap(); mappings.put("[root]", mapping); try { loadMapping(in); } catch (BeanIOConfigurationException ex) { if (mapping.getLocation() != null) { throw new BeanIOConfigurationException("Invalid mapping file '" + mapping.getName() + "': " + ex.getMessage(), ex); } throw ex; } List configList = new ArrayList(mappings.size()); for (XmlMapping m : mappings.values()) { BeanIOConfig config = m.getConfiguration().clone(); // global type handlers are the only elements that need to be imported // from other mapping files List handlerList = new ArrayList(); m.addTypeHandlers(handlerList); config.setTypeHandlerList(handlerList); configList.add(config); } return configList; } /** * Initiates the parsing of an imported mapping file. *

After parsing completes, {@link #pop()} must be invoked * before continuing. * @param name the name of the imported mapping file * @param location the location of the imported mapping file * (this should be an absolute URL so that importing the * same mapping more than once can be detected) * @return the new Mapping object pushed onto the stack * (this can also be accessed by calling {@link #getMapping()}) * @see #pop() */ protected final XmlMapping push(String name, String location) { XmlMapping m = new XmlMapping(name, location, mapping); mappings.put(location, m); mapping.addImport(m); mapping = m; return mapping; } /** * Completes the parsing of an imported mapping file. * @see #push(String, String) */ protected final void pop() { mapping = mapping.getParent(); } /** * Returns the mapping file information actively being parsed, which may change * when one mapping file imports another. * @return the active mapping information */ protected final XmlMapping getMapping() { return mapping; } /** * Returns the amount to offset a field position, which is calculated * according to included template offset configurations. * @return the current field position offset */ protected final int getPositionOffset() { if (includeStack.isEmpty()) { return 0; } else { return includeStack.getFirst().getOffset(); } } /** * Loads a mapping file from an input stream. * @param in the input stream to read * @throws IOException if an I/O error occurs */ protected void loadMapping(InputStream in) throws IOException { Document document = reader.loadDocument(in); loadMapping(document.getDocumentElement()); } /** * Parses a BeanIO configuration from a DOM element. * @param element the root 'beanio' DOM element to parse */ protected void loadMapping(Element element) { BeanIOConfig config = mapping.getConfiguration(); NodeList children = element.getChildNodes(); for (int i = 0, j = children.getLength(); i < j; i++) { Node node = children.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element child = (Element) node; String name = child.getTagName(); if ("import".equals(name)) { importConfiguration(child); } else if ("typeHandler".equals(name)) { TypeHandlerConfig handler = createHandlerConfig(child); if (handler.getName() != null && mapping.isDeclaredGlobalTypeHandler(handler.getName())) { throw new BeanIOConfigurationException( "Duplicate global type handler named '" + handler.getName() + "'"); } config.addTypeHandler(createHandlerConfig(child)); } else if ("template".equals(name)) { createTemplate(child); } else if ("stream".equals(name)) { config.addStream(createStreamConfig(child)); } } } /** * Parses an import DOM element and loads its mapping file. * @param element the import DOM element * @return a new BeanIOConfig for the imported resource or file */ protected final XmlMapping importConfiguration(Element element) { URL url = null; String resource = getAttribute(element, "resource"); String name = resource; if (resource.startsWith("classpath:")) { resource = resource.substring("classpath:".length()).trim(); if ("".equals(resource)) { throw new BeanIOConfigurationException("Invalid import resource"); } url = IOUtil.getResource(resource); if (url == null) { throw new BeanIOConfigurationException("Resource '" + name + "' not found in classpath for import"); } } else if (resource.startsWith("file:")) { resource = resource.substring("file:".length()).trim(); if ("".equals(resource)) { throw new BeanIOConfigurationException("Invalid import resource"); } File file = new File(resource); if (!file.canRead()) { throw new BeanIOConfigurationException("Resource '" + name + "' not found in file system for import"); } try { url = file.toURI().toURL(); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } } else { throw new BeanIOConfigurationException("Import resource name must begin with 'classpath:' or 'file:'"); } if (mapping.isLoading(url.toString())) { throw new BeanIOConfigurationException("Failed to import resource '" + name + "': Circular reference(s) detected"); } String key = url.toString(); // check to see if the mapping file has already been loaded XmlMapping m = mappings.get(key); if (m != null) { return m; } InputStream in = null; try { in = new BufferedInputStream(url.openStream()); // push a new Mapping instance onto the stack for this url push(name, key).getConfiguration().setSource(name); loadMapping(in); // this is purposely not put in a finally block so that // calling methods can know the mapping file that errored // if a BeanIOConfigurationException is thrown pop(); return mapping; } catch (IOException ex) { throw new BeanIOConfigurationException("Failed to import mapping file '" + name + "'", ex); } finally { IOUtil.closeQuietly(in); } } /** * Parses a TypeHandlerConfig from a DOM element. * @param element the DOM element to parse * @return the new TypeHandlerConfig */ protected TypeHandlerConfig createHandlerConfig(Element element) { TypeHandlerConfig config = new TypeHandlerConfig(); config.setName(getAttribute(element, "name")); config.setType(getAttribute(element, "type")); config.setClassName(getAttribute(element, "class")); config.setFormat(getAttribute(element, "format")); config.setProperties(createProperties(element)); return config; } /** * Adds a template to the active mapping. * @param element the DOM element that defines the template */ protected void createTemplate(Element element) { String templateName = element.getAttribute("name"); if (!mapping.addTemplate(templateName, element)) { throw new BeanIOConfigurationException( "Duplicate template named '" + templateName + "'"); } } /** * Parses a Bean from a DOM element. * @param element the DOM element to parse * @return the new Bean */ protected Bean createBeanFactory(Element element) { Bean config = new Bean(); config.setClassName(getAttribute(element, "class")); config.setProperties(createProperties(element)); return config; } /** * Parses Properties from a DOM element. * @param element the DOM element to parse * @return the new Properties */ protected Properties createProperties(Element element) { Properties props = null; NodeList children = element.getChildNodes(); for (int i = 0, j = children.getLength(); i < j; i++) { Node node = children.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element child = (Element) node; String name = child.getTagName(); if ("property".equals(name)) { if (props == null) { props = new Properties(); } props.put( child.getAttribute("name"), child.getAttribute("value")); } } return props; } /** * Parses a StreamConfig from a DOM element. * @param element the stream DOM element to parse * @return the new StreamConfig */ protected StreamConfig createStreamConfig(Element element) { StreamConfig config = new StreamConfig(); config.setName(getAttribute(element, "name")); config.setFormat(getAttribute(element, "format")); config.setMode(getAttribute(element, "mode")); config.setOrdered(getBooleanAttribute(element, "ordered", true)); config.setResourceBundle(getAttribute(element, "resourceBundle")); config.setMinOccurs(getIntAttribute(element, "minOccurs", 0)); Integer maxOccurs = getUnboundedIntegerAttribute(element, "maxOccurs", -1); if (maxOccurs == null) maxOccurs = 1; config.setMaxOccurs(maxOccurs); config.getRootGroupConfig().setXmlName(getAttribute(element, "xmlName")); config.getRootGroupConfig().setXmlNamespace(getOptionalAttribute(element, "xmlNamespace")); config.getRootGroupConfig().setXmlPrefix(getOptionalAttribute(element, "xmlPrefix")); config.getRootGroupConfig().setXmlType(getOptionalAttribute(element, "xmlType")); NodeList children = element.getChildNodes(); for (int i = 0, j = children.getLength(); i < j; i++) { Node node = children.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element child = (Element) node; String name = child.getTagName(); if ("typeHandler".equals(name)) { config.addHandler(createHandlerConfig(child)); } else if ("reader".equals(name)) { config.setReaderFactory(createBeanFactory(child)); } else if ("writer".equals(name)) { config.setWriterFactory(createBeanFactory(child)); } else if ("record".equals(name)) { config.addNode(createRecordConfig(child)); } else if ("group".equals(name)) { config.addNode(createGroupConfig(child)); } } return config; } /** * Parses a group configuration from a DOM element. * @param element the group DOM element to parse * @return the parsed group configuration */ protected GroupConfig createGroupConfig(Element element) { GroupConfig config = new GroupConfig(); config.setName(getAttribute(element, "name")); config.setOrder(getIntAttribute(element, "order", -1)); config.setMinOccurs(getIntegerAttribute(element, "minOccurs")); config.setMaxOccurs(getUnboundedIntegerAttribute(element, "maxOccurs", -1)); config.setXmlName(getAttribute(element, "xmlName")); config.setXmlNamespace(getOptionalAttribute(element, "xmlNamespace")); config.setXmlPrefix(getOptionalAttribute(element, "xmlPrefix")); config.setXmlType(getOptionalAttribute(element, "xmlType")); NodeList children = element.getChildNodes(); for (int i = 0, j = children.getLength(); i < j; i++) { Node node = children.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element child = (Element) node; String name = child.getTagName(); if ("record".equals(name)) { config.addChild(createRecordConfig(child)); } else if ("group".equals(name)) { config.addChild(createGroupConfig(child)); } } return config; } /** * Parses a record configuration from the given DOM element. * @param record the record DOM element to parse * @return the parsed record configuration */ protected RecordConfig createRecordConfig(Element record) { BeanConfig beanConfig = new BeanConfig(); beanConfig.setName(getAttribute(record, "name")); beanConfig.setType(getAttribute(record, "class")); beanConfig.setXmlName(getAttribute(record, "xmlName")); beanConfig.setXmlNamespace(getOptionalAttribute(record, "xmlNamespace")); beanConfig.setXmlPrefix(getOptionalAttribute(record, "xmlPrefix")); String template = getOptionalAttribute(record, "template"); if (template != null) { includeTemplate(beanConfig, template, 0); } addProperties(beanConfig, record); RecordConfig config = new RecordConfig(); config.setName(getAttribute(record, "name")); config.setOrder(getIntAttribute(record, "order", -1)); config.setMinOccurs(getIntegerAttribute(record, "minOccurs")); config.setMaxOccurs(getUnboundedIntegerAttribute(record, "maxOccurs", -1)); config.setMinLength(getIntegerAttribute(record, "minLength")); config.setMaxLength(getUnboundedIntegerAttribute(record, "maxLength", -1)); config.setBean(beanConfig); return config; } /** * Parses bean properties from the given DOM element. * @param beanConfig the enclosing bean configuration to add the properties to * @param element the bean or record DOM element to parse */ protected void addProperties(BeanConfig beanConfig, Element element) { NodeList children = element.getChildNodes(); for (int i = 0, j = children.getLength(); i < j; i++) { Node node = children.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) continue; Element child = (Element) node; String name = child.getTagName(); if ("field".equals(name)) { beanConfig.addProperty(createFieldConfig(child)); } else if ("bean".equals(name)) { beanConfig.addProperty(createBeanConfig(child)); } else if ("property".equals(name)) { beanConfig.addProperty(createBeanPropertyConfig(child)); } else if ("include".equals(name)) { includeTemplate(beanConfig, child); } } } /** * Includes a template. * @param beanConfig the parent bean configuration * @param element the include DOM element to parse */ protected void includeTemplate(BeanConfig beanConfig, Element element) { String template = getAttribute(element, "template"); int offset = getIntAttribute(element, "offset", 0); includeTemplate(beanConfig, template, offset); } /** * Includes a template. * @param beanConfig the parent bean configuration * @param template the name of the template to include * @param offset the value to offset configured positions by */ protected void includeTemplate(BeanConfig beanConfig, String template, int offset) { Element element = mapping.findTemplate(template); // validate the template was declared if (element == null) { throw new BeanIOConfigurationException("Template '" + template + "' not found"); } // validate there is no circular reference for (Include include : includeStack) { if (template.equals(include.getTemplate())) { throw new BeanIOConfigurationException( "Circular reference detected in template '" + template + "'"); } } // adjust the configured offset by any previous offset offset += getPositionOffset(); Include inc = new Include(template, offset); includeStack.addFirst(inc); addProperties(beanConfig, element); includeStack.removeFirst(); } private void populatePropertyConfig(PropertyConfig config, Element element) { config.setName(getAttribute(element, "name")); config.setGetter(getAttribute(element, "getter")); config.setSetter(getAttribute(element, "setter")); config.setCollection(getAttribute(element, "collection")); config.setMinOccurs(getIntegerAttribute(element, "minOccurs")); config.setMaxOccurs(getUnboundedIntegerAttribute(element, "maxOccurs", -1)); } /** * Parses a bean configuration from a DOM element. * @param element the bean DOM element to parse * @return the parsed bean configuration */ protected BeanConfig createBeanConfig(Element element) { BeanConfig config = new BeanConfig(); populatePropertyConfig(config, element); config.setType(getAttribute(element, "class")); config.setXmlName(getAttribute(element, "xmlName")); config.setXmlNamespace(getOptionalAttribute(element, "xmlNamespace")); config.setXmlPrefix(getOptionalAttribute(element, "xmlPrefix")); config.setXmlType(getOptionalAttribute(element, "xmlType")); config.setNillable(getBooleanAttribute(element, "nillable", config.isNillable())); config.setXmlWrapper(getOptionalAttribute(element, "xmlWrapper")); String template = getOptionalAttribute(element, "template"); if (template != null) { includeTemplate(config, template, 0); } addProperties(config, element); return config; } /** * Parses a field configuration from a DOM element. * @param element the field DOM element to parse * @return the parsed field configuration */ protected FieldConfig createFieldConfig(Element element) { FieldConfig config = new FieldConfig(); populatePropertyConfig(config, element); // adjust the position by the configured include offset int position = getIntAttribute(element, "position", -1); if (position >= 0) { position += getPositionOffset(); config.setPosition(position); } config.setMinLength(getIntegerAttribute(element, "minLength")); config.setMaxLength(getUnboundedIntegerAttribute(element, "maxLength", -1)); config.setRegex(getAttribute(element, "regex")); config.setLiteral(getAttribute(element, "literal")); config.setTypeHandler(getTypeHandler(element, "typeHandler")); config.setType(getAttribute(element, "type")); config.setFormat(getAttribute(element, "format")); config.setDefault(getOptionalAttribute(element, "default")); config.setRequired(getBooleanAttribute(element, "required", config.isRequired())); config.setTrim(getBooleanAttribute(element, "trim", config.isTrim())); config.setRecordIdentifier(getBooleanAttribute(element, "rid", config.isRecordIdentifier())); config.setIgnored(getBooleanAttribute(element, "ignore", config.isIgnored())); config.setLength(getIntAttribute(element, "length", config.getLength())); config.setPadding(getCharacterAttribute(element, "padding")); config.setJustify(getAttribute(element, "justify")); config.setXmlType(getAttribute(element, "xmlType")); config.setXmlName(getAttribute(element, "xmlName")); config.setXmlNamespace(getOptionalAttribute(element, "xmlNamespace")); config.setXmlPrefix(getOptionalAttribute(element, "xmlPrefix")); config.setNillable(getBooleanAttribute(element, "nillable", config.isNillable())); config.setXmlWrapper(getOptionalAttribute(element, "xmlWrapper")); return config; } /** * Parses a bean property configuration from a DOM element. * @param element the property DOM element to parse * @return the parsed bean property configuration */ protected BeanPropertyConfig createBeanPropertyConfig(Element element) { BeanPropertyConfig config = new BeanPropertyConfig(); try { config.setName(getAttribute(element, "name")); config.setGetter(getAttribute(element, "getter")); config.setSetter(getAttribute(element, "setter")); config.setType(getAttribute(element, "type")); config.setTypeHandler(getTypeHandler(element, "typeHandler")); config.setValue(getAttribute(element, "format")); config.setRecordIdentifier(getBooleanAttribute(element, "rid", config.isRecordIdentifier())); config.setValue(getAttribute(element, "value")); return config; } catch (BeanIOConfigurationException ex) { throw new BeanIOConfigurationException( "Invalid '" + config.getName() + "' property definition: " + ex.getMessage()); } } private String getTypeHandler(Element element, String name) { String handler = getAttribute(element, name); /* if (handler != null && !mapping.isName(Mapping.TYPE_HANDLER_NAMESPACE, handler)) { throw new BeanIOConfigurationException("Unresolved type handler named '" + handler + "'"); } */ return handler; } private String getOptionalAttribute(Element element, String name) { Attr att = element.getAttributeNode(name); if (att == null) { return null; } else { return att.getTextContent(); } } private String getAttribute(Element element, String name) { String value = element.getAttribute(name); if ("".equals(value)) value = null; return value; } private int getIntAttribute(Element element, String name, int defaultValue) { String text = getAttribute(element, name); if (text == null) return defaultValue; return Integer.parseInt(text); } private Character getCharacterAttribute(Element element, String name) { String text = getAttribute(element, name); if (text == null || text.length() == 0) return null; return text.charAt(0); } private Integer getIntegerAttribute(Element element, String name) { String text = getAttribute(element, name); if (text == null) return null; return Integer.parseInt(text); } private Integer getUnboundedIntegerAttribute(Element element, String name, int unboundedValue) { String text = getAttribute(element, name); if (text == null) return null; if ("unbounded".equals(text)) return unboundedValue; return Integer.parseInt(text); } private boolean getBooleanAttribute(Element element, String name, boolean defaultValue) { String text = getAttribute(element, name); if (text == null) return defaultValue; return "true".equals(text) || "1".equals(text); } private static final class Include { private String template; private int offset = 0; public Include(String template, int offset) { this.template = template; this.offset = offset; } public String getTemplate() { return template; } public int getOffset() { return offset; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy