From f2839912966174f0f0bdfd0d607f83d4a8aee271 Mon Sep 17 00:00:00 2001 From: Stef Date: Wed, 26 Nov 2003 02:12:18 +0000 Subject: Initial Import --- src/com/memberwebs/ldapxml/LXConvert.java | 13 + src/com/memberwebs/ldapxml/LXException.java | 34 + src/com/memberwebs/ldapxml/LXHook.java | 40 ++ src/com/memberwebs/ldapxml/LXMap.java | 130 ++++ src/com/memberwebs/ldapxml/LXMapException.java | 16 + src/com/memberwebs/ldapxml/LXReader.java | 708 +++++++++++++++++++++ src/com/memberwebs/ldapxml/LXResults.java | 123 ++++ src/com/memberwebs/ldapxml/LXSpecs.java | 256 ++++++++ .../memberwebs/ldapxml/helpers/LXAttribute.java | 43 ++ src/com/memberwebs/ldapxml/helpers/LXBase.java | 226 +++++++ src/com/memberwebs/ldapxml/helpers/LXClass.java | 81 +++ .../memberwebs/ldapxml/helpers/LXComparator.java | 141 ++++ .../ldapxml/helpers/LXDefaultConvert.java | 19 + .../memberwebs/ldapxml/helpers/LXDefaultHook.java | 66 ++ src/com/memberwebs/ldapxml/helpers/LXEntry.java | 43 ++ src/com/memberwebs/ldapxml/helpers/LXRoot.java | 40 ++ .../memberwebs/ldapxml/helpers/LXSAXHandler.java | 447 +++++++++++++ 17 files changed, 2426 insertions(+) create mode 100644 src/com/memberwebs/ldapxml/LXConvert.java create mode 100644 src/com/memberwebs/ldapxml/LXException.java create mode 100644 src/com/memberwebs/ldapxml/LXHook.java create mode 100644 src/com/memberwebs/ldapxml/LXMap.java create mode 100644 src/com/memberwebs/ldapxml/LXMapException.java create mode 100644 src/com/memberwebs/ldapxml/LXReader.java create mode 100644 src/com/memberwebs/ldapxml/LXResults.java create mode 100644 src/com/memberwebs/ldapxml/LXSpecs.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXAttribute.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXBase.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXClass.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXComparator.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXDefaultConvert.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXDefaultHook.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXEntry.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXRoot.java create mode 100644 src/com/memberwebs/ldapxml/helpers/LXSAXHandler.java diff --git a/src/com/memberwebs/ldapxml/LXConvert.java b/src/com/memberwebs/ldapxml/LXConvert.java new file mode 100644 index 0000000..1024ed4 --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXConvert.java @@ -0,0 +1,13 @@ +package com.memberwebs.ldapxml; + +/** + * Interface for hooking LXReader value conversions. + * + * @author nielsen@memberwebs.com + * @version 1.0 + */ +public interface LXConvert +{ + public String parse(String name, String syntax, String value) + throws LXException; +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXException.java b/src/com/memberwebs/ldapxml/LXException.java new file mode 100644 index 0000000..242e081 --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXException.java @@ -0,0 +1,34 @@ +package com.memberwebs.ldapxml; + +import com.familymembers.util.*; + +/** + * Thrown when an error occurs during LX retrieval or + * processing. + * + * @author nielsen@memberwebs.com + * @version 1.0 + */ +public class LXException + extends WrappedException +{ + /** + * Creates a new LXException object. + * + * @param message Error message. + */ + public LXException(String message) + { + super(message, null); + } + + /** + * Creates a new LXException object wrapping a current Exception. + * + * @param e The exception. + */ + public LXException(Exception e) + { + super(null, e); + } +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXHook.java b/src/com/memberwebs/ldapxml/LXHook.java new file mode 100644 index 0000000..eac7a6c --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXHook.java @@ -0,0 +1,40 @@ +package com.memberwebs.ldapxml; + +import com.novell.ldap.*; +import org.w3c.dom.*; + +/** + * Interface for hooking into LX processing. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public interface LXHook +{ + /** + * Initialize this hook. + * + * @param obj An initialization parameter passed into LXSpecs.setData + */ + public void initialize(Object obj) + throws LXException; + + /** + * Allows pre processing of an entry retrieved from LDAP + * + * @param entry The LDAP entry retrieved + * @return If false then this element will not be included in tree. + */ + public boolean prefix(LDAPEntry entry) + throws LXException; + + /** + * Allows post processing of an element retrieved from LDAP. + * + * @param entry The LDAP entry retrieved. + * @param el The DOM element constructed. + * @return If false then this element will not be included in tree. + */ + public boolean postfix(LDAPEntry entry, Element el) + throws LXException; +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXMap.java b/src/com/memberwebs/ldapxml/LXMap.java new file mode 100644 index 0000000..332ce07 --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXMap.java @@ -0,0 +1,130 @@ +package com.memberwebs.ldapxml; + +import java.io.*; +import java.util.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +import com.memberwebs.ldapxml.helpers.*; + +/** + * The in memory representation of an LX map. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXMap +{ + /** + * Constructs a new LXMap object. + */ + public LXMap() + { + m_root = null; + m_nameMap = null; + m_nameSet = null; + } + + /** + * Get the root node of the LX map. + * + * @return The root. + */ + protected final LXRoot getRoot() + { + return m_root; + } + + + /** + * Load an LX map from an XML data stream + * + * @param source The source of the XML data. + */ + public void loadMap(InputSource source) + throws LXMapException, IOException + { + try + { + XMLReader xr = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); + + // We require some form of validation on the document + // as our internal checks are lax + xr.setFeature("http://xml.org/sax/features/validation", true); + + // Parse and load the map. + LXSAXHandler handler = new LXSAXHandler(); + xr.setContentHandler(handler); + xr.setErrorHandler(handler); + xr.parse(source); + + m_root = handler.getRoot(); + m_nameMap = handler.getNameMap(); + m_nameSet = handler.getNameSet(); + } + catch(SAXException e) + { + Exception inside = e.getException(); + + if(inside != null) + { + inside.printStackTrace(); + if(inside instanceof LXMapException) + throw (LXMapException)inside; + else if(inside.getMessage() != null) + throw new LXMapException(inside.getMessage()); + else + throw new LXMapException(inside.getClass().getName()); + } + + throw new LXMapException(e.getMessage()); + } + } + + /** + * Load an LX map from a uri. + * + * @param uri The fully qualified uri of the file. + */ + public void loadMap(String uri) + throws LXMapException, IOException + { + loadMap(new InputSource(uri)); + } + + /** + * Get public (XML) to private (LDAP) name mappings + * for the first entry. + * + * @return The name map. + */ + public final Map getNameMap() + { + return m_nameMap; + } + + /** + * Get the minimum set of LDAP attributes required + * to retrieve items via this map. + */ + protected final Set getNameSet() + { + return m_nameSet; + } + + public final boolean isLoaded() + { + return m_root != null; + } + + // The root of the LX map + private LXRoot m_root; + + // Cache of the name map + private Map m_nameMap; + + // Cache of the name set + private Set m_nameSet; +} + diff --git a/src/com/memberwebs/ldapxml/LXMapException.java b/src/com/memberwebs/ldapxml/LXMapException.java new file mode 100644 index 0000000..91051d5 --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXMapException.java @@ -0,0 +1,16 @@ +package com.memberwebs.ldapxml; + +/** + * Thrown when an error occurs parsing an LX map file. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXMapException + extends LXException +{ + public LXMapException(String message) + { + super(message); + } +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXReader.java b/src/com/memberwebs/ldapxml/LXReader.java new file mode 100644 index 0000000..9477b5f --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXReader.java @@ -0,0 +1,708 @@ +package com.memberwebs.ldapxml; + +import java.io.*; +import java.util.*; + +import com.novell.ldap.*; +import org.apache.xerces.dom.*; +import org.apache.xerces.parsers.*; +import org.w3c.dom.*; + +import com.memberwebs.ldapxml.helpers.*; +import com.familymembers.util.ldap.*; +import com.familymembers.util.*; + +/** + * Uses an LX map to read data from an LDAP directory, returning + * XML in a DOM format. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXReader +{ + /** + * Creates a new LXReader object. + */ + public LXReader() + { + m_map = null; + m_connection = null; + m_hooks = new Hashtable(); + m_convert = new LXDefaultConvert(); + } + + /** + * Get the map that will be used to transform data. + * + * @return The map. + */ + public final LXMap getMap() + { + return m_map; + } + + public final void setConvert(LXConvert convert) + { + m_convert = convert; + } + + public final LXConvert getConvert() + { + return m_convert; + } + + + /** + * Set the LX map that will be used to transform data + * retrieved from the LDAP directory. + * + * @param map The map. + */ + public final void setMap(LXMap map) + throws LXException + { + if(map.getRoot() == null) + throw new LXException("Must supply a valid loaded map"); + + m_map = map; + } + + /** + * Get the LDAP connection used to retrieve data. + * + * @return The connection. + */ + public final LDAPConnection getConnection() + { + return m_connection; + } + + /** + * Set the LDAP connection to retrieve data from. + * + * @param conn The connection. + */ + public final void setConnection(LDAPConnection conn) + throws LXException + { + if(!conn.isConnected()) + throw new LXException("Must supply a valid open connection"); + + // Force a refresh of the schema as connection + // has changed. + m_schema = null; + + m_connection = conn; + } + + /** + * Search for and retrieve data matching a filter. + * + * @param doc The document from which to create elements. + * @param base Point in the LDAP tree to root search. + * @param filter The search filter. + * @return An array of retrieved elements, one for each LDAP + * entry found. + */ + public LXResults retrieveSearch(Document doc, String base, String filter) + throws LXException, LDAPException + { + return retrieveSearch(doc, base, filter, new LXSpecs(), false); + } + + public LXResults retrieveSearch(Document doc, String base, String filter, + LXSpecs specs) + throws LXException, LDAPException + { + return retrieveSearch(doc, base, filter, specs, false); + } + + /** + * Search for and retrive data matching filter with additional + * retrieval specifications. + * + * @param doc The document from which to create elements. + * @param base The point in the LDAP tree to root search. + * @param filter The search filter. + * @param batch Whether the retrieval is called in a batch + * and should update start and limit in specs + * @return An array of retrieved DOM elements, one for each LDAP + * entry found. + */ + public LXResults retrieveSearch(Document doc, String base, String filter, + LXSpecs specs, boolean batch) + throws LXException, LDAPException + { + checkInternals(); + + String[] attrs = getAttributes(specs); + String[] sort = specs.getMappedSort(m_map); + + int start = specs.getStart(); + int last = specs.getLimit() + start; + + LDAPSearchConstraints cons = new LDAPSearchConstraints(); + cons.setMaxResults(0); + + // If no sort then we can have the server limit stuff + // To get some added efficiency + if(sort == null) + cons.setMaxResults(last + 1); + + // Search tree for entries + LDAPSearchResults results = m_connection.search(base, m_connection.SCOPE_SUB, + specs.getFilter(filter), attrs, false, cons); + + if(sort != null) + results.sort(new LDAPCompareAttrNames(sort, specs.getSortDirection())); + + Vector els = new Vector(); + + if(results.getCount() > 0) + { + int number = 0; + int skipped = 0; + int retrieved = 0; + + // Idle through entries till we find the one we're supposed to + // start at + while(number < start && results.hasMoreElements()) + { + results.next(); + number++; + skipped++; + } + + // Now read each element in + while(results.hasMoreElements() && number <= last) + { + LDAPEntry entry = results.next(); + Element el = retrieveEntry(doc, entry, specs); + + // And retrieve sub elements if neccessary + if(el != null) + { + retrieveSubTree(doc, entry.getDN(), specs, el, specs.getDepth(), attrs); + els.add(el); + } + + number++; + retrieved++; + } + + if(batch) + { + start -= skipped; + specs.setStart(start < 0 ? 0 : start); + + int limit = specs.getLimit(); + limit -= retrieved; + specs.setLimit(retrieved < 0 ? 0 : limit); + } + + } + + // Create the root element etc... + LXRoot lx = m_map.getRoot(); + Element root = createElement(doc, lx); + + return new LXResults(doc, els, root); + } + + /** + * Retrieve a blank result set, with a properly + * created document, root node etc... + * + * @param doc The document from which to create elements. + * @return The blank result set. + */ + public LXResults retrieveBlank(Document doc) + throws LXException + { + if(m_map == null || m_map.getRoot() == null) + throw new LXException("Must supply a valid loaded map"); + + LXRoot lx = m_map.getRoot(); + Element root = createElement(doc, lx); + return new LXResults(doc, new Vector(), root); + } + + /** + * Retrieves a single entry from an LDAP tree. + * + * @param doc The document from which to create elements. + * @param dn The LDAP DN of the entry to retrieve. + * @return The DOM element or null if not found. + */ + public Element retrieveEntry(Document doc, String dn) + throws LXException, LDAPException + { + return retrieveEntry(doc, dn, new LXSpecs()); + } + + /** + * Retrieves a single entry from an LDAP tree with additional + * specifications. + * + * @param doc The document from which to create elements. + * @param dn The LDAP DN of the entry to retrieve. + * @param specs The additional retrieval specifications. + * @return The DOM element or null if not found. + */ + public Element retrieveEntry(Document doc, String dn, LXSpecs specs) + throws LXException, LDAPException + { + checkInternals(); + + String[] attrs = getAttributes(specs); + + // Get the entry + LDAPSearchResults results = m_connection.search(dn, + m_connection.SCOPE_BASE, specs.getFilter(), attrs, false); + + Element el = null; + + // If we got something then return it. + if(results.hasMoreElements()) + { + LDAPEntry entry = results.next(); + el = retrieveEntry(doc, entry, specs); + } + + // And get sub elements if neccessary + retrieveSubTree(doc, dn, specs, el, specs.getDepth(), attrs); + + return el; + } + + + /** + * Transform an already retrieved LDAP entry. + * + * @param doc The document from which to create elements. + * @entry The LDAP entry. + * @return The DOM element or null if not found in map. + */ + public Element retrieveEntry(Document doc, LDAPEntry entry) + throws LXException, LDAPException + { + return retrieveEntry(doc, entry, new LXSpecs()); + } + + /** + * Transform an already retrieved LDAP entry with additional specifications. + * + * @param doc The document from which to create elements. + * @param entry The LDAP entry. + * @return The DOM element retrieved or null if not found in map. + */ + public Element retrieveEntry(Document doc, LDAPEntry entry, LXSpecs specs) + throws LXException, LDAPException + { + checkInternals(); + + // Make sure we have a copy of the server schema with us + // We retrieve the whole thing for efficiency + refreshSchema(); + + LXRoot root = m_map.getRoot(); + + // Get all the classes we use them to determine a number + // of things + LDAPAttribute objectClasses = entry.getAttribute(LDAPUtil.CLASS); + if(objectClasses == null) + return null; + + String[] classes = objectClasses.getStringValueArray(); + + // Okay now we need to find out which entry it is. We use the + // name attribute of an LXEntry in order to determine this + LXEntry lxentry = null; + + for(int i = 0; i < classes.length; i++) + { + lxentry = root.getEntry(classes[i]); + if(lxentry != null) + break; + } + + // We don't create elements not found in the map + // so return null to signify this + if(lxentry == null) + return null; + + LXHook hook = getHookFor(lxentry, specs); + + // Call the hooks for this element + if(!hook.prefix(entry)) + return null; + + objectClasses = entry.getAttribute(LDAPUtil.CLASS); + classes = objectClasses.getStringValueArray(); + + // Create the entry element + Element el = createElement(doc, lxentry); + + // Okay go through all the objectClass attributes + // and "do" those classes + for(int i = 0; i < classes.length; i++) + { + String clsName = classes[i]; + + // Look up the class. We only do classes we can handle + LXClass lxclass = lxentry.getClass(clsName); + if(lxclass != null) + { + // If the class is not inline then create the class element + Element clsEl = el; + if(!lxclass.isInline()) + { + clsEl = createElement(doc, lxclass); + el.appendChild(clsEl); + } + + // Otherwise add the namespaces of the class to the parent + // element as necessary + else + { + addNamespace(lxclass, el); + } + + if(lxclass.isInclusive()) + { + // Get out the actual LDAP objectClass + LDAPObjectClassSchema objectClass = m_schema.getObjectClassSchema(clsName); + if(objectClass == null) + throw new LXException("invalid objectClass in schema: " + clsName); + + // Okay now go through the attributes and convert them + // into XML + retrieveAttributes(objectClass.getRequiredAttributes(), + clsEl, entry, lxclass, specs); + retrieveAttributes(objectClass.getOptionalAttributes(), + clsEl, entry, lxclass, specs); + } + else + { + // Just get the attributes that were asked for + Vector attrs = new Vector(); + Enumeration e = lxclass.getChildNames(); + while(e.hasMoreElements()) + attrs.add(e.nextElement()); + + retrieveAttributes(StringUtil.toStringArray(attrs), + clsEl, entry, lxclass, specs); + } + } + } + + // Call the hooks for this element + if(!hook.postfix(entry, el)) + el = null; + + return el; + } + + /** + * Retrieve given attributes and add them to a DOM element as appropriate + * + * @param attributes Array of attributes to retrieve. + * @param clsEl The parent class element. + * @param entry The LDAP entry to retrieve attributes from. + * @param lxcls The class these attributes belong to. + * @param hook The hook for conversions. + * @param specs Additional retrieval specifications. + */ + private void retrieveAttributes(String[] attributes, Element clsEl, + LDAPEntry entry, LXClass lxcls, + LXSpecs specs) + throws LXException + { + // We need this for later node creation + Document doc = clsEl.getOwnerDocument(); + + // Get language stuff initialized + String language = specs.getLanguage(); + + for(int i = 0; i < attributes.length; i++) + { + + // Retrieve the attribute based on language + LDAPAttribute ldapattr = + LDAPUtil.getAttributeForLang(entry, attributes[i], language); + + // Each one can have multiple values + LXAttribute lxattr = lxcls.getAttribute(attributes[i]); + if(ldapattr != null && lxattr != null) + { + + // Get the syntax for conversion functions + LDAPAttributeSchema schattr = m_schema.getAttributeSchema(ldapattr.getName()); + String syntax = ""; + + if(schattr != null) + syntax = schattr.getSyntaxString(); + + Enumeration values = ldapattr.getStringValues(); + + // XML child elements handled here. + if(lxattr.isElement()) + { + // Just convert and append one child element + // for each value. + while(values.hasMoreElements()) + { + String val = m_convert.parse(ldapattr.getName(), + syntax, (String)values.nextElement()); + if(val == null) + continue; + + Element attrEl = createElement(doc, lxattr); + attrEl.appendChild(doc.createTextNode(val)); + clsEl.appendChild(attrEl); + } + } + + // XML Attributes handled here. + else + { + // All values are concatenated with spaces + // and set as the attribute. + String value = ""; + + while(values.hasMoreElements()) + { + String val = m_convert.parse(ldapattr.getName(), + syntax, (String)values.nextElement()); + if(val == null) + continue; + + if(value.length() != 0) + value += " "; + + value += val; + } + + clsEl.setAttributeNS(lxattr.getNamespace(), lxattr.getXmlName(), value); + addNamespace(lxattr, clsEl); + } + } + } + } + + + /** + * Helper function to create an element from an LX object. + * Uses namespace information as appropriate. + * + * @param doc The document from which to create the element. + * @param lx The LX object. + * @return The DOM element. + */ + private Element createElement(Document doc, LXBase lx) + { + Element el = doc.createElementNS(lx.getNamespace(), lx.getXmlName()); + addNamespace(lx, el); + return el; + } + + + /** + * Add namespace (xmlns) attribute to element as necessary. + * + * @param lx The LX object + * @param el The element to add the attribute to + */ + private void addNamespace(LXBase lx, Element el) + { + // Passing true to getNamespace forces it + // only to return namespaces for this LX object + String nameSpace = lx.getNamespace(true); + + if(nameSpace != null) + { + String attr = "xmlns"; + + String prefix = lx.getPrefix(); + if(prefix != null) + attr += ":" + prefix; + + el.setAttributeNS("http://www.w3.org/2000/xmlns/", attr, nameSpace); + } + } + + /** + * Refresh the cached LDAP schema if necessary. + */ + private void refreshSchema() + throws LDAPException, LXException + { + refreshSchema(false); + } + + /** + * Refresh the cached LDAP schema. + * + * @param force Force a refresh even if not necessary. + */ + private void refreshSchema(boolean force) + throws LDAPException, LXException + { + checkInternals(); + + if(m_schema == null || force) + { + m_schema = new LDAPSchema(); + force = true; + } + + if(force) + m_schema.fetchSchema(m_connection); + } + + /** + * Retrieve sub elements of an LDAP entry to a specified depth. + * + * @param doc Document to create elements from. + * @param dn The parent LDAP entry. + * @param specs Additional retrieval specifications. + * @param el The parent DOM element. + * @param depth The depth to go down in the tree. + */ + private void retrieveSubTree(Document doc, String dn, LXSpecs specs, + Element el, int depth, String[] attrs) + throws LXException, LDAPException + { + // Make sure we need to retrieve this level + // If depth has gone down to zero, then no need + if(el != null && depth > 0) + { + // Get everything at one level down + LDAPSearchResults results = m_connection.search(dn, + m_connection.SCOPE_ONE, specs.getFilter(), attrs, false); + + String[] sort = specs.getMappedSort(m_map); + if(sort != null) + results.sort(new LDAPCompareAttrNames(sort, specs.getSortDirection())); + + // Now retrieve each + while(results.hasMoreElements()) + { + LDAPEntry entry = results.next(); + Element sub = retrieveEntry(doc, entry, specs); + + // And it's sub tree + if(sub != null) + { + retrieveSubTree(doc, entry.getDN(), specs, sub, + depth - 1, attrs); + el.appendChild(sub); + } + } + + } + } + + /** + * Retrieves the hook for a given element. Caches the + * hooks. When no hook is present returns a default hook. + * + * @param base The LX object to retrieve a hook for. + */ + private LXHook getHookFor(LXBase base, LXSpecs specs) + throws LXException + { + String hook = base.getHook(); + if(hook == null) + hook = ""; + + LXHook hk = (LXHook)m_hooks.get(hook); + if(hk == null) + { + try + { + if(hook.length() == 0) + hk = new LXDefaultHook(); + else + hk = (LXHook)Class.forName(hook).newInstance(); + } + catch(ClassNotFoundException e) + { + + } + catch(IllegalAccessException e) + { + + } + catch(InstantiationException e) + { + + } + + if(hk == null) + throw new LXException("couldn't instantiate hook class: " + hook); + + hk.initialize(specs.getData()); + m_hooks.put(hook, hk); + } + + return hk; + } + + /** + * Helper to do some sanity checking on required calling + * procedures. + */ + private final void checkInternals() + throws LXException + { + if(m_connection == null || !m_connection.isConnected()) + throw new LXException("Must supply a valid open connection"); + + if(m_map == null || m_map.getRoot() == null) + throw new LXException("Must supply a valid loaded map"); + } + + /** + * Get the list of attributes required for a map and sort + */ + private String[] getAttributes(LXSpecs specs) + { + String[] sort = specs.getMappedSort(m_map); + + Set names = m_map.getNameSet(); + if(names != null && sort != null) + names.addAll(Arrays.asList(sort)); + + if(names != null) + names.add(LDAPUtil.CLASS); + + if(names == null) + { + String[] ret = { "*", "+" }; + return ret; + } + + else + { + names.add("+"); + return StringUtil.toStringArray(names); + } + } + + // The LDAP connection we're using + private LDAPConnection m_connection; + + // Cache of schema for the connection + private LDAPSchema m_schema; + + // The LX map we're using to transform data + private LXMap m_map; + + // Cache of all the loaded hooks so far + private Hashtable m_hooks; + + // The converter + private LXConvert m_convert; +}; \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXResults.java b/src/com/memberwebs/ldapxml/LXResults.java new file mode 100644 index 0000000..b07d4af --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXResults.java @@ -0,0 +1,123 @@ +package com.memberwebs.ldapxml; + +import java.util.*; + +import org.w3c.dom.*; + +/** + * Represents a set of elements retrieved from an LDAP tree + * by the LXReader. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXResults +{ + /** + * Construct a new LXResults object + * + * @param doc The owner document for the elements + * @param els The elements + */ + LXResults(Document doc, Vector els, Element root) + { + m_document = doc; + m_elements = els; + m_root = root; + } + + /** + * Get the number of elements + * + * @return The number of elements. + */ + public final int getLength() + { + return m_elements.size(); + } + + /** + * Get a certain element in the results. + * + * @param i The index to the element. + * @return The element. + */ + public final Element getResult(int i) + { + return (Element)m_elements.elementAt(i); + } + + /** + * Get all the elements as an array + * + * @return All the result elements. + */ + public final Element[] getElements() + { + Object[] values = m_elements.toArray(); + Element[] array = new Element[values.length]; + for(int i = 0; i < array.length; i++) + array[i] = (Element)values[i]; + + return array; + } + + /** + * Get the elements' owner document + * + * @return The document. + */ + public final Document getOwnerDocument() + { + return m_document; + } + + /** + * Get the root document element defined in map + * + * @return The root element. + */ + public final Element getRootElement() + { + return m_root; + } + + /** + * Puts together the result set into the result document. + * + * @return The assembled document. + */ + public final Document assembleDocument() + { + while(m_document.hasChildNodes()) + m_document.removeChild(m_document.getFirstChild()); + + Node root = m_root.cloneNode(true); + + for(int i = 0; i < m_elements.size(); i++) + root.appendChild((Element)m_elements.elementAt(i)); + + m_document.appendChild(root); + + return m_document; + } + + /** + * Add the results from another set to this one. + * + * @param results The results to add. + */ + public final void addResults(LXResults results) + { + m_elements.addAll(results.m_elements); + } + + // The elements that make up the results + private Vector m_elements; + + // The parent document of the results + private Document m_document; + + // The root element + private Element m_root; +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/LXSpecs.java b/src/com/memberwebs/ldapxml/LXSpecs.java new file mode 100644 index 0000000..98f1d55 --- /dev/null +++ b/src/com/memberwebs/ldapxml/LXSpecs.java @@ -0,0 +1,256 @@ +package com.memberwebs.ldapxml; + +import java.util.*; + +import com.memberwebs.ldapxml.helpers.*; +import com.familymembers.util.ldap.*; + +/** + * Additional specifications for retrieving data from an + * LDAP directory. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXSpecs +{ + /** + * Don't retrieve any sub entries. + */ + public static final int DEPTH_BASE = 0; + + /** + * Retrive only direct children of the referenced entry. + */ + public static final int DEPTH_SINGLE = 1; + + /** + * Retrieve all sub entries. + */ + public static final int DEPTH_INFINITE = 0xFFFFFFFF; + + + private static final String LANG = "lang-"; + + /** + * Construct a new LXSpecs object. + */ + public LXSpecs() + { + m_filter = LDAPUtil.FILTER_ALL; + m_depth = DEPTH_BASE; + m_sort = null; + m_data = null; + m_language = null; + m_start = 0; + m_limit = Integer.MAX_VALUE; + } + + public final int getStart() + { + return m_start; + } + + public final int getLimit() + { + return m_limit; + } + + public final void setStart(int start) + { + m_start = start; + } + + public final void setLimit(int limit) + { + m_limit = limit; + } + + /** + * Get preferred language. + * + * @return The language or null if none set. + */ + public final String getLanguage() + { + return m_language; + } + + /** + * Set preferred language for LDAP retrieval. + * + * @param language The language or null for no preference. + */ + public final void setLanguage(String language) + { + m_language = language; + + if(m_language != null && !m_language.startsWith(LANG)) + m_language = LANG + m_language; + } + + /** + * Returns the depth to which entries are recursively + * retrieved from the LDAP directory. + * + * @return The depth. + */ + public final int getDepth() + { + return m_depth; + } + + /** + * Sets the depth to which entries are recursively + * retrieved from the LDAP directory. + * + * @return The depth. + */ + public final void setDepth(int depth) + { + m_depth = depth; + } + + /** + * Gets the additional filter when searching for entries. + * + * @return The filter. + */ + public final String getFilter() + { + return m_filter == null ? LDAPUtil.FILTER_ALL : m_filter; + } + + /** + * Gets the filter when searching for entries, combining it + * with another filter. + * + * @param prevFilter The other filter. + * @return The combined filter. + */ + public final String getFilter(String prevFilter) + { + String filter = LDAPUtil.combineFilters(m_filter, prevFilter); + return filter == null ? LDAPUtil.FILTER_ALL : filter; + } + + /** + * Set an additional filter used when searching for entries. + * + * @param filter The filter. + */ + public final void setFilter(String filter) + { + m_filter = LDAPUtil.combineFilters(filter, null); + } + + /** + * Get the requested sort order. + * + * @return The sort order, or null if none present. + */ + public final String[] getSort() + { + if(m_sort != null && m_sort.length > 0) + return m_sort; + else + return null; + } + + /** + * Get the sort directions (ascending, descending) for the + * sort order. + * + * @return An array of sort directions. + */ + public final boolean[] getSortDirection() + { + if(m_direc != null && m_direc.length > 0) + return m_direc; + else + return null; + } + + /** + * Set the sort order. Attribute names prefixed with '-' are + * treated as descending. + * + * @param sort The sort order. + */ + public final void setSort(String[] sort) + { + m_mappedSort = null; + + m_sort = new String[sort.length]; + m_direc = new boolean[sort.length]; + + for(int i = 0; i < sort.length; i++) + { + boolean desc = sort[i].startsWith("-"); + if(desc) + m_sort[i] = sort[i].substring(1); + else + m_sort[i] = sort[i]; + + m_direc[i] = !desc; + } + } + + public final Object getData() + { + return m_data; + } + + public final void setData(Object data) + { + m_data = data; + } + + /** + * Translate the sort order with mapped LDAP attribute + * names retrieved from the map. + * + * @param map The LX Map to get the attribute names from. + * @return The translated sort order. + */ + protected final String[] getMappedSort(LXMap map) + { + if(m_mappedSort == null) + { + if(m_sort != null && m_sort.length > 0) + { + m_mappedSort = new String[m_sort.length]; + + Map nameMap = map.getNameMap(); + for(int i = 0; i < m_sort.length; i++) + { + String name = (String)nameMap.get(m_sort[i]); + m_mappedSort[i] = name == null ? m_sort[i] : name; + } + } + } + return m_mappedSort; + } + + // The additional filter + private String m_filter; + + // The depth to which to recursively retrieve sub entries + private int m_depth; + + // The starting point to retrieve entries + private int m_start; + // The number of entries to retrieve + private int m_limit; + + // The sort order and direction + private String[] m_sort; + private String[] m_mappedSort; + private boolean[] m_direc; + + // The preferred language + private String m_language; + + // Data for hooks + private Object m_data; +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXAttribute.java b/src/com/memberwebs/ldapxml/helpers/LXAttribute.java new file mode 100644 index 0000000..1e75da5 --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXAttribute.java @@ -0,0 +1,43 @@ +package com.memberwebs.ldapxml.helpers; + +import java.io.*; +import java.util.*; + +import org.w3c.dom.*; + +/** + * Represents an <attribute> or <element> in an LX map. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXAttribute + extends LXBase +{ + /** + * Construct an LXAttribute object. Can only be constructed + * from within the package. + * + * @param parent The parent in the map tree. + * @param isElement Set to true if this is object represents an + * <element>, otherwise an <attribute>. + */ + LXAttribute(LXBase parent, boolean isElement) + { + super(parent); + m_isElement = isElement; + } + + /** + * Check whether this object represents an <element> + * or <attribute>. + * + * @return The state. + */ + public final boolean isElement() + { + return m_isElement; + } + + protected boolean m_isElement; +}; \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXBase.java b/src/com/memberwebs/ldapxml/helpers/LXBase.java new file mode 100644 index 0000000..2a42c35 --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXBase.java @@ -0,0 +1,226 @@ +package com.memberwebs.ldapxml.helpers; + +import java.io.*; +import java.util.*; + +import org.w3c.dom.*; + +/** + * A base class for all objects in an LX map tree. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXBase +{ + /** + * Constructs an LXBase object. + * + * @param parent The parent object in the LX map tree. + */ + public LXBase(LXBase parent) + { + m_name = null; + m_xmlName = null; + m_nameSpace = null; + m_ns = null; + m_parent = parent; + m_isUseable = true; + + // If we have a parent and it doesn't have any children set + // Then give it child capabilities. + if(parent != null && parent.m_children == null) + parent.m_children = new Hashtable(); + } + + /** + * Get the full XML name for this object. This includes any namespace + * prefixes prepended to the name. + * + * @return The XML name. + */ + public String getXmlName() + { + String xmlName = m_xmlName; + if(xmlName == null) + xmlName = m_name; + + if(xmlName != null) + { + String prefix = getPrefix(); + if(prefix != null) + xmlName = prefix + ":" + xmlName; + } + + return xmlName; + } + + + /** + * Get the name of this object. + * + * @return The name. + */ + public final String getName() + { + return m_name; + } + + + /** + * Set the name of this object. Updates the parent (if any) + * to reflect this change in tree structure. + * + * @param name The new name + */ + public void setName(String name) + { + if(m_name != null && m_parent != null) + m_parent.m_children.remove(m_name); + + if(name != null) + { + if(m_parent != null) + m_parent.m_children.put(name, this); + + m_name = name; + } + } + + /** + * Get the namespace prefix for this object. If this object has + * no namespaces info set, returns the parent objects namespace + * prefix. + * + * @return The namespace prefix, or null if there is no prefix. + */ + public String getPrefix() + { + // If we have a namespace then return + // the ns value regardless + if(m_nameSpace != null) + return m_ns; + + if(m_ns != null) + return m_ns; + + if(m_parent != null) + return m_parent.getPrefix(); + + return null; + } + + /** + * Get the full namespace URI for this object. + * + * @param here If set to true, then parent namespaces will + * be returned if none is set on this object. + * @return The namespace. + */ + public String getNamespace(boolean here) + { + if(m_nameSpace != null || here) + return m_nameSpace; + + if(m_parent != null) + return m_parent.getNamespace(); + + return null; + } + + /** + * Get the full namespace URI for this node. + * If this object has no namespace, it's parent will + * be queried. + * + * @return The namespace. + */ + public String getNamespace() + { + return getNamespace(false); + } + + /** + * Get this object's parent in the LX map tree. + * + * @return The parent, or null if this object has no parent. + */ + public final LXBase getParent() + { + return m_parent; + } + + /** + * Get a child of this object in the LX map tree. + * + * @param name The child's name. + * @return The child, or null if no such child is present. + */ + public final LXBase getChild(String name) + { + if(m_children == null) + return null; + + return (LXBase)m_children.get(name); + } + + /** + * Get the names of all the child objects. + * + * @return An enumeraton of the child attribute names. + */ + public final Enumeration getChildNames() + { + if(m_children == null) + return null; + + return m_children.keys(); + } + + /** + * Check whether this object has been marked unusable. + * + * @return Usability status. + */ + public final boolean isUseable() + { + return m_isUseable; + } + + /** + * Get the LX hook for this object. If this + * object has no hook, then the parent will be queried. + * + * @return The class name of the LX hook. + */ + public String getHook() + { + if(m_hook == null && m_parent != null) + return m_parent.getHook(); + + return m_hook; + } + + // The name to be used when creating XML for this object. + String m_xmlName; + + // Namespace information for this object + String m_nameSpace; + String m_ns; + + // The hook for this object and below + String m_hook; + + // Is this object useable or not + boolean m_isUseable; + + // This object's parent in the LX map + private LXBase m_parent; + + // This object's name + private String m_name; + + // Children in the LX map tree of this object. + private Hashtable m_children; +}; + diff --git a/src/com/memberwebs/ldapxml/helpers/LXClass.java b/src/com/memberwebs/ldapxml/helpers/LXClass.java new file mode 100644 index 0000000..a88dba1 --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXClass.java @@ -0,0 +1,81 @@ +package com.memberwebs.ldapxml.helpers; + +import java.io.*; +import java.util.*; + +import org.w3c.dom.*; + +/** + * Represents a <class> in an LX map. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXClass + extends LXBase +{ + /** + * Construct a new LXClass. + * + * @param parent This class's parent object in the LX map tree. + */ + LXClass(LXBase parent) + { + super(parent); + m_isInline = true; + m_isInclusive = true; + } + + /** + * Returns an attribute of this class. + * + * @param name The attribute's name + * @return The attrtibute, or null if no such attribute is present. + */ + public LXAttribute getAttribute(String name) + { + LXAttribute attr = (LXAttribute)getChild(name); + + // If there is no such attribute and we're allowed + // to just include everything, then... + if(attr == null) + { + if(m_isInclusive) + { + // ... just create it on the fly. + attr = new LXAttribute(this, true); + attr.setName(name); + } + } + + // Check if we haven't been marked as unusable + else if(!attr.isUseable()) + { + attr = null; + } + + return attr; + } + + /** + * Checks if this class' attributes go inline + * or if the class gets it's own element in XML. + * + * @return Inline or not. + */ + public final boolean isInline() + { + return m_isInline; + } + + public final boolean isInclusive() + { + return m_isInclusive; + } + + // Include all attributes not otherwise specified + boolean m_isInclusive; + + // Create a class element around attributes in XML. + boolean m_isInline; +}; \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXComparator.java b/src/com/memberwebs/ldapxml/helpers/LXComparator.java new file mode 100644 index 0000000..c25326c --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXComparator.java @@ -0,0 +1,141 @@ +package com.memberwebs.ldapxml.helpers; + +import java.util.*; + +import org.w3c.dom.*; + +/** + * Used to sort XML elements according to a specified sort order + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXComparator + implements Comparator +{ + /** + * Construct a new LXComparator + * + * @param attrs The element or attribute names to sort by + * @param directions The sort direction for each attr + */ + public LXComparator(String[] attrs, boolean[] directions) + { + m_attrs = attrs; + m_directions = directions; + } + + /** + * Compare two objects and return -1, 0 or 1 + * + * @param o1 The first object + * @param o2 The second object + */ + public int compare(Object o1, Object o2) + { + Element e1 = (Element)o1; + Element e2 = (Element)o2; + + for(int i = 0; i < m_attrs.length; i++) + { + String v1 = getSubElementValue(e1, m_attrs[i]); + String v2 = getSubElementValue(e2, m_attrs[i]); + + int ret; + + if(v1 == null && v2 == null) + ret = 0; + else if(v1 == null) + ret = -1; + else if(v2 == null) + ret = 1; + else + ret = v1.compareTo(v2); + + if(ret != 0) + return m_directions[i] ? ret : -ret; + } + + return 0; + } + + /** + * Check whether another comparator is equal to this one. + * + * @param obj The other comparator + * @return Whether equal or not + */ + public boolean equals(Object obj) + { + LXComparator other = (LXComparator)obj; + return m_attrs.equals(other.m_attrs) && + m_directions.equals(other.m_directions); + } + + /** + * Get a value from an element. The value is either retrieved + * from a sub element of the appropriate name or an attribute + * of the same name. + * + * @param el The element to retrieve from. + * @param attr The value name + */ + private static String getSubElementValue(Element el, String attr) + { + // First we search sub elements and then attributes + if(el.hasChildNodes()) + { + NodeList children = el.getChildNodes(); + for(int i = 0; i < children.getLength(); i++) + { + Node child = children.item(i); + if(child != null && + child.getNodeType() == Node.ELEMENT_NODE && + child.getNodeName().equals(attr)) + { + return getElementValue(el); + } + } + } + + // Otherwise try to get the attribute by the same name + return el.getAttribute(attr); + } + + /** + * Get the complete text value of an element. + * + * @param el The element to retrieve value for + * @return The value. + */ + private static String getElementValue(Element el) + { + String value = ""; + + if(el.hasChildNodes()) + { + NodeList children = el.getChildNodes(); + for(int i = 0; i < children.getLength(); i++) + { + Node child = children.item(i); + if(child != null) + { + switch(child.getNodeType()) + { + case Node.ELEMENT_NODE: + value += getElementValue((Element)child); + break; + case Node.TEXT_NODE: + value += child.getNodeValue(); + break; + } + } + } + } + + return value; + } + + private String[] m_attrs; + private boolean[] m_directions; +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXDefaultConvert.java b/src/com/memberwebs/ldapxml/helpers/LXDefaultConvert.java new file mode 100644 index 0000000..3c2b64b --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXDefaultConvert.java @@ -0,0 +1,19 @@ +package com.memberwebs.ldapxml.helpers; + +import com.memberwebs.ldapxml.*; + +/** + * A default converter for the LX Reader routines + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXDefaultConvert + implements LXConvert +{ + public String parse(String name, String syntax, String value) + throws LXException + { + return value; + } +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXDefaultHook.java b/src/com/memberwebs/ldapxml/helpers/LXDefaultHook.java new file mode 100644 index 0000000..552c4bc --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXDefaultHook.java @@ -0,0 +1,66 @@ +package com.memberwebs.ldapxml.helpers; + +import com.novell.ldap.*; +import org.w3c.dom.*; + +import com.memberwebs.ldapxml.*; + +/** + * Default implementation of LX processing hook. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXDefaultHook + implements LXHook +{ + /** + * No initialization necessary. + * + * @param obj Initialization parameter. + */ + public void initialize(Object obj) + throws LXException + { + + } + + /** + * Allows pre processing of an entry retrieved from LDAP + * + * @param entry The LDAP entry retrieved + * @return If false then this element will not be included in tree. + */ + public boolean prefix(LDAPEntry entry) + throws LXException + { + return true; + } + + + /** + * Does no post processing on element. + * + * @param entry The LDAP entry read. + * @param el The DOM element constructed. + * @return Always returns true. + */ + public boolean postfix(LDAPEntry entry, Element el) + throws LXException + { + return true; + } + + /** + * No conversion done for attributes. + * + * @param attrName The attribute name. + * @param value The attribute value. + * @return The converted value. + */ + public String convert(String attrName, String syntax, String value) + throws LXException + { + return value; + } +} \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXEntry.java b/src/com/memberwebs/ldapxml/helpers/LXEntry.java new file mode 100644 index 0000000..28722ef --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXEntry.java @@ -0,0 +1,43 @@ +package com.memberwebs.ldapxml.helpers; + +import java.io.*; +import java.util.*; + +import org.w3c.dom.*; + +/** + * Represents an <entry> object in an LX map. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXEntry + extends LXBase +{ + /** + * Constructs a new LXEntry object. + * + * @param parent The parent of this entry in the LX map tree. + */ + LXEntry(LXBase parent) + { + super(parent); + } + + /** + * Gets a class in this entry. + * + * @param name The name of the class. + * @return The class, or null if no such class is present. + */ + public LXClass getClass(String name) + { + LXClass cls = (LXClass)getChild(name); + + if(cls != null && !cls.isUseable()) + cls = null; + + return cls; + } + +}; \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXRoot.java b/src/com/memberwebs/ldapxml/helpers/LXRoot.java new file mode 100644 index 0000000..057671c --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXRoot.java @@ -0,0 +1,40 @@ +package com.memberwebs.ldapxml.helpers; + +import java.io.*; +import java.util.*; + +import org.w3c.dom.*; + +/** + * Represents the root of an LX map + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public class LXRoot + extends LXBase +{ + /** + * Constructs a new LXRoot object. + */ + LXRoot() + { + super(null); + } + + /** + * Get an entry in this LX map by name. + * + * @param name The entry name. + * @return The entry, or null if no such entry is present + */ + public LXEntry getEntry(String name) + { + LXEntry entry = (LXEntry)getChild(name); + + if(entry != null && !entry.isUseable()) + entry = null; + + return entry; + } +}; \ No newline at end of file diff --git a/src/com/memberwebs/ldapxml/helpers/LXSAXHandler.java b/src/com/memberwebs/ldapxml/helpers/LXSAXHandler.java new file mode 100644 index 0000000..fc3adab --- /dev/null +++ b/src/com/memberwebs/ldapxml/helpers/LXSAXHandler.java @@ -0,0 +1,447 @@ +package com.memberwebs.ldapxml.helpers; + +import java.util.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +import com.memberwebs.ldapxml.*; + +/** + * Parses an XML file into an in memory LX map representation. + * + * @author nielsen@memberwebs.com + * @version 0.5 + */ +public 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"; + + + /** + * 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_root = 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 LXRoot) + { + 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_root = new LXRoot(); + m_cur = m_root; + + 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_nameSet = null; + 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 root of the resulting LX map. + * + * @return The root of the map. + */ + public final LXRoot getRoot() + { + return m_root; + } + + /** + * 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.contains(base.m_xmlName)) + m_nameMap.put(base.m_xmlName, name); + + String xmlName = base.getXmlName(); + if(xmlName != null && !m_nameMap.contains(xmlName)) + m_nameMap.put(xmlName, name); + + if(m_nameSet != null) + m_nameSet.add(name); + } + + // The root of the map being built + private LXRoot m_root; + + // 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; +} \ No newline at end of file -- cgit v1.2.3