summaryrefslogtreecommitdiff
path: root/src/com/memberwebs/ldapxml/LXReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/memberwebs/ldapxml/LXReader.java')
-rw-r--r--src/com/memberwebs/ldapxml/LXReader.java708
1 files changed, 708 insertions, 0 deletions
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