diff options
Diffstat (limited to 'src/com/memberwebs/ldapxml/LXReader.java')
-rw-r--r-- | src/com/memberwebs/ldapxml/LXReader.java | 708 |
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 |