package com.idega.core.ldap.client.jndi; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.Context; import javax.naming.Name; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; /** * <p>The BasicOps class contains methods for performing basic * directory operations. Errors are generally caught and handled locally, * although return codes usually indicate the general success status of * operations. </p> * * <p>Two methods, error() and log() are defined. These are intended to be * over-ridden by programs wishing application specific handling of these * (i.e. for more sensible user output than System.out.println()...).</p */ public class BasicOps { private static final String DEFAULT_CTX = "com.sun.jndi.ldap.LdapCtxFactory"; private static final String DEFAULT_DSML_CTX = "com.sun.jndi.dsmlv2.soap.DsmlSoapCtxFactory"; private final static Logger log = Logger.getLogger("com.idega.core.ldap.client.jndi.BasicOps"); private Attributes schema = null; protected DirContext ctx = null; protected ConnectionData connectionData = null; protected static int ldapVersion = -1; // ldap version of the current connection (-1 = not connected) String errorMsg; //TE: a record of the last error msg. Exception errorException = null; //TE: a record of the last exception. /** * Initialise a Basic Operation object with a context. */ public BasicOps(DirContext c) { this.ctx = c; } /** * Factory Method to create BasicOps objects, initialised * with an ldap context created from the connectionData, * and maintaining a reference to that connectionData. * * @param cData the details of the directory to connect to * @return a BasicOps object. */ public static BasicOps getInstance(ConnectionData cData) throws NamingException { BasicOps newObject = new BasicOps(openContext(cData)); newObject.setConnectionData(cData); return newObject; } /** * Sets the details of the connection Data used to make * the ldap context. * * @param cData the ldap connection details */ public void setConnectionData(ConnectionData cData) { this.connectionData = cData; } /** * Open an initial context. * Will open an initial context which can then be used to construct a * BasicOps object. Note that this method may take some time to return. * * @param connectionData a data object contain all the connection details. */ public static DirContext openContext(ConnectionData connectionData) throws NamingException { // sanity check if (connectionData.url == null) { throw new NamingException("URL not specified in openContext()!"); } if (connectionData.version <2 || connectionData.version>3) { throw new NamingException("Incorrect ldap Version! (was " + connectionData.version + ")"); } if (connectionData.useSSL && (connectionData.cacerts == null)) { throw new NamingException("Cannot use SSL without a trusted CA certificates JKS file."); } if (connectionData.referralType == null) { connectionData.referralType = "follow"; // not an error not to specify this. } if (connectionData.aliasType == null) { connectionData.aliasType = "finding"; // not an error not to specify this } if ("followthrowignore".indexOf(connectionData.referralType) == -1) { throw new NamingException("unknown referral type: " + connectionData.referralType + " (setting to 'follow')"); } Properties env = new Properties(); // ldap version env.put("java.naming.ldap.version", String.valueOf(connectionData.version) ); // ignored for DSML // general default parameters log.log(Level.FINER,"connection protocol = " + connectionData.protocol); if (connectionData.protocol == connectionData.LDAP) { env.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_CTX); //TE: Set the property to keep RDN env.put("java.naming.ldap.deleteRDN", "false"); } else if (connectionData.protocol == connectionData.DSML) { //TE: Set the property to keep RDN env.put("java.naming.ldap.deleteRDN", "false"); env.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_DSML_CTX); // env.put(Context.PROVIDER_URL, "http://betch01:6666/axis/services/DSML"); // could add a baseDN here if desired } else { throw new NamingException("Unknown protocol '" + connectionData.protocol + "' encountered in com.ca.commons.jndi.BasicOps"); } env.put(Context.PROVIDER_URL, connectionData.url); // could add a baseDN here if desired //TE: was 'follow' by default, could be: follow, ignore, throw.... //TE: this method should also throw a ReferralException which is what happens if the referral is 'throw'... env.put(Context.REFERRAL, connectionData.referralType); env.put("java.naming.ldap.attributes.binary", "photo jpegphoto jpegPhoto"); // host name and port //System.out.println("ignoring provider url: '" + connectionData.url + "'"); // alias handling env.put("java.naming.ldap.derefAliases", connectionData.aliasType); if (connectionData.tracing) { env.put("com.sun.jndi.ldap.trace.ber", System.err); // would be attractive, but doesn't seem to work... } if (connectionData.userDN != null && connectionData.pwd != null) // Set up for simple authentication { // if the user is a Manager env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, connectionData.userDN); env.put(Context.SECURITY_CREDENTIALS, new String(connectionData.pwd)); } else { env.put(Context.SECURITY_AUTHENTICATION, "none"); // no authentication } if (connectionData.useSSL) // Specify SSL as the security protocol (others are possible) { env.put(Context.SECURITY_PROTOCOL, "ssl"); if (connectionData.cacerts != null) { try { // Initialise the SSL Socket Factory JndiSocketFactory.init(connectionData.cacerts, connectionData.clientcerts, connectionData.caKeystorePwd, connectionData.clientKeystorePwd, connectionData.caKeystoreType, connectionData.clientKeystoreType); // try to use client authentication if a clientcert keystore and pwd supplied if (connectionData.clientcerts != null && (connectionData.clientKeystorePwd != null && connectionData.clientKeystorePwd.length > 0)) { env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL"); // Use sasl external (i.e., certificate) auth } env.put("java.naming.ldap.factory.socket", "com.ca.commons.jndi.JndiSocketFactory"); // Specify SSL socket factory } catch (Exception e) { String msg = "Error opening SSL connection: "; if (e.getMessage() != null) { msg += e.getMessage(); } e.printStackTrace(); throw new NamingException(msg); } } } // create the connection ! return openContext(env); } /** * This static ftn. can be used to open an initial context (which can then * be used to construct a BasicOps object). Note that this ftn may take some * time to return... * * @param version the LDAP Version (2 or 3) being used. * @param host the LDAP server name. * @param port the LDAP server port (default 389) being used. * @param user the Manager User's DN - (is null if user is not manager) * @param pwd the Manager User's password - (is null if user is not manager) * @param tracing whether to set BER tracing on or not * @param referralType the jndi ldap referral type: [follow:ignore:throw] * @param aliasHandling how aliases should be handled in searches ('always'|'never'|'find'|'search') * @deprecated use getInstance() instead * @return The created context. */ public static DirContext openContext(int version, String host, int port, String user, char[] pwd, boolean tracing, String referralType, String aliasHandling) throws NamingException { if (host == null) { throw new NamingException("Host not specified in openContext()!"); } if (port == 0) { port = 389; } return openContext(version, ("ldap://" + host + ":" + port), user, pwd, tracing, referralType, aliasHandling); } /** * Opens a simple default initial context, with no authentication, using version 3 ldap. * @deprecated use getInstance() instead. */ public static DirContext openContext(String url) throws NamingException { ConnectionData myData = new ConnectionData(); myData.url = url; return openContext(3, url, "", null, false, null, null, false, null, null, null, null, null, null); } /** * Opens an initial context with (optional) authentication and configurable ldap version. * @param version the LDAP Version (2 or 3) being used. * @param url a url of the form ldap://hostname:portnumber * @param managerUserDN the Manager User's distinguished name (optionally null if not used) * @param pwd the Manager User's password - (is null if user is not manager) */ /* public static DirContext openContext(int version, String url, String managerUserDN, char[] pwd) throws NamingException { return openContext(version, url, managerUserDN, pwd, false, null, null, false, null, null, null, null, null); } */ /** * This static ftn. can be used to open an initial context (which can then * be used to construct a BasicOps object). Note that this ftn may take some * time to return... * * @param version the LDAP Version (2 or 3) being used. * @param url a url of the form ldap://hostname:portnumber * @param userDN the Manager User's distinguished name (optionally null if not used) * @param pwd the Manager User's password - (is null if user is not manager) * @param tracing whether to set BER tracing on or not * @param referralType the jndi ldap referral type: [follow:ignore:throw] (may be null - defaults to 'follow') * @param aliasHandling * @deprecated use getInstance() instead * @return The created context. */ public static DirContext openContext(int version, String url, String userDN, char[] pwd, boolean tracing, String referralType, String aliasHandling) throws NamingException { return openContext(version, url, userDN, pwd, tracing, referralType, aliasHandling, false, null, null, null, null, null, null); } /** * This static ftn. can be used to open an initial context (which can then * be used to construct a BasicOps object). Note that this ftn may take some * time to return... * * @param version the LDAP Version (2 or 3) being used. * @param url a url of the form ldap://hostname:portnumber. * @param userDN the Manager User's distinguished name (optionally null if not used). * @param pwd the Manager User's password - (is null if user is not manager). * @param tracing whether to set BER tracing on or not. * @param referralType the jndi ldap referral type: [follow:ignore:throw] (may be null - defaults to 'follow'). * @param aliasType how aliases should be handled in searches ('always'|'never'|'find'|'search'). * @param useSSL whether to use SSL (either simple or client-authenticated). * @param cacerts the file containing the trusted server certificates (no keys). * @param clientcerts the file containing client certificates. * @param caKeystorePwd the password to the ca's keystore (may be null for non-client authenticated ssl). * @param clientKeystorePwd the password to the client's keystore (may be null for non-client authenticated ssl). * @param caKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'. * @param clientKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'. * * @return The created context. */ public static DirContext openContext(int version, String url, String userDN, char[] pwd, boolean tracing, String referralType, String aliasType, boolean useSSL, String cacerts, String clientcerts, char[] caKeystorePwd, char[] clientKeystorePwd, String caKeystoreType, String clientKeystoreType ) throws NamingException { ConnectionData connectionData = new ConnectionData(); connectionData.version = version; connectionData.url = url; connectionData.userDN = userDN; connectionData.pwd = pwd; connectionData.referralType = referralType; connectionData.aliasType = aliasType; connectionData.useSSL = useSSL; connectionData.cacerts = cacerts; connectionData.clientcerts = clientcerts; connectionData.caKeystorePwd = caKeystorePwd; connectionData.clientKeystorePwd = clientKeystorePwd; connectionData.caKeystoreType = caKeystoreType; connectionData.clientKeystoreType = clientKeystoreType; connectionData.tracing = tracing; return openContext(connectionData); } /** * This is a raw interface to javax.naming.directory.InitialDirContext, that allows * an arbitrary environment string to be passed through. Often the other version * of openContext() above will prove more convenient. * @param env a list of environment variables for the context * @return a newly created DirContext. */ public static DirContext openContext(Properties env) throws NamingException { log.log(Level.FINER,"opening Directory Context to " + env.get(Context.PROVIDER_URL) + "\n using: " + env.get(Context.INITIAL_CONTEXT_FACTORY)); DirContext ctx = new InitialDirContext(env); log.log(Level.FINER,"context successfully opened " + (ctx != null)); if (ctx != null) { try { ldapVersion = Integer.parseInt(env.get("java.naming.ldap.version").toString()); } catch (Exception e) { throw new NamingException("BasicOps.openContext(): unable to determine ldap version of connection."); } } return ctx; } /** * A simple wrapper for a ctx.getSchema("") call. * @deprecated - jndi's 'getSchema' may not always be available (e.g. not implemented in dsml). * use 'getSchemaAttributes()' instead */ public DirContext getSchema() throws NamingException { if (this.ctx == null) { throw new NamingException("No context open to retrieve Schema from"); } log.log(Level.FINER,"getSchema() call"); return this.ctx.getSchema(""); } public void setSchemaAttributes(Attributes newSchema) { this.schema = newSchema; } /** * This returns the schema as an Attributes object. Each multi-valued Attribute within * that Attributes object represents a different aspect of schema: e.g. 'objectClasses', * 'ldapSyntaxes' etc. Convenienct getObjectClasses, getLdapSyntaxes and getAttributeTypes * @return a collection of schema 'Attribute' entries. */ public Attributes getSchemaAttributes() throws NamingException { // every v3 ldap directory *should* have a special 'schema entry', that is named by the 'subschemaSubentry' // attribute of the directory. if (this.schema != null) { return this.schema; // 'cache' schema. } String subschemaSubentry = "cn=schema"; // default - it usually *is* this though... Attributes atts = this.ctx.getAttributes("", new String[] {"subschemaSubentry"}); if (atts != null) { Attribute subschema = atts.get("subschemaSubentry"); if (subschema != null && subschema.get() != null) { subschemaSubentry = subschema.get().toString(); if (subschemaSubentry.length() == 0) { subschemaSubentry = "cn=schema"; } } } this.schema = this.ctx.getAttributes(subschemaSubentry); // sets object variable 'schema' return this.schema; } /** * Convenience method to get the objectClasses Attribute, which represents the syntax of * object classes, what attributes each object class has, and what it is derived from. * @return an Attribute with multiple values, each representing a particular object class * represented as complex string of values that must be parsed: e.g. the country value might be: * ' 2.5.6.2 NAME 'country' SUP ( top ) STRUCTURAL MUST ( c ) MAY ( description $ searchGuide ) ' */ public Attribute getObjectClasses() throws NamingException { if (this.schema == null) { this.schema = getSchemaAttributes(); } return (this.schema == null)?null:this.schema.get("objectClasses"); } /** * Convenience method to get the Syntax Attribute, which represents the ldap attribute syntaxes available. * @return an Attribute with multiple values, each representing a particular Syntax, * represented as complex string of values that must be parsed: e.g. the DirectoryString value might be: * '1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' */ public Attribute getLdapSyntaxes() throws NamingException { if (this.schema == null) { this.schema = getSchemaAttributes(); } return (this.schema == null)?null:this.schema.get("ldapSyntaxes"); } /** * Convenience method to get the objectClasses Attribute, which represents the syntax of * attributeTypes. * @return an Attribute with multiple values, each representing a particular attributeType * represented as complex string of values that must be parsed: e.g. the surname value might be: * 2.5.4.4 NAME ( 'sn' 'surname' ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 */ public Attribute getAttributeTypes() throws NamingException { if (this.schema == null) { this.schema = getSchemaAttributes(); } return (this.schema == null)?null:this.schema.get("attributeTypes"); } /** * basically a wrapper for context.rename... changes the * distinguished name of an object, checks for error. * * @param oldDN current distinguished name of an object. * @param newDN the name it is to be changed to. * @return the success status of the operation */ //XXXX - this will fail for single valued manditory attributes. //XXXX - since using 'deleteRDN = false' - 30 May 2002. public boolean renameObject (Name OldDN, Name NewDN) { log.log(Level.FINER,"renaming object " + OldDN.toString() + " to " + NewDN.toString()); Name oldDN = preParse(OldDN); Name newDN = preParse(NewDN); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.renameObject()\n (so can't do anything!)", null); } Name rdn = newDN.getSuffix(newDN.size()-1); Name oldRdn = oldDN.getSuffix(oldDN.size()-1); if (oldRdn.toString().equals(rdn.toString())) { return true; // nothing to do, rdns are identical. } try { this.ctx.rename (oldDN, rdn); } catch (NamingException e) { return error("Failed to rename entry " + oldDN + " to " + newDN + "\n in BasicOps.renameObject()", e ); } return true; } /** * Copies an object to a new DN by the simple expedient of adding * an object with the new DN, and the attributes of the old object. * @param FromDN the original object being copied * @param ToDN the new object being created * @return the success status of the operation */ public boolean copyObject(Name FromDN, Name ToDN) { log.log(Level.FINER,"copying object " + FromDN.toString() + " to " + ToDN.toString()); Name fromDN = preParse(FromDN); Name toDN = preParse(ToDN); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.copyObject\n (so can't do anything!)", null); } return (addObject(toDN, read(fromDN))); } /** * creates a new object (subcontext) with the given * dn and attributes. * * @param Dn the distinguished name of the new object * @param atts attributes for the new object * @return the success status of the operation */ public boolean addObject (Name Dn, Attributes atts) { log.log(Level.FINER,"add object " + Dn.toString()); Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.addObject()\n (so can't do anything!)", null); } // Sanity check if (dn==null) { return error("null DN in BasicOps.addObject()", null); } if (atts==null) { return error("null atts in BasicOps.addObject()", null); } if (dn.size()==0) { return error("DN has no elements in BasicOps.addObject()", null); } try { this.ctx.createSubcontext (dn, atts); } catch (NamingException e) { e.printStackTrace(); return error("Unable to add object '" + dn + "' in BasicOps.addObject()", e); } return true; } /** * deletes a leaf entry (subcontext). It is * an error to attempt to delete an entry which is not a leaf * entry, i.e. which has children. * */ public boolean deleteObject (Name Dn) { log.log(Level.FINER,"delete object " + Dn.toString()); Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.deleteObject()\n (so can't do anything!)", null); } // Sanity check if (dn==null) { return error("null DN in BasicOps.deleteObject()", null); } if (dn.size()==0) { return error("DN has no elements in BasicOps.deleteObject()", null); } try { this.ctx.destroySubcontext (dn); } catch (NamingException e) { return error("Unable to delete object " + dn + "\n in BasicOps.deleteObject()", e); } return true; } /** * Checks the existence of a particular DN, without (necessarily) * reading any attributes. * @param NodeDN the DN to check * @return the existence of the nodeDN (or false if an error occurs). */ public boolean exists(Name NodeDN) { log.log(Level.FINER,"checking existance of: " + NodeDN.toString()); Name nodeDN = preParse(NodeDN); try { Object o = this.ctx.lookup(nodeDN); return (o!=null); } catch (NamingException e) // this is normal - implies object not found. { return false; } catch (NullPointerException e) //TE: thrown by sun at com.sun.jndi.dsmlv2.soap.DsmlSoapCtx.c_lookup(DsmlSoapCtx.java:571) { return false; } } /** * Reads all the attribute type and values for the given entry. * @param Dn the ldap string distinguished name of entry to be read * @return an 'Attributes' object containing a list of all Attribute * objects. */ public synchronized Attributes read(Name Dn) { return read(Dn, null); } /** * Reads all the attribute type and values for the given entry. * @param Dn the ldap string distinguished name of entry to be read * @param returnAttributes a list of specific attributes to return. * @return an 'Attributes' object containing a list of all Attribute * objects. */ public synchronized Attributes read(Name Dn, String[] returnAttributes) { log.log(Level.FINER,"reading object " + Dn.toString()); Name dn = preParse(Dn); if (this.ctx == null) {error("Null Directory Context\n in BasicOps.read()\n (so can't do anything!)", null); return null; } if (dn==null) {error("Null DN in BasicOps.read()",null);return null;} try { Attributes atts = this.ctx.getAttributes(dn, returnAttributes); return atts; } catch(NamingException e) { error("Failed to read attributes for " + dn + "\n in BasicOps.read()\n", e); } return null; } /** * Modifies an object's attributes, either adding, replacing or * deleting the passed attributes. * * @param Dn distinguished name of object to modify * @param mod_type the modification type to be performed; one of * DirContext.REPLACE_ATTRIBUTE, DirContext.DELETE_ATTRIBUTE, or * DirContext.ADD_ATTRIBUTE. * @param attr the new attributes to update the object with. * @return the success status of the operation */ public boolean modifyAttributes(Name Dn, int mod_type, Attributes attr) { log.log(Level.FINER,"modifying object " + Dn.toString() + " mod type is: " + mod_type); Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.modifyAttributes\n (so can't do anything!)", null); } try { this.ctx.modifyAttributes(dn, mod_type, attr); } catch (NamingException e) { return error("Failed to modify entry " + dn + "\n in BasicOps.modifyAttributes()", e); } return true; } /** * Modifies an object's attributes, either adding, replacing or * deleting the passed attributes. * * @param Dn distinguished name of object to modify * @param modList a list of ModificationItems * @return the success status of the operation */ public boolean modifyAttributes(Name Dn, ModificationItem[] modList) { log.log(Level.FINER,"modifying object " + Dn.toString() + " with list of mod items "); Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.modifyAttributes\n (so can't do anything!)", null); } try { this.ctx.modifyAttributes(dn, modList); } catch (NamingException e) { return error("Failed to modify entry " + dn + "\n in BasicOps.modifyAttributes()", e); } return true; } /** * Updates an object with a new set of attributes * * @param Dn distinguished name of object to update * @param atts the new attributes to update the object with. * @return the success status of the operation */ public boolean updateObject (Name Dn, Attributes atts) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.updateObject()\n (so can't do anything!)", null); } return modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts); } /** * deletes an attribute from an object * * @param Dn distinguished name of object * @param a the attribute to delete * @return Whether the deletion was successful. */ public boolean deleteAttribute(Name Dn, Attribute a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context in BasicOps.deleteAttribute\n (so can't do anything!)", null); } BasicAttributes atts = new BasicAttributes(); atts.put(a); return modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, atts); } /** * deletes a set of attribute-s from an object * * @param Dn distinguished name of object * @param a the Attributes object containing the * list of attribute-s to delete * @return Whether the deletion was successful. */ public boolean deleteAttributes(Name Dn, Attributes a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.deleteAttributes\n (so can't do anything!)", null); } return modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, a); } /** * updates an Attribute with a new value set * * @param Dn distinguished name of object * @param a the attribute to modify * @return success of operation */ public boolean updateAttribute(Name Dn, Attribute a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.updateAttribute\n (so can't do anything!)", null); } BasicAttributes atts = new BasicAttributes(); atts.put(a); return modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts); } /** * updates a set of Attribute-s. * * @param Dn distinguished name of object * @param a an Attributes object containing the attribute-s to modify * @return success of operation */ public boolean updateAttributes(Name Dn, Attributes a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.updateAttributes\n (so can't do anything!)", null); } return modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, a); } /** * Adds a new attribute to a particular dn. * * @param Dn distinguished name of object * @param a the attribute to modify * @return success status */ public boolean addAttribute(Name Dn, Attribute a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.addAttribute()\n (so can't do anything!)", null); } BasicAttributes atts = new BasicAttributes(); atts.put(a); return modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, atts); } /** * Adds a set of attributes to a particular dn. * * @param Dn distinguished name of object * @param a the Attributes (set of attribute-s) to add * @return success status */ public boolean addAttributes(Name Dn, Attributes a) { Name dn = preParse(Dn); if (this.ctx == null) { return error("Null Directory Context\n in BasicOps.addAttribute()\n (so can't do anything!)", null); } return modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, a); } /** * returns the next level of a directory tree, returning * a Enumeration of the results, *relative* to the SearchBase (i.e. not as * absolute DNs). * * @param Searchbase the node in the tree to expand * @return list of results (NameClassPair); the next layer of the tree... */ public NamingEnumeration list(Name Searchbase) { // Attempt to read the names of the next level of subentries along with their object // classes. Failing that, try to just read their names. try { return rawOneLevelSearch(Searchbase, "(objectclass=*)", 0, 0, new String[] {"objectclass"} ); } catch (NamingException e) // can this be made more specific? { try { return rawOneLevelSearch(Searchbase, "(objectclass=*)", 0, 0, new String[] {"1.1"} ); } catch (NamingException e2) { error("Unable to list sub entries!", e2); return null; } } } /** * Performs a one-level directory search (i.e. a search of immediate children), without * returning any attributes (e.g. just returns DNs). * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchOneLevel(Name Searchbase, String filter, int limit, int timeout) { return searchOneLevel(Searchbase, filter, limit, timeout, new String[] {"objectClass"}); } /** * Performs a one-level directory search (i.e. a search of immediate children) * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none) * * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchOneLevel(Name Searchbase, String filter, int limit, int timeout, String[] returnAttributes) { Name searchbase = preParse(Searchbase); if (this.ctx == null) { error("Null Directory Context\n in BasicOps.searchOneLevel()\n (so can't do anything!)", null); return null; } //XXX if (returnAttributes != null && returnAttributes.length == 0) { returnAttributes = new String[] {"objectClass"}; } try { return rawOneLevelSearch(searchbase, filter, limit, timeout, returnAttributes); } catch (NamingException e) { log.log(Level.WARNING,"Search failed", e); return null; } } private NamingEnumeration rawOneLevelSearch(Name searchbase, String filter, int limit, int timeout, String[] returnAttributes ) throws NamingException { log.log(Level.FINER,"searching next level: from " + searchbase.toString() + " with filter " + filter); /* specify search constraints to search one level */ SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); constraints.setCountLimit(limit); constraints.setTimeLimit(timeout); constraints.setReturningAttributes(returnAttributes); NamingEnumeration results = this.ctx.search(searchbase, filter, null); log.log(Level.FINER,"finished next level search; results exist? " + results.hasMoreElements()); results = postParseNameClassPairs(results, searchbase); return results; } /** * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below), * returning no attributes (i.e. just DNs); * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchSubTree(Name Searchbase, String filter, int limit, int timeout) { return searchSubTree(Searchbase, filter, limit, timeout, new String[] {"objectClass"}); } /** * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below). * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none) * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchSubTree(Name Searchbase, String filter, int limit, int timeout, String[] returnAttributes) { log.log(Level.FINER,"searching subtree from " + Searchbase.toString() + " with filter " + filter); Name searchbase = preParse(Searchbase); if (this.ctx == null) {error("Null Directory Context\n in BasicOps.searchSubTree()\n (so can't do anything!)", null); return null; } NamingEnumeration result = null; if (returnAttributes != null && returnAttributes.length == 0) { returnAttributes = new String[] {"objectClass"}; } try { /* specify search constraints to search subtree */ SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); constraints.setCountLimit(limit); constraints.setTimeLimit(timeout); constraints.setReturningAttributes(returnAttributes); result = this.ctx.search(searchbase, filter, constraints); return postParseNameClassPairs(result, searchbase); } catch (NamingException e) { error("Search failed", e); return null; } } /** * Performs a base object search (i.e. just a search of the current entry, nothing below it), * returning no attributes (i.e. just DNs); * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchBaseObject(Name Searchbase, String filter, int limit, int timeout) { return searchBaseObject(Searchbase, filter, limit, timeout, new String[] {"objectClass"}); } /** * Performs a base object search (i.e. just a search of the current entry, nothing below it). * * @param Searchbase the domain name (relative to initial context in ldap) to seach from. * @param filter the non-null filter to use for the search * @param limit the maximum number of results to return * @param timeout the maximum time to wait before abandoning the search * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none) * @return list of search results ('SearchResult's); entries matching the search filter. */ public NamingEnumeration searchBaseObject(Name Searchbase, String filter, int limit, int timeout, String[] returnAttributes) { log.log(Level.FINER,"searching object " + Searchbase.toString() + " with filter " + filter); Name searchbase = preParse(Searchbase); if (this.ctx == null) {error("Null Directory Context\n in BasicOps.searchSubTree()\n (so can't do anything!)", null); return null; } NamingEnumeration result = null; //XXX if (returnAttributes != null && returnAttributes.length == 0) { returnAttributes = new String[] {"objectClass"}; } try { /* specify search constraints to search subtree */ SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.OBJECT_SCOPE); constraints.setCountLimit(limit); constraints.setTimeLimit(timeout); constraints.setReturningAttributes(returnAttributes); result = this.ctx.search(searchbase, filter, constraints); return postParseNameClassPairs(result, searchbase); } catch (NamingException e) { error("Search failed", e); return null; } } /** * Shuts down the current context.<p> * nb. It is not an error to call this method multiple times. */ public void close() { log.log(Level.FINER,"closing context"); if (this.ctx == null) { return; // it is not an error to multiply disconnect. } setSchemaAttributes(null); // clear the schema try { this.ctx.close(); ldapVersion = -1; } catch (NamingException e) { error("Error closing context in BasicOps.disconnect()", e); } } /** * This picks up the name parser used at the root level... if * the context only spans a single name space (i.e. for an ldap * directory) this will be the same as the one used throughout. */ public NameParser getBaseNameParser() { log.log(Level.FINER,"getting base name parser"); if (this.ctx == null) {error("Null Directory Context\n in BasicOps.searchSubTree()\n (so can't do anything!)", null); return null; } try { return this.ctx.getNameParser(""); } catch (NamingException e) { error("Error getting Name Parser in BasicOps.getBaseNameParser()", e); return null; } } /** * This function provides a common point for all error reporting. * In order to customise it, simply over-ride this function in a * class inheriting from BasicOps. * * @param msg User friendly error message * @param e The exception * @return returns false (always) for easy chaining. */ public boolean error(String msg, Exception e) { this.errorMsg = msg; //TE: set the error msg. this.errorException = e; //TE: set the exception. log.log(Level.WARNING,"BasicOps error: " + msg + "\n ", e); return false; } /** * Returns the error message & exception as a string. * NOTE: this method is used for JXweb - the only way to get error messages. * @return errrorMsg+errorException (if not null - they are set in the 'error' method). */ public String getError() { return this.errorException == null ? this.errorMsg : this.errorMsg + this.errorException; } /** * This function provides a common point for all logging. * In order to customise it, simply over-ride this function in a * class inheriting from BasicOps. * * @param msg log Message */ public void log(String msg, int logLevel) { System.out.println("BasicOps Log: " + msg); } /** * This preparses a name, preparitory to passing to the jndi operation. * Usefull to over-ride if a Name needs to be escaped or re-formatted. * @param name the pre jndi operation name. * @return the version used by the operation. */ protected Name preParse(Name name) { return name; } /** * This postparses a name, after it has been returned from the jndi operation. * Usefull to over-ride if the name needs to be unescaped or reformatted. * @param name the post jndi operation name. * @return the re-formatted version used by the application. */ protected Name postParse(Name name) { return name; } /** * This postparses a namingEnumeration of NameClassPairs, after it has been returned from the jndi operation. * Usefull to over-ride if the names in the enumeration need to be unescaped or reformatted. * @param e the post jndi operation namingEnumeration. * @param searchBase the 'base' dn from which the names in the enumeration (may) be relative. * If the Names in * the enumeration are suffixed by the searchBase, they are unaltered, otherwise the searchBase * is added to the names to give the full DN in the namespace. * @return the re-formatted version used by the application. */ protected NamingEnumeration postParseNameClassPairs(NamingEnumeration e, Name searchBase) throws NamingException { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER,"in post Parse Name Class Pairs. Elements available = " + e.hasMoreElements()); try { while (e.hasMoreElements()) { SearchResult bloop = (SearchResult)e.nextElement(); if (bloop == null) { log.log(Level.FINER,"NULL RESULT"); } else { log.log(Level.FINER,"next result: " + bloop.getName()); } } } catch (Exception e2) { log.log(Level.WARNING,"unexpected exception: ", e2); } } return e; } /** * Returns the ldap version of the current connection */ public int getLdapVersion() { return ldapVersion; } /** * Get the raw context for occasions where direct * jndi operations must be performed. */ public DirContext getContext() { return this.ctx; } public boolean renameObject (Name OldDN, Name NewDN, boolean deleteOldRDN) { String value = (deleteOldRDN) ? "true" : "false" ; try { this.ctx.addToEnvironment("java.naming.ldap.deleteRDN", value); boolean retValue = renameObject(OldDN, NewDN); this.ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false"); // reset to default of 'false' afterwards. return retValue; } catch (NamingException e) { log.log(Level.WARNING,"Error changing context environment deleteRDN to " + value); } return false; } }