/* * Copyright (c) 2004, Nate Nielsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Nate Nielsen * */ package com.memberwebs.ldapxml.map; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import java.util.Stack; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Parses an XML file into an in memory LX map representation. * * @author nielsen@memberwebs.com * @version 0.5 */ class LXSAXHandler extends DefaultHandler { private static final String EL_ROOT = "lxmap"; private static final String EL_ENTRY = "entry"; private static final String EL_CLASS = "class"; private static final String EL_ELEMENT = "element"; private static final String EL_ATTRIBUTE = "attribute"; private static final String EL_RENAME = "rename"; private static final String EL_IGNORE = "ignore"; private static final String AT_NAME = "name"; private static final String AT_CLASS = "class"; private static final String AT_MODE = "mode"; private static final String AT_NAMESPACE = "namespace"; private static final String AT_NS = "ns"; private static final String AT_PLACE = "place"; private static final String AT_HOOK = "hook"; private static final String AT_ROOT = "root"; private static final String VAL_INCLUSIVE = "inclusive"; private static final String VAL_EXCLUSIVE = "exclusive"; private static final String VAL_INLINE = "inline"; private static final String VAL_BLOCK = "block"; private static final String AT_VERSION = "version"; private static final String VAL_VERSION = "1.0"; private static final String INTERNAL_ERROR = "LX Map parser invalid internal state"; // The root of the map being built private LXMap m_map; // The current LX map object being acted on. private LXBase m_cur; // Stack of LX objects private Stack m_elStack; // public (XML) to private (LDAP) name mappings private Hashtable m_nameMap; // All the LDAP attributes required for this map private HashSet m_nameSet; /** * Constructs a new LXSAXHandler object. */ public LXSAXHandler() { m_elStack = new Stack(); m_nameMap = new Hashtable(); m_nameSet = null; } /** * Called at the start of the XML document. */ public void startDocument() throws SAXException { m_cur = null; m_map = null; m_nameMap.clear(); m_nameSet = new HashSet(); } /** * Called at the end of the XML document. */ public void endDocument() throws SAXException { // The current LX object should be null as we're back // to the root. If not then something went wrong. if(m_cur != null) throw new SAXException(new LXMapException(INTERNAL_ERROR)); } /** * Called when a new element is encountered in the XML file. * * @param uri The element namespace * @param localName The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { try { // Push the current LX object on a stack so it can // be popped as we encounter the end of elements. // Since null doesn't work with the collection classes // we use 'this' to represent it. (See endElement) if(m_cur == null) m_elStack.push(this); else m_elStack.push(m_cur); // Now depending on what kind of LX Object we have around // we hand of parsing to different functions. if(m_cur == null) { rootHandler(uri, localName, qName, attributes); } else if(m_cur instanceof LXMap) { entryHandler(uri, localName, qName, attributes); } else if(m_cur instanceof LXEntry) { if(!classHandler(uri, localName, qName, attributes)) actionHandler(uri, localName, qName, attributes); } else if(m_cur instanceof LXClass) { if(!attributeHandler(uri, localName, qName, attributes)) actionHandler(uri, localName, qName, attributes); } else if(m_cur instanceof LXAttribute) { actionHandler(uri, localName, qName, attributes); } else throw new LXMapException(INTERNAL_ERROR); } catch(LXMapException e) { throw new SAXException(e); } } /** * Called when the end of an element is encountered. * * @param uri The namespace of this element. * @param localName The plain name of this element. * @param qName The fully qualified name of this element. */ public void endElement(String uri, String localName, String qName) throws SAXException { // Add the current element to the naming stack if(m_cur instanceof LXAttribute) addName(m_cur); // Pop the current LX object off the stack Object obj = m_elStack.pop(); if(obj == null) throw new SAXException(new LXMapException(INTERNAL_ERROR)); // If it's set to 'this' then it's actually // 'null' (See startElement) if(obj == this) m_cur = null; else m_cur = (LXBase)obj; } /** * Handles elements at the root document level. * * @param uri The element namespace * @param localname The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. * @return Returns true if element was processed. */ private boolean rootHandler(String uri, String localName, String qName, Attributes attributes) throws LXMapException { if(qName.equals(EL_ROOT)) { if(!attributes.getValue(AT_VERSION).equals(VAL_VERSION)) throw new LXMapException("Invalid LX Map version"); m_map = new LXMap(m_nameMap, m_nameSet); m_cur = m_map; nameAttrHandler(attributes, AT_ROOT); commonAttrHandler(attributes); // Put some exceptions into the name map m_nameMap.put("class", "objectClass"); String prefix = m_cur.getPrefix(); if(prefix != null) m_nameMap.put(prefix + ":class", "objectClass"); return true; } return false; } /** * Handles entry elements at the map root level. * * @param uri The element namespace * @param localname The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. * @return Returns true if element was processed. */ private boolean entryHandler(String uri, String localName, String qName, Attributes attributes) throws LXMapException { if(qName.equals(EL_ENTRY)) { LXEntry entry = new LXEntry(m_cur); m_cur = entry; nameAttrHandler(attributes, AT_CLASS); commonAttrHandler(attributes); return true; } return false; } /** * Handles class elements at the map entry level. * * @param uri The element namespace * @param localname The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. * @return Returns true if element was processed. */ private boolean classHandler(String uri, String localName, String qName, Attributes attributes) throws LXMapException { if(qName.equals(EL_CLASS)) { LXClass cls = new LXClass(m_cur); m_cur = cls; nameAttrHandler(attributes, null); commonAttrHandler(attributes); classAttrHandler(attributes); return true; } return false; } /** * Handles attribute elements at the map class level. * * @param uri The element namespace * @param localname The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. * @return Returns true if element was processed. */ private boolean attributeHandler(String uri, String localName, String qName, Attributes attributes) throws LXMapException { if(qName.equals(EL_ATTRIBUTE)) { LXAttribute attr = new LXAttribute(m_cur, false); m_cur = attr; nameAttrHandler(attributes, null); return true; } else if(qName.equals(EL_ELEMENT)) { LXAttribute attr = new LXAttribute(m_cur, true); m_cur = attr; nameAttrHandler(attributes, null); return true; } return false; } /** * Handles action elements at various levels. * * @param uri The element namespace * @param localname The plain element name. * @param qName The fully quallified element name. * @param attributes The element's attributes. * @return Returns true if element was processed. */ private boolean actionHandler(String uri, String localName, String qName, Attributes attributes) throws LXMapException { if(qName.equals(EL_IGNORE)) { m_cur.m_isUseable = false; return true; } else if(qName.equals(EL_RENAME)) { m_cur.m_xmlName = attributes.getValue(AT_NAME); if(m_cur.m_xmlName == null) throw new LXMapException("LX map element requires a '" + AT_NAME + "' attribute"); } return false; } /** * Handles an element's name. * * @param attributes The element's attributes. */ private void nameAttrHandler(Attributes attributes, String attr) throws LXMapException { if(attr == null) attr = AT_NAME; m_cur.setName(attributes.getValue(attr)); if(m_cur.getName() == null) throw new LXMapException("LX map element requires a '" + attr + "' attribute"); } /** * Handles an element's namespace and other basic info. * * @param attributes The element's attributes. */ private void commonAttrHandler(Attributes attributes) throws LXMapException { m_cur.m_nameSpace = attributes.getValue(AT_NAMESPACE); m_cur.m_ns = attributes.getValue(AT_NS); m_cur.m_hook = attributes.getValue(AT_HOOK); } /** * Handles various attributes for class elements. * * @param attributes The element's attributes. */ private void classAttrHandler(Attributes attributes) throws LXMapException { if(!(m_cur instanceof LXClass)) throw new LXMapException(INTERNAL_ERROR); LXClass cls = (LXClass)m_cur; String inc = attributes.getValue(AT_MODE); if(inc == null || inc.equals(VAL_EXCLUSIVE)) { cls.m_isInclusive = false; } else if(inc.equals(VAL_INCLUSIVE)) { // We don't know the list of names m_map.m_hasInclusive = true; cls.m_isInclusive = true; } else throw new LXMapException("LX map invalid value for '" + AT_MODE + "' attribute"); String place = attributes.getValue(AT_PLACE); if(place != null) { if(place.equals(VAL_INLINE)) cls.m_isInline = true; else if(place.equals(VAL_BLOCK)) cls.m_isInline = false; else throw new LXMapException("LX map invalid value for '" + AT_PLACE + "' attribute"); } } /** * Get the resulting LX Map. * * @return The map. */ public final LXMap getMap() { return m_map; } /** * Get public (XML) to private (LDAP) name mappings * for the first entry. * * @return The name map. */ public final Map getNameMap() { return m_nameMap; } /** * Gets a listing of the minimum set of attributes required * to satisfy this map, or null when not known */ public final Set getNameSet() { return m_nameSet; } /** * Add an LX object to the name map. This adds both * the prefixed name as well as the local name. * * @param base The LX object whose name to add. */ private void addName(LXBase base) { String name = base.getName(); if(base.m_xmlName != null && !m_nameMap.containsKey(base.m_xmlName)) m_nameMap.put(base.m_xmlName, name); String xmlName = base.getXmlName(); if(xmlName != null && !m_nameMap.containsKey(xmlName)) m_nameMap.put(xmlName, name); if(m_nameSet != null) m_nameSet.add(name); } }