summaryrefslogtreecommitdiff
path: root/src/com/memberwebs
diff options
context:
space:
mode:
authorStef <stef@ws.local>2003-11-26 02:12:18 +0000
committerStef <stef@ws.local>2003-11-26 02:12:18 +0000
commitf2839912966174f0f0bdfd0d607f83d4a8aee271 (patch)
tree741bf067468837e9a30adcecd08c5ea7d915a1fb /src/com/memberwebs
parent507524b97ef3bedb42f6c15ec93eedff8ee4b150 (diff)
Initial Import
Diffstat (limited to 'src/com/memberwebs')
-rw-r--r--src/com/memberwebs/ldapxml/LXConvert.java13
-rw-r--r--src/com/memberwebs/ldapxml/LXException.java34
-rw-r--r--src/com/memberwebs/ldapxml/LXHook.java40
-rw-r--r--src/com/memberwebs/ldapxml/LXMap.java130
-rw-r--r--src/com/memberwebs/ldapxml/LXMapException.java16
-rw-r--r--src/com/memberwebs/ldapxml/LXReader.java708
-rw-r--r--src/com/memberwebs/ldapxml/LXResults.java123
-rw-r--r--src/com/memberwebs/ldapxml/LXSpecs.java256
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXAttribute.java43
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXBase.java226
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXClass.java81
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXComparator.java141
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXDefaultConvert.java19
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXDefaultHook.java66
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXEntry.java43
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXRoot.java40
-rw-r--r--src/com/memberwebs/ldapxml/helpers/LXSAXHandler.java447
17 files changed, 2426 insertions, 0 deletions
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 &lt;attribute&gt; or &lt;element&gt; 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
+ * &lt;element&gt;, otherwise an &lt;attribute&gt;.
+ */
+ LXAttribute(LXBase parent, boolean isElement)
+ {
+ super(parent);
+ m_isElement = isElement;
+ }
+
+ /**
+ * Check whether this object represents an &lt;element&gt;
+ * or &lt;attribute&gt;.
+ *
+ * @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 &lt;class&gt; 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 &lt;entry&gt 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