package com.ca.jndiproviders.dsml;
import com.ca.commons.naming.DXNamingEnumeration;
import com.ca.commons.naming.DN;
import com.ca.commons.cbutil.CBBase64;
import com.ca.commons.cbutil.CBBase64EncodingException;
import javax.naming.directory.*;
import javax.naming.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
/**
* <p>This is a DSML jndiproviders context, that provides support for all the basic DSML operations.</p>
* <p/>
* <p>However, it deliberately does *not* support:</p>
* <ul>
* <li>binding java objects - the bind methods are not implemented. Users will need to do their
* own serialisation if they want this functionality.
* <li> referrals aren't implemented yet.
* <li> ldap v3 extensions.
* <li> composite names spanning multiple namespaces - all names are DSML names only. (Composite
* names are a bloody stupid idea that come close to making jndiproviders unusable, IMNSHO - CB)
* </ul>
* <p/>
* <p>If you need the above features, you may be better off using the Sun DSML provider which is
* far more ambitious in scope (but which has some licencing problems, and is a bit rocky in parts).</p>
*/
/*
* DSML SERVER ERROR NOTES
*
* Server is incorrectly parsing escaped dns - e.g. dn="cn=Vivienne \"LEVER\", ... ".
* Server is returning HTTP 500, which prevents parsing of returned soap error
*
*/
//TODO: schema support??
//TODO: we ended up using strings rather than names, so adjust all methods to chain to the string methods instead of the name methods.
//TODO: makes heavy use of static stuff - may not be thread safe.
public class DsmlContext implements DirContext
{
// Formatting
private static int TABLEN = 4;
private static String TAB = " ";
private static String TAB2 = TAB + TAB;
private static String TAB3 = TAB2 + TAB;
private static String TAB4 = TAB3 + TAB;
private static String TAB5 = TAB4 + TAB;
private static String TAB6 = TAB5 + TAB;
private static String SOAPHEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
TAB + "<soap-env:Envelope xmlns:soap-env=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
TAB2 + "<soap-env:Body>\n";
private static String SOAPFOOTER = TAB2 + "</soap-env:Body>\n" +
TAB + "</soap-env:Envelope>";
private static String DSMLHEADER = TAB3 + "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n";
private static String DSMLFOOTER = TAB3 + "</dsml:batchRequest>\n";
private static String SEARCHFOOTER = TAB3 + "</dsml:searchRequest>\n";
private static String STANDARDHEADER = SOAPHEADER + DSMLHEADER;
private static String STANDARDFOOTER = DSMLFOOTER + SOAPFOOTER;
// SEARCH SCOPE CONSTANTS
private static String BASEOBJECT = "baseObject";
private static String SINGLELEVEL = "singleLevel";
private static String WHOLESUBTREE = "wholeSubtree";
private static String[] SCOPEOPTIONS = new String[]{BASEOBJECT, SINGLELEVEL, WHOLESUBTREE};
// SEARCH ALIAS DEREF OPTIONS
private static String NEVER = "neverDerefAliases";
private static String SEARCHING = "derefInSearching";
private static String FINDING = "derefFindingBaseObj";
private static String ALWAYS = "derefAlways";
private static String[] ALIASOPTIONS = new String[]{NEVER, SEARCHING, FINDING, ALWAYS};
// Default Search Controls
private static SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
protected Hashtable environment;
// the name of the context (as set by 'createSubcontext' methods
private String contextName = "";
private static Logger log = Logger.getLogger(DsmlContext.class.getName());
// Debug
{
log.setLevel(Level.FINEST);
}
/**
* <p>This constructs the XML tag and tag attributes for a DSML search operation start tag. (filter and attributes elements
* must be added separately, the end tag is a defined constant.)
* <p/>
* <p>e.g.
* <pre>
* <dsml:searchRequest requestID="1" dn="cn=schema" scope="baseObject" derefAliases="derefInSearching">
* </pre>
* <p/>
* something like
*
* @param searchHeader an existing string buffer to append the search header information in.
* @param dn the dn to search from
* @param scope the search scope, must be one of 'baseLevel', 'singleLevel' or 'wholeSubtree' (see constants)
* @param derefAliases how to handle aliases. must be on of 'neverDerefAliases', 'derefInSearching', 'derefFindingBaseObj', 'derefAlways' (see constants)
* @param sizeLimit the LDAP search size limit - default is 0 (unlimited by client)
* @param timeLimit the LDAP search time limit - default is 0 (unlimited by client)
* @param typesOnly - no idea, but it's in the spec.
* @return a string buffer containing the raw xml for a search operation.
* @throws NamingException
*/
private static StringBuffer getSearchRequestHeader(StringBuffer searchHeader, String dn, String scope, String derefAliases, long sizeLimit, int timeLimit, boolean typesOnly)
throws NamingException
{
// sanity check arguments
if (!checkValidity(SCOPEOPTIONS, scope))
throw new NamingException("search scope argument '" + scope + "' is invalid");
if (!checkValidity(ALIASOPTIONS, derefAliases))
{
if (derefAliases != null) log.info("bad alias option passed '" + derefAliases + "'");
derefAliases = SEARCHING;
}
if (searchHeader == null)
searchHeader = new StringBuffer();
searchHeader.append(TAB4 + "<dsml:searchRequest dn=\""); // TODO: requestID is optional - let's drop it in the final version - CB
searchHeader.append(escapeName(dn));
searchHeader.append("\" scope=\"").append(scope);
searchHeader.append("\" derefAliases=\"").append(derefAliases);
if (sizeLimit > 0)
searchHeader.append("\" sizeLimit=\"").append(sizeLimit);
if (timeLimit > 0)
searchHeader.append("\" timeLimit=\"").append(timeLimit);
// never used?
if (typesOnly == true)
searchHeader.append("\" typesOnly=\"").append(typesOnly);
searchHeader.append("\">\n");
log.finest("created search header: " + searchHeader);
return searchHeader;
}
/**
* <p>This constructs the XML tag and elements for a DSML search attribute list
* <p/>
* e.g.
* <pre>
* >dsml:attributes<
* >dsml:attribute name="attributeTypes"/<
* >dsml:attribute name="objectClasses"/<
* >dsml:attribute name="matchingRules"/<
* >dsml:attribute name="ldapSyntaxes"/<
* >dsml:attribute name="*"/<
* >/dsml:attributes<
* </pre>
*
* @param searchAttributes an existing string buffer to append the search attribute list information to
* @param attributes a string array of attribute names
*
* @return the search request attributes
*/
private static StringBuffer getSearchRequestAttributes(StringBuffer searchAttributes, String[] attributes)
{
if (attributes == null || attributes.length == 0)
{
return searchAttributes; // do nothing if there are no specific attributes (means return all)
}
if (searchAttributes == null)
searchAttributes = new StringBuffer(40 + 80 * attributes.length);
searchAttributes.append(TAB5 + "<dsml:attributes>\n");
int len = attributes.length;
for (int i = 0; i < len; i++)
{
searchAttributes.append(TAB6 + "<dsml:attribute name=\"").append(attributes[i]).append("\"/>\n");
}
searchAttributes.append(TAB5 + "</dsml:attributes>\n");
log.finest("created search attribute list: " + searchAttributes);
return searchAttributes;
}
/**
* Small utility method to check that a string is one of a number of valid options.
*
* @param options an array of valid options.
* @param checkme a string to compare against the previous array.
* @return true if checkme is one of the options, false if it is null, or is not one of the options.
*/
private static boolean checkValidity(String[] options, String checkme)
{
if (checkme != null)
{
for (int i = 0; i < options.length; i++)
if (options[i].equals(checkme))
return true;
}
return false;
}
/**
* A little test method to produce dummy enumerations during development
*
* @param name
* @param num
*
* @return the test enumeration
*/
private NamingEnumeration getTestEnumeration(Name name, int num)
{
log.finest("generating " + num + " test names from '" + name.toString() + "'");
String itemBase = "c=AU";
if (name.size() == 1)
itemBase = "o=beta";
else if (name.size() == 2)
itemBase = "ou=gamma";
else if (name.size() == 3)
itemBase = "ou=delta";
DXNamingEnumeration testEnumeration = new DXNamingEnumeration();
for (int i = 0; i < num; i++)
{
String itemName = itemBase + i;
testEnumeration.add(new SearchResult(itemName, null, getTestAttributes(itemName)));
}
return testEnumeration;
}
/**
* A little test method to produce dummy attributes objects during development.
*
* @param name
*
* @return the test attributes
*/
private BasicAttributes getTestAttributes(String name)
{
log.finest("generating test data from name '" + name + "'");
BasicAttributes testAttributes = new BasicAttributes("cn", name);
testAttributes.put(new BasicAttribute("objectClass", "person"));
testAttributes.put(new BasicAttribute("sn", "Test"));
return testAttributes;
}
private DsmlContext()
{
} // don't do this :-).
private DsmlContext(String baseDN, Hashtable env)
{
contextName = baseDN==null?"":baseDN;
environment = env; // Don't see any reason to clone environment for every entry?
}
/**
* Called by DsmlCtxFactory / also used for testing
*
* @param env the environment to use for this connection...
*/
DsmlContext(Hashtable env)
{
environment = (env == null) ? new Hashtable() : env;
log.fine("Created DsmlContext");
}
/**
* Retrieves all of the attributes associated with a named object.
* See {@link #getAttributes(javax.naming.Name)} for details.
*
* @param name the name of the object from which to retrieve attributes
* @return the set of attributes associated with <code>name</code>
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public Attributes getAttributes(String name) throws NamingException
{
return getAttributes(name, null);
}
/**
* Modifies the attributes associated with a named object.
* See {@link #modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes)} for details.
*
* @param name the name of the object whose attributes will be updated
* @param mod_op the modification operation, one of:
* <code>ADD_ATTRIBUTE</code>,
* <code>REPLACE_ATTRIBUTE</code>,
* <code>REMOVE_ATTRIBUTE</code>.
* @param attrs the attributes to be used for the modification; map not be null
* @throws javax.naming.directory.AttributeModificationException if the modification cannot
* be completed successfully
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException
{
ModificationItem[] mods = new ModificationItem[attrs.size()];
Enumeration attObjects = attrs.getAll();
int i = 0;
while (attObjects.hasMoreElements())
{
mods[i++] = new ModificationItem(mod_op, (Attribute) attObjects.nextElement());
}
modifyAttributes(name, mods);
}
/**
* Retrieves all of the attributes associated with a named object.
* See the class description regarding attribute models, attribute
* type names, and operational attributes.
*
* @param name the name of the object from which to retrieve attributes
* @return the set of attributes associated with <code>name</code>.
* Returns an empty attribute set if name has no attributes;
* never null.
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #getAttributes(String)
* @see #getAttributes(javax.naming.Name, String[])
*/
public Attributes getAttributes(Name name) throws NamingException
{
return getAttributes(name, null);
}
/**
* Modifies the attributes associated with a named object.
* The order of the modifications is not specified. Where
* possible, the modifications are performed atomically.
*
* @param name the name of the object whose attributes will be updated
* @param mod_op the modification operation, one of:
* <code>ADD_ATTRIBUTE</code>,
* <code>REPLACE_ATTRIBUTE</code>,
* <code>REMOVE_ATTRIBUTE</code>.
* @param attrs the attributes to be used for the modification; may not be null
* @throws javax.naming.directory.AttributeModificationException if the modification cannot
* be completed successfully
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[])
*/
public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException
{
modifyAttributes(name.toString(), mod_op, attrs);
}
/**
* Retrieves the schema associated with the named object.
* See {@link #getSchema(javax.naming.Name)} for details.
*
* @param name the name of the object whose schema is to be retrieved
* @return the schema associated with the context; never null
* @throws javax.naming.OperationNotSupportedException if schema not supported
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated not yet implemented
*/
public DirContext getSchema(String name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
}
/**
* Retrieves a context containing the schema objects of the
* named object's class definitions.
* See {@link #getSchemaClassDefinition(javax.naming.Name)} for details.
*
* @param name the name of the object whose object class
* definition is to be retrieved
* @return the <tt>DirContext</tt> containing the named
* object's class definitions; never null
* @throws javax.naming.OperationNotSupportedException if schema not supported
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated not yet implemented
*/
public DirContext getSchemaClassDefinition(String name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
}
/**
* Retrieves the schema associated with the named object.
* The schema describes rules regarding the structure of the namespace
* and the attributes stored within it. The schema
* specifies what types of objects can be added to the directory and where
* they can be added; what mandatory and optional attributes an object
* can have. The range of support for schemas is directory-specific.
* <p/>
* <p> This method returns the root of the schema information tree
* that is applicable to the named object. Several named objects
* (or even an entire directory) might share the same schema.
* <p/>
* <p> Issues such as structure and contents of the schema tree,
* permission to modify to the contents of the schema
* tree, and the effect of such modifications on the directory
* are dependent on the underlying directory.
*
* @param name the name of the object whose schema is to be retrieved
* @return the schema associated with the context; never null
* @throws javax.naming.OperationNotSupportedException if schema not supported
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated not yet implemented
*/
public DirContext getSchema(Name name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
}
/**
* Retrieves a context containing the schema objects of the
* named object's class definitions.
* <p/>
* One category of information found in directory schemas is
* <em>class definitions</em>. An "object class" definition
* specifies the object's <em>type</em> and what attributes (mandatory
* and optional) the object must/can have. Note that the term
* "object class" being referred to here is in the directory sense
* rather than in the Java sense.
* For example, if the named object is a directory object of
* "Person" class, <tt>getSchemaClassDefinition()</tt> would return a
* <tt>DirContext</tt> representing the (directory's) object class
* definition of "Person".
* <p/>
* The information that can be retrieved from an object class definition
* is directory-dependent.
* <p/>
* Prior to JNDI 1.2, this method
* returned a single schema object representing the class definition of
* the named object.
* Since JNDI 1.2, this method returns a <tt>DirContext</tt> containing
* all of the named object's class definitions.
*
* @param name the name of the object whose object class
* definition is to be retrieved
* @return the <tt>DirContext</tt> containing the named
* object's class definitions; never null
* @throws javax.naming.OperationNotSupportedException if schema not supported
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated not yet implemented
*/
public DirContext getSchemaClassDefinition(Name name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
}
/**
* Modifies the attributes associated with a named object using
* an ordered list of modifications.
* See {@link #modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[])} for details.
*
* @param name the name of the object whose attributes will be updated
* @param mods an ordered sequence of modifications to be performed;
* may not be null
* @throws javax.naming.directory.AttributeModificationException if the modifications
* cannot be completed successfully
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException
{
log.finest("modify Atts of (" + name + ")");
// construct XML
StringBuffer modRequestBuffer = constructModRequest(name, mods);
// send XML to server
String response = sendDSMLRequest(modRequestBuffer);
// parse response XML
parseModResponse(response);
}
private StringBuffer constructModRequest(String name, ModificationItem[] mods)
throws NamingException
{
StringBuffer message = new StringBuffer(200);
message.append(STANDARDHEADER);
getModRequestElement(message, name, mods);
message.append(STANDARDFOOTER);
return message;
}
/**
* @param message
* @param name
* @param mods
*/
/*
" <dsml:modifyRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\">" +
" <dsml:modification name=\"favoriteDrink\" operation=\"add\">" +
" <dsml:value>japanese slipper</dsml:value>" +
" </dsml:modification>" +
" <dsml:modification name=\"address\" operation=\"delete\">\n" +
" <dsml:value>21 Jump Street$New York$90210</dsml:value>\n" +
" </dsml:modification>\n" +
" <dsml:modification name=\"userPassword\" operation=\"replace\">" +
" <dsml:value xsi:type=\"xsd:base64Binary\">c2VjcmV0IHBhc3N3b3Jk</dsml:value>" +
" </dsml:modification>" +
" </dsml:modifyRequest>" +
*/
static void getModRequestElement(StringBuffer message, String name, ModificationItem[] mods)
throws NamingException
{
message.append(TAB4).append("<dsml:modifyRequest dn=\"").append(escapeName(name)).append("\">\n");
for (int i = 0; i < mods.length; i++)
{
Attribute att = mods[i].getAttribute();
NamingEnumeration values = att.getAll();
switch (mods[i].getModificationOp())
{
case ADD_ATTRIBUTE:
message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"add\">\n");
while (values.hasMore())
createDsmlValueElement(values.next(), message);
message.append(TAB5).append("</dsml:modification>\n");
break;
case REPLACE_ATTRIBUTE:
message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"replace\">\n");
while (values.hasMore())
createDsmlValueElement(values.next(), message);
message.append(TAB5).append("</dsml:modification>\n");
break;
case REMOVE_ATTRIBUTE:
message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"delete\">\n");
while (values.hasMore())
createDsmlValueElement(values.next(), message);
message.append(TAB5).append("</dsml:modification>\n");
break;
}
}
message.append(TAB4).append("</dsml:modifyRequest>\n");
}
void parseModResponse(String response)
throws NamingException
{
// check for a dsml error
checkForError(response);
// quick check that it really was a modify response...
if (response.indexOf("modifyResponse>") == -1)
throw new NamingException("Unexpected DSML Response to Modify Request:\n " + response);
}
//private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry.*?dn=\"(.*?)\">(.*?)</searchResultEntry>", Pattern.DOTALL);
//TODO: tighten up...
private static Pattern errorResult = Pattern.compile("errorResponse.*?type=\"(.*?)\"", Pattern.DOTALL);
private static Pattern errorMessage = Pattern.compile("message>(.*?)</", Pattern.DOTALL);
private static Pattern errorDetail = Pattern.compile("detail>(.*?)</", Pattern.DOTALL);
private static Pattern ldapResultCode = Pattern.compile("code=\"(.*?)\"", Pattern.DOTALL);
private static Pattern ldapResultDesc = Pattern.compile("resultCode.*?descr=\"(.*?)\"", Pattern.DOTALL);
private static Pattern ldapResultMsg = Pattern.compile("errorMessage>(.*?)</", Pattern.DOTALL);
static void checkForError(String response)
throws NamingException
{
Matcher error = errorResult.matcher(response);
if (error.find())
{
String errorMsg = "Error Processing DSML Request: " + error.group(1);
Matcher message = errorMessage.matcher(response);
if (message.find())
errorMsg = errorMsg + "\n" + message.group(1);
Matcher detail = errorDetail.matcher(response);
if (detail.find())
errorMsg = errorMsg + "\n" + detail.group(1);
throw new NamingException(errorMsg);
}
else
{
try
{
Matcher resultMatcher = ldapResultCode.matcher(response);
resultMatcher.find();
String resultCode = resultMatcher.group(1);
int i = Integer.parseInt(resultCode);
if (i == 0)
return; // all good here.
Matcher descMatcher = ldapResultDesc.matcher(response);
String desc = "";
if (descMatcher.find())
desc = descMatcher.group(1);
Matcher msgMatcher = ldapResultMsg.matcher(response);
String msg = "";
if (msgMatcher.find())
msg = msgMatcher.group(1);
throw new NamingException(desc + " Exception (LDAP " + resultCode + ")\n" + msg);
}
catch (NumberFormatException e)
{
throw new NamingException("Unable to parse result code in DSML Response\n" + response);
}
catch (IllegalStateException e)
{
throw new NamingException("Unable to find result code in DSML Response\n" + response);
}
}
}
/**
* Modifies the attributes associated with a named object using
* an ordered list of modifications.
* The modifications are performed
* in the order specified. Each modification specifies a
* modification operation code and an attribute on which to
* operate. Where possible, the modifications are
* performed atomically.
*
* @param name the name of the object whose attributes will be updated
* @param mods an ordered sequence of modifications to be performed;
* may not be null
* @throws javax.naming.directory.AttributeModificationException if the modifications
* cannot be completed successfully
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes)
* @see javax.naming.directory.ModificationItem
*/
public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException
{
modifyAttributes(name.toString(), mods);
}
/**
* Searches in a single context for objects that contain a
* specified set of attributes.
* See {@link #search(javax.naming.Name, javax.naming.directory.Attributes)} for details.
*
* @param name the name of the context to search
* @param matchingAttributes the attributes to search for
* @return an enumeration of <tt>SearchResult</tt> objects
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException
{
return search(name, matchingAttributes, null);
}
/**
* Searches in a single context for objects that contain a
* specified set of attributes.
* This method returns all the attributes of such objects.
* It is equivalent to supplying null as
* the <tt>atributesToReturn</tt> parameter to the method
* <code>search(Name, Attributes, String[])</code>, i.e. search(name, matchingAttributes, null)
* <br>
* See {@link #search(javax.naming.Name, javax.naming.directory.Attributes, String[])} for a full description.
*
* @param name the name of the context to search
* @param matchingAttributes the attributes to search for
* @return an enumeration of <tt>SearchResult</tt> objects
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #search(javax.naming.Name, javax.naming.directory.Attributes, String[])
*/
public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException
{
return search(name, matchingAttributes, null);
}
/**
* Binds a name to an object, along with associated attributes.
* See {@link #bind(javax.naming.Name, Object, javax.naming.directory.Attributes)} for details.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @param attrs the attributes to associate with the binding
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
* of the binding are not supplied
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated this method will not be implemented
*/
public void bind(String name, Object obj, Attributes attrs) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object, along with associated attributes,
* overwriting any existing binding.
* See {@link #rebind(javax.naming.Name, Object, javax.naming.directory.Attributes)} for details.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @param attrs the attributes to associate with the binding
* @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
* of the binding are not supplied
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated this method will not be implemented
*/
public void rebind(String name, Object obj, Attributes attrs) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object, along with associated attributes.
* If <tt>attrs</tt> is null, the resulting binding will have
* the attributes associated with <tt>obj</tt> if <tt>obj</tt> is a
* <tt>DirContext</tt>, and no attributes otherwise.
* If <tt>attrs</tt> is non-null, the resulting binding will have
* <tt>attrs</tt> as its attributes; any attributes associated with
* <tt>obj</tt> are ignored.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @param attrs the attributes to associate with the binding
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
* of the binding are not supplied
* @throws javax.naming.NamingException if a naming exception is encountered
* @see javax.naming.Context#bind(javax.naming.Name, Object)
* @see #rebind(javax.naming.Name, Object, javax.naming.directory.Attributes)
* @deprecated this method will not be implemented
*/
public void bind(Name name, Object obj, Attributes attrs) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object, along with associated attributes,
* overwriting any existing binding.
* If <tt>attrs</tt> is null and <tt>obj</tt> is a <tt>DirContext</tt>,
* the attributes from <tt>obj</tt> are used.
* If <tt>attrs</tt> is null and <tt>obj</tt> is not a <tt>DirContext</tt>,
* any existing attributes associated with the object already bound
* in the directory remain unchanged.
* If <tt>attrs</tt> is non-null, any existing attributes associated with
* the object already bound in the directory are removed and <tt>attrs</tt>
* is associated with the named object. If <tt>obj</tt> is a
* <tt>DirContext</tt> and <tt>attrs</tt> is non-null, the attributes
* of <tt>obj</tt> are ignored.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @param attrs the attributes to associate with the binding
* @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
* of the binding are not supplied
* @throws javax.naming.NamingException if a naming exception is encountered
* @see javax.naming.Context#bind(javax.naming.Name, Object)
* @see #bind(javax.naming.Name, Object, javax.naming.directory.Attributes)
* @deprecated this method will not be implemented
*/
public void rebind(Name name, Object obj, Attributes attrs) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Retrieves selected attributes associated with a named object.
* See {@link #getAttributes(javax.naming.Name, String[])} for details.
*
* @param name The name of the object from which to retrieve attributes
* @param attrIds the identifiers of the attributes to retrieve.
* null indicates that all attributes should be retrieved;
* an empty array indicates that none should be retrieved.
* @return the requested attributes; never null
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public Attributes getAttributes(String name, String[] attrIds) throws NamingException
{
log.finest("getAttributes (" + name.toString() + ")");
NamingEnumeration en = doDsmlSearch(name.toString(), BASEOBJECT, SEARCHING, 0, 0, false, "(objectClass=*)", attrIds);
if (en.hasMoreElements() == false)
return new BasicAttributes(); // return empty attributes object for 'virtual' nodes (e.g. router entries, base DN RDNs)
SearchResult result = (SearchResult) en.next();
return result.getAttributes();
//return getTestAttributes(name.toString()); //To change body of implemented methods use File | Settings | File Templates.
}
/**
* Retrieves selected attributes associated with a named object.
* See the class description regarding attribute models, attribute
* type names, and operational attributes.
* <p/>
* <p> If the object does not have an attribute
* specified, the directory will ignore the nonexistent attribute
* and return those requested attributes that the object does have.
* <p/>
* <p> A directory might return more attributes than was requested
* (see <strong>Attribute Type Names</strong> in the class description),
* but is not allowed to return arbitrary, unrelated attributes.
* <p/>
* <p> See also <strong>Operational Attributes</strong> in the class
* description.
*
* @param name the name of the object from which to retrieve attributes
* @param attrIds the identifiers of the attributes to retrieve.
* null indicates that all attributes should be retrieved;
* an empty array indicates that none should be retrieved.
* @return the requested attributes; never null
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public Attributes getAttributes(Name name, String[] attrIds) throws NamingException
{
return getAttributes(name.toString(), attrIds);
}
/**
* Creates and binds a new context, along with associated attributes.
* See {@link #createSubcontext(javax.naming.Name, javax.naming.directory.Attributes)} for details.
*
* @param name the name of the context to create; may not be empty
* @param attrs the attributes to associate with the newly created context
* @return the newly created context
* @throws javax.naming.NameAlreadyBoundException if the name is already bound
* @throws javax.naming.directory.InvalidAttributesException if <code>attrs</code> does not
* contain all the mandatory attributes required for creation
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public DirContext createSubcontext(String name, Attributes attrs) throws NamingException
{
log.finest("createSubcontext (" + name.toString() + ")");
// construct XML
StringBuffer addRequestBuffer = constructAddRequest(name, attrs);
// send XML to server
String response = sendDSMLRequest(addRequestBuffer);
// parse response XML
parseAddResponse(response);
// return new context with new name?
return new DsmlContext(name, environment);
}
void parseAddResponse(String response)
throws NamingException
{
checkForError(response);
// quick check that it really was a modify response...
if (response.indexOf("addResponse>") == -1)
throw new NamingException("Unexpected DSML Response to Add Request:\n " + response);
}
/**
* Creates and binds a new context, along with associated attributes.
* This method creates a new subcontext with the given name, binds it in
* the target context (that named by all but terminal atomic
* component of the name), and associates the supplied attributes
* with the newly created object.
* All intermediate and target contexts must already exist.
* If <tt>attrs</tt> is null, this method is equivalent to
* <tt>Context.createSubcontext()</tt>.
*
* @param name the name of the context to create; may not be empty
* @param attrs the attributes to associate with the newly created context
* @return the newly created context
* @throws javax.naming.NameAlreadyBoundException if the name is already bound
* @throws javax.naming.directory.InvalidAttributesException if <code>attrs</code> does not
* contain all the mandatory attributes required for creation
* @throws javax.naming.NamingException if a naming exception is encountered
* @see javax.naming.Context#createSubcontext(javax.naming.Name)
*/
public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException
{
return createSubcontext(name.toString(), attrs);
}
/**
* Searches in a single context for objects that contain a
* specified set of attributes, and retrieves selected attributes.
* See {@link #search(javax.naming.Name, javax.naming.directory.Attributes, String[])} for details.
*
* @param name the name of the context to search
* @param matchingAttributes the attributes to search for
* @param attributesToReturn the attributes to return
* @return a non-null enumeration of <tt>SearchResult</tt> objects
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NamingEnumeration search(String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException
{
// create a search filter based on the matchingAttributes list.
String filter;
filter = getAttributeMatchFilter(matchingAttributes);
// TODO search context crap: alias handling, time limits etc.
return doDsmlSearch(name, WHOLESUBTREE, SEARCHING, 0, 0, false, filter, attributesToReturn);
}
/**
* Searches in a single context for objects that contain a
* specified set of attributes, and retrieves selected attributes.
* The search is performed using the default
* <code>SearchControls</code> settings.
* <p/>
* For an object to be selected, each attribute in
* <code>matchingAttributes</code> must match some attribute of the
* object. If <code>matchingAttributes</code> is empty or
* null, all objects in the target context are returned.
* <p/>
* An attribute <em>A</em><sub>1</sub> in
* <code>matchingAttributes</code> is considered to match an
* attribute <em>A</em><sub>2</sub> of an object if
* <em>A</em><sub>1</sub> and <em>A</em><sub>2</sub> have the same
* identifier, and each value of <em>A</em><sub>1</sub> is equal
* to some value of <em>A</em><sub>2</sub>. This implies that the
* order of values is not significant, and that
* <em>A</em><sub>2</sub> may contain "extra" values not found in
* <em>A</em><sub>1</sub> without affecting the comparison. It
* also implies that if <em>A</em><sub>1</sub> has no values, then
* testing for a match is equivalent to testing for the presence
* of an attribute <em>A</em><sub>2</sub> with the same
* identifier.
* <p/>
* The precise definition of "equality" used in comparing attribute values
* is defined by the underlying directory service. It might use the
* <code>Object.equals</code> method, for example, or might use a schema
* to specify a different equality operation.
* For matching based on operations other than equality (such as
* substring comparison) use the version of the <code>search</code>
* method that takes a filter argument.
* <p/>
* When changes are made to this <tt>DirContext</tt>,
* the effect on enumerations returned by prior calls to this method
* is undefined.
* <p/>
* If the object does not have the attribute
* specified, the directory will ignore the nonexistent attribute
* and return the requested attributes that the object does have.
* <p/>
* A directory might return more attributes than was requested
* (see <strong>Attribute Type Names</strong> in the class description),
* but is not allowed to return arbitrary, unrelated attributes.
* <p/>
* See also <strong>Operational Attributes</strong> in the class
* description.
*
* @param name the name of the context to search
* @param matchingAttributes the attributes to search for. If empty or null,
* all objects in the target context are returned.
* @param attributesToReturn the attributes to return. null indicates that
* all attributes are to be returned;
* an empty array indicates that none are to be returned.
* @return a non-null enumeration of <tt>SearchResult</tt> objects.
* Each <tt>SearchResult</tt> contains the attributes
* identified by <code>attributesToReturn</code>
* and the name of the corresponding object, named relative
* to the context named by <code>name</code>.
* @throws javax.naming.NamingException if a naming exception is encountered
* @see javax.naming.directory.SearchControls
* @see javax.naming.directory.SearchResult
* @see #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)
*/
public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException
{
return search(name.toString(), matchingAttributes, attributesToReturn);
}
/**
* This takes a list of Attributes and produces a 'present' match filter based on them.
*
* @param matchingAttributes a list of attributes required to be present (e.g. 'person', 'favoriteDrink')
* @return the ldap filter for matching the attributes; e.g. (&(person=*)(favoriteDrink=*))
*/
static String getAttributeMatchFilter(Attributes matchingAttributes)
{
String filter;
int numbAtts = (matchingAttributes == null) ? 0 : matchingAttributes.size();
if (numbAtts == 0)
{
filter = "(objectClass=*)";
}
else
{
StringBuffer filterBuffer = new StringBuffer();
if (numbAtts > 1)
filterBuffer.append("(&");
Enumeration atts = matchingAttributes.getIDs(); // ignore NamingException, as these atts come from the client, not the server
while (atts.hasMoreElements())
{
String att = (String) atts.nextElement();
filterBuffer.append("(").append(att).append("=*)");
}
if (numbAtts > 1)
filterBuffer.append(")");
filter = filterBuffer.toString();
}
return filter;
}
/**
* Searches in the named context or object for entries that satisfy the
* given search filter. Performs the search as specified by
* the search controls.
* <p/>
* See {@link #search(javax.naming.Name, String, javax.naming.directory.SearchControls)} for details.
*
* @param name the name of the context or object to search
* @param filter the filter expression to use for the search; may not be null
* @param cons the search controls that control the search. If null,
* the default search controls are used (equivalent
* to <tt>(new SearchControls())</tt>).
* @return an enumeration of <tt>SearchResult</tt>s for
* the objects that satisfy the filter.
* @throws javax.naming.directory.InvalidSearchFilterException if the search filter specified is
* not supported or understood by the underlying directory
* @throws javax.naming.directory.InvalidSearchControlsException if the search controls
* contain invalid settings
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException
{
return search(name, filter, null, cons); //To change body of implemented methods use File | Settings | File Templates.
}
/**
* Searches in the named context or object for entries that satisfy the
* given search filter. Performs the search as specified by
* the search controls.
* <p/>
* The format and interpretation of <code>filter</code> follows RFC 2254
* with the
* following interpretations for <code>attr</code> and <code>value</code>
* mentioned in the RFC.
* <p/>
* <code>attr</code> is the attribute's identifier.
* <p/>
* <code>value</code> is the string representation the attribute's value.
* The translation of this string representation into the attribute's value
* is directory-specific.
* <p/>
* For the assertion "someCount=127", for example, <code>attr</code>
* is "someCount" and <code>value</code> is "127".
* The provider determines, based on the attribute ID ("someCount")
* (and possibly its schema), that the attribute's value is an integer.
* It then parses the string "127" appropriately.
* <p/>
* Any non-ASCII characters in the filter string should be
* represented by the appropriate Java (Unicode) characters, and
* not encoded as UTF-8 octets. Alternately, the
* "backslash-hexcode" notation described in RFC 2254 may be used.
* <p/>
* If the directory does not support a string representation of
* some or all of its attributes, the form of <code>search</code> that
* accepts filter arguments in the form of Objects can be used instead.
* The service provider for such a directory would then translate
* the filter arguments to its service-specific representation
* for filter evaluation.
* See <code>search(Name, String, Object[], SearchControls)</code>.
* <p/>
* RFC 2254 defines certain operators for the filter, including substring
* matches, equality, approximate match, greater than, less than. These
* operators are mapped to operators with corresponding semantics in the
* underlying directory. For example, for the equals operator, suppose
* the directory has a matching rule defining "equality" of the
* attributes in the filter. This rule would be used for checking
* equality of the attributes specified in the filter with the attributes
* of objects in the directory. Similarly, if the directory has a
* matching rule for ordering, this rule would be used for
* making "greater than" and "less than" comparisons.
* <p/>
* Not all of the operators defined in RFC 2254 are applicable to all
* attributes. When an operator is not applicable, the exception
* <code>InvalidSearchFilterException</code> is thrown.
* <p/>
* The result is returned in an enumeration of <tt>SearchResult</tt>s.
* Each <tt>SearchResult</tt> contains the name of the object
* and other information about the object (see SearchResult).
* The name is either relative to the target context of the search
* (which is named by the <code>name</code> parameter), or
* it is a URL string. If the target context is included in
* the enumeration (as is possible when
* <code>cons</code> specifies a search scope of
* <code>SearchControls.OBJECT_SCOPE</code> or
* <code>SearchControls.SUBSTREE_SCOPE</code>), its name is the empty
* string. The <tt>SearchResult</tt> may also contain attributes of the
* matching object if the <tt>cons</tt> argument specified that attributes
* be returned.
* <p/>
* If the object does not have a requested attribute, that
* nonexistent attribute will be ignored. Those requested
* attributes that the object does have will be returned.
* <p/>
* A directory might return more attributes than were requested
* (see <strong>Attribute Type Names</strong> in the class description)
* but is not allowed to return arbitrary, unrelated attributes.
* <p/>
* See also <strong>Operational Attributes</strong> in the class
* description.
* <p/>
* <p>Equivalent to search(name, filter, null, cons).
*
* @param name the name of the context or object to search
* @param filter the filter expression to use for the search; may not be null
* @param cons the search controls that control the search. If null,
* the default search controls are used (equivalent
* to <tt>(new SearchControls())</tt>).
* @return an enumeration of <tt>SearchResult</tt>s of
* the objects that satisfy the filter; never null
* @throws javax.naming.directory.InvalidSearchFilterException if the search filter specified is
* not supported or understood by the underlying directory
* @throws javax.naming.directory.InvalidSearchControlsException if the search controls
* contain invalid settings
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)
* @see javax.naming.directory.SearchControls
* @see javax.naming.directory.SearchResult
*/
public NamingEnumeration search(Name name, String filter, SearchControls cons) throws NamingException
{
return search(name, filter, null, cons);
}
/**
* <p>Searches in the named context or object for entries that satisfy the
* given search filter. Performs the search as specified by
* the search controls.
* <p/>
* See {@link #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)} for details.
*
* @param name the name of the context or object to search
* @param filterExpr the filter expression to use for the search.
* The expression may contain variables of the
* form "<code>{i}</code>" where <code>i</code>
* is a nonnegative integer. May not be null.
* @param filterArgs <b>NOT IMPLEMENTED!</b>
* the array of arguments to substitute for the variables
* in <code>filterExpr</code>. The value of
* <code>filterArgs[i]</code> will replace each
* occurrence of "<code>{i}</code>".
* If null, equivalent to an empty array.
* @param cons the search controls that control the search. If null,
* the default search controls are used (equivalent
* to <tt>(new SearchControls())</tt>).
* @return an enumeration of <tt>SearchResult</tt>s of the objects
* that satisfy the filter; never null
* @throws ArrayIndexOutOfBoundsException if <tt>filterExpr</tt> contains
* <code>{i}</code> expressions where <code>i</code> is outside
* the bounds of the array <code>filterArgs</code>
* @throws javax.naming.directory.InvalidSearchControlsException if <tt>cons</tt> contains
* invalid settings
* @throws javax.naming.directory.InvalidSearchFilterException if <tt>filterExpr</tt> with
* <tt>filterArgs</tt> represents an invalid search filter
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException
{
if (filterExpr == null)
throw new NamingException("null ldap filter in DsmlContext search");
if (filterArgs != null && filterArgs.length > 0)
throw new NamingException("filter arguments not implemented in com.ca.jndiproviders.dsml provider");
log.finest("search III (" + name.toString() + ") + filter: " + filterExpr);
if (cons == null)
cons = DEFAULT_SEARCH_CONTROLS;
// translate JNDI search scope into DSML speak
String searchScope;
switch (cons.getSearchScope())
{
case SearchControls.OBJECT_SCOPE:
searchScope = BASEOBJECT;
break;
case SearchControls.ONELEVEL_SCOPE:
searchScope = SINGLELEVEL;
break;
case SearchControls.SUBTREE_SCOPE:
searchScope = WHOLESUBTREE;
break;
default:
throw new NamingException("unexpected error; unknown search scope in SearchControl object: " + cons.getSearchScope()); // really, really, really should be impossible
}
// translate JNDI alias handling into DSML speak
String jndiAliasHandling = (String) environment.get("java.naming.ldap.derefAliases");
if (jndiAliasHandling == null)
jndiAliasHandling = "always";
String aliasHandling = "derefAlways";
if (jndiAliasHandling.equals("always"))
aliasHandling = ALWAYS;
else if (jndiAliasHandling.equals("never"))
aliasHandling = NEVER;
else if (jndiAliasHandling.equals("finding"))
aliasHandling = FINDING;
else if (jndiAliasHandling.equals("searching"))
aliasHandling = SEARCHING;
// doesn't seem to be anything corresponding to the DSML 'types only' flag, unless possibly it is cons.getReturningObjFlag()??
// do the DSML search using the jndiproviders search controls data, environment data, and passed parameters
NamingEnumeration returnEnum = doDsmlSearch(name, searchScope, aliasHandling, cons.getCountLimit(), cons.getTimeLimit(), false, filterExpr, cons.getReturningAttributes());
return returnEnum;
}
/**
* Searches in the named context or object for entries that satisfy the
* given search filter. Performs the search as specified by
* the search controls.
* <p/>
* The interpretation of <code>filterExpr</code> is based on RFC
* 2254. It may additionally contain variables of the form
* <code>{i}</code> -- where <code>i</code> is an integer -- that
* refer to objects in the <code>filterArgs</code> array. The
* interpretation of <code>filterExpr</code> is otherwise
* identical to that of the <code>filter</code> parameter of the
* method <code>search(Name, String, SearchControls)</code>.
* <p/>
* When a variable <code>{i}</code> appears in a search filter, it
* indicates that the filter argument <code>filterArgs[i]</code>
* is to be used in that place. Such variables may be used
* wherever an <em>attr</em>, <em>value</em>, or
* <em>matchingrule</em> production appears in the filter grammar
* of RFC 2254, section 4. When a string-valued filter argument
* is substituted for a variable, the filter is interpreted as if
* the string were given in place of the variable, with any
* characters having special significance within filters (such as
* <code>'*'</code>) having been escaped according to the rules of
* RFC 2254.
* <p/>
* For directories that do not use a string representation for
* some or all of their attributes, the filter argument
* corresponding to an attribute value may be of a type other than
* String. Directories that support unstructured binary-valued
* attributes, for example, should accept byte arrays as filter
* arguments. The interpretation (if any) of filter arguments of
* any other type is determined by the service provider for that
* directory, which maps the filter operations onto operations with
* corresponding semantics in the underlying directory.
* <p/>
* This method returns an enumeration of the results.
* Each element in the enumeration contains the name of the object
* and other information about the object (see <code>SearchResult</code>).
* The name is either relative to the target context of the search
* (which is named by the <code>name</code> parameter), or
* it is a URL string. If the target context is included in
* the enumeration (as is possible when
* <code>cons</code> specifies a search scope of
* <code>SearchControls.OBJECT_SCOPE</code> or
* <code>SearchControls.SUBSTREE_SCOPE</code>),
* its name is the empty string.
* <p/>
* The <tt>SearchResult</tt> may also contain attributes of the matching
* object if the <tt>cons</tt> argument specifies that attributes be
* returned.
* <p/>
* If the object does not have a requested attribute, that
* nonexistent attribute will be ignored. Those requested
* attributes that the object does have will be returned.
* <p/>
* A directory might return more attributes than were requested
* (see <strong>Attribute Type Names</strong> in the class description)
* but is not allowed to return arbitrary, unrelated attributes.
* <p/>
* If a search filter with invalid variable substitutions is provided
* to this method, the result is undefined.
* When changes are made to this DirContext,
* the effect on enumerations returned by prior calls to this method
* is undefined.
* <p/>
* See also <strong>Operational Attributes</strong> in the class
* description.
*
* @param name the name of the context or object to search
* @param filterExpr the filter expression to use for the search.
* The expression may contain variables of the
* form "<code>{i}</code>" where <code>i</code>
* is a nonnegative integer. May not be null.
* @param filterArgs the array of arguments to substitute for the variables
* in <code>filterExpr</code>. The value of
* <code>filterArgs[i]</code> will replace each
* occurrence of "<code>{i}</code>".
* If null, equivalent to an empty array.
* @param cons the search controls that control the search. If null,
* the default search controls are used (equivalent
* to <tt>(new SearchControls())</tt>).
* @return an enumeration of <tt>SearchResult</tt>s of the objects
* that satisfy the filter; never null
* @throws ArrayIndexOutOfBoundsException if <tt>filterExpr</tt> contains
* <code>{i}</code> expressions where <code>i</code> is outside
* the bounds of the array <code>filterArgs</code>
* @throws javax.naming.directory.InvalidSearchControlsException if <tt>cons</tt> contains
* invalid settings
* @throws javax.naming.directory.InvalidSearchFilterException if <tt>filterExpr</tt> with
* <tt>filterArgs</tt> represents an invalid search filter
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #search(javax.naming.Name, javax.naming.directory.Attributes, String[])
* @see java.text.MessageFormat
*/
public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException
{
return search(name.toString(), filterExpr, filterArgs, cons);
}
/**
* Closes this context.
* This method releases this context's resources immediately, instead of
* waiting for them to be released automatically by the garbage collector.
* <p/>
* <p> This method is idempotent: invoking it on a context that has
* already been closed has no effect. Invoking any other method
* on a closed context is not allowed, and results in undefined behaviour.
*
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public void close() throws NamingException
{
log.finest("close()");
// TODO - any cleanup required here?
//To change body of implemented methods use File | Settings | File Templates.
}
/**
* Retrieves the full name of this context within its own namespace.
* <p/>
* <p> Many naming services have a notion of a "full name" for objects
* in their respective namespaces. For example, an LDAP entry has
* a distinguished name, and a DNS record has a fully qualified name.
* This method allows the client application to retrieve this name.
* The string returned by this method is not a JNDI composite name
* and should not be passed directly to context methods.
* In naming systems for which the notion of full name does not
* make sense, <tt>OperationNotSupportedException</tt> is thrown.
*
* @return this context's name in its own namespace; never null
* @throws javax.naming.OperationNotSupportedException if the naming system does
* not have the notion of a full name
* @throws javax.naming.NamingException if a naming exception is encountered
* @since 1.3
*/
public String getNameInNamespace() throws NamingException
{
log.finest("getNameInNamespace()");
return contextName;
}
/**
* Destroys the named context and removes it from the namespace.
* See {@link #destroySubcontext(javax.naming.Name)} for details.
*
* @param name the name of the context to be destroyed; may not be empty
* @throws javax.naming.NameNotFoundException if an intermediate context does not exist
* @throws javax.naming.NotContextException if the name is bound but does not name a
* context, or does not name a context of the appropriate type
* @throws javax.naming.ContextNotEmptyException if the named context is not empty
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public void destroySubcontext(String name) throws NamingException
{
log.finest("destroySubcontext (" + name.toString() + ")");
// construct XML
StringBuffer deleteRequestBuffer = constructDeleteRequest(name);
// send XML to server
String response = sendDSMLRequest(deleteRequestBuffer);
// parse response XML
parseDeleteResponse(response);
}
void parseDeleteResponse(String response)
throws NamingException
{
checkForError(response);
// quick check that it really was a modify response...
if (response.indexOf("delResponse>") == -1)
throw new NamingException("Unexpected DSML Response to Delete Request:\n " + response);
}
/**
* Unbinds the named object.
* See {@link #unbind(javax.naming.Name)} for details.
*
* @param name the name to unbind; may not be empty
* @throws javax.naming.NameNotFoundException if an intermediate context does not exist
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated bind operations not supported
*/
public void unbind(String name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support storing java objects");
//To change body of implemented methods use File | Settings | File Templates.
}
/**
* Retrieves the environment in effect for this context.
* See class description for more details on environment properties.
* <p/>
* <p> The caller should not make any changes to the object returned:
* their effect on the context is undefined.
* The environment of this context may be changed using
* <tt>addToEnvironment()</tt> and <tt>removeFromEnvironment()</tt>.
*
* @return the environment of this context; never null
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #addToEnvironment(String, Object)
* @see #removeFromEnvironment(String)
*/
public Hashtable getEnvironment() throws NamingException
{
return (Hashtable) environment.clone(); //To change body of implemented methods use File | Settings | File Templates.
}
/**
* Destroys the named context and removes it from the namespace.
* Any attributes associated with the name are also removed.
* Intermediate contexts are not destroyed.
* <p/>
* <p> This method is idempotent.
* It succeeds even if the terminal atomic name
* is not bound in the target context, but throws
* <tt>NameNotFoundException</tt>
* if any of the intermediate contexts do not exist.
* <p/>
* <p> In a federated naming system, a context from one naming system
* may be bound to a name in another. One can subsequently
* look up and perform operations on the foreign context using a
* composite name. However, an attempt destroy the context using
* this composite name will fail with
* <tt>NotContextException</tt>, because the foreign context is not
* a "subcontext" of the context in which it is bound.
* Instead, use <tt>unbind()</tt> to remove the
* binding of the foreign context. Destroying the foreign context
* requires that the <tt>destroySubcontext()</tt> be performed
* on a context from the foreign context's "native" naming system.
*
* @param name the name of the context to be destroyed; may not be empty
* @throws javax.naming.NameNotFoundException if an intermediate context does not exist
* @throws javax.naming.NotContextException if the name is bound but does not name a
* context, or does not name a context of the appropriate type
* @throws javax.naming.ContextNotEmptyException if the named context is not empty
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #destroySubcontext(String)
*/
public void destroySubcontext(Name name) throws NamingException
{
destroySubcontext(name.toString());
//To change body of implemented methods use File | Settings | File Templates.
}
/**
* Unbinds the named object.
* Removes the terminal atomic name in <code>name</code>
* from the target context--that named by all but the terminal
* atomic part of <code>name</code>.
* <p/>
* <p> This method is idempotent.
* It succeeds even if the terminal atomic name
* is not bound in the target context, but throws
* <tt>NameNotFoundException</tt>
* if any of the intermediate contexts do not exist.
* <p/>
* <p> Any attributes associated with the name are removed.
* Intermediate contexts are not changed.
*
* @param name the name to unbind; may not be empty
* @throws javax.naming.NameNotFoundException if an intermediate context does not exist
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #unbind(String)
* @deprecated bind/unbind operations not supported by DsmlContext
*/
public void unbind(Name name) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support storing java objects");
}
/**
* Retrieves the named object.
* See {@link #lookup(javax.naming.Name)} for details.
*
* @param name the name of the object to look up
* @return the object bound to <tt>name</tt>
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public Object lookup(String name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Retrieves the named object, following links except
* for the terminal atomic component of the name.
* See {@link #lookupLink(javax.naming.Name)} for details.
*
* @param name the name of the object to look up
* @return the object bound to <tt>name</tt>, not following the
* terminal link (if any)
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public Object lookupLink(String name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Removes an environment property from the environment of this
* context. See class description for more details on environment
* properties.
*
* @param propName the name of the environment property to remove; may not be null
* @return the previous value of the property, or null if the property was
* not in the environment
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #getEnvironment()
* @see #addToEnvironment(String, Object)
*/
public Object removeFromEnvironment(String propName) throws NamingException
{
return environment.remove(propName);
}
/**
* Binds a name to an object.
* See {@link #bind(javax.naming.Name, Object)} for details.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public void bind(String name, Object obj) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object, overwriting any existing binding.
* See {@link #rebind(javax.naming.Name, Object)} for details.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public void rebind(String name, Object obj) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Retrieves the named object.
* If <tt>name</tt> is empty, returns a new instance of this context
* (which represents the same naming context as this context, but its
* environment may be modified independently and it may be accessed
* concurrently).
*
* @param name the name of the object to look up
* @return the object bound to <tt>name</tt>
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #lookup(String)
* @see #lookupLink(javax.naming.Name)
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public Object lookup(Name name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Retrieves the named object, following links except
* for the terminal atomic component of the name.
* If the object bound to <tt>name</tt> is not a link,
* returns the object itself.
*
* @param name the name of the object to look up
* @return the object bound to <tt>name</tt>, not following the
* terminal link (if any).
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #lookupLink(String)
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public Object lookupLink(Name name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object.
* All intermediate contexts and the target context (that named by all
* but terminal atomic component of the name) must already exist.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #bind(String, Object)
* @see #rebind(javax.naming.Name, Object)
* @see javax.naming.directory.DirContext#bind(javax.naming.Name, Object,
* javax.naming.directory.Attributes)
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public void bind(Name name, Object obj) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a name to an object, overwriting any existing binding.
* All intermediate contexts and the target context (that named by all
* but terminal atomic component of the name) must already exist.
* <p/>
* <p> If the object is a <tt>DirContext</tt>, any existing attributes
* associated with the name are replaced with those of the object.
* Otherwise, any existing attributes associated with the name remain
* unchanged.
*
* @param name the name to bind; may not be empty
* @param obj the object to bind; possibly null
* @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #rebind(String, Object)
* @see #bind(javax.naming.Name, Object)
* @see javax.naming.directory.DirContext#rebind(javax.naming.Name, Object,
* javax.naming.directory.Attributes)
* @see javax.naming.directory.DirContext
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public void rebind(Name name, Object obj) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Binds a new name to the object bound to an old name, and unbinds
* the old name.
* See {@link #rename(javax.naming.Name, javax.naming.Name)} for details.
*
* @param oldName the name of the existing binding; may not be empty
* @param newName the name of the new binding; may not be empty
* @throws javax.naming.NameAlreadyBoundException if <tt>newName</tt> is already bound
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public void rename(String oldName, String newName) throws NamingException
{
// note - this is the only time we use the 'name' version of the call,
// because we have to do some processing rather than writing the string
// out to DSML immediately.
rename(new DN(oldName), new DN(newName));
}
StringBuffer constructModDNRequest(String oldName, String newName)
throws NamingException
{
StringBuffer message = new StringBuffer(200);
message.append(STANDARDHEADER);
getModDNRequestElement(message, oldName, newName);
message.append(STANDARDFOOTER);
return message;
}
/**
* Renames an entry. Note that 'new Superior' is not supported; entries must
* be copied and deleted instead.
*
* @param message
* @param oldName the full DN of the old entry name
* @param newRDN the rdn of the new entry name
*/
/*
" <modDNRequest dn="CN=Alice Johnson,DC=Example,DC=COM"
newrdn="CN=Alice Weiss"
deleteoldrdn="true"
newSuperior="OU=Marketing,DC=Example,DC=COM"/>
*/
void getModDNRequestElement(StringBuffer message, String oldName, String newRDN)
{
message.append(TAB4).append("<dsml:modDNRequest dn=\"").append(escapeName(oldName)).append("\" newrdn=\"").append(escapeName(newRDN)).append("\" ");
if (environment.get("java.naming.ldap.deleteRDN").toString().equalsIgnoreCase("false"))
message.append("deleteoldrdn=\"false\"/>");
else
message.append("deleteoldrdn=\"true\"/>");
}
void parseModDNResponse(String response)
throws NamingException
{
// check for a dsml error
checkForError(response);
// quick check that it really was a modify response...
if (response.indexOf("modDNResponse>") == -1)
throw new NamingException("Unexpected DSML Response to Modify DN Request:\n " + response);
}
/**
* Creates and binds a new context.
* See {@link #createSubcontext(javax.naming.Name)} for details.
*
* @param name the name of the context to create; may not be empty
* @return the newly created context
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if creation of the subcontext requires specification of
* mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated it is impossible to create a directory object without attributes (RFC 2251 - sec 4.7)
*/
public Context createSubcontext(String name) throws NamingException
{
throw new InvalidAttributesException("cannot create directory object without attributes");
}
/**
* Creates and binds a new context.
* Creates a new context with the given name and binds it in
* the target context (that named by all but terminal atomic
* component of the name). All intermediate contexts and the
* target context must already exist.
*
* @param name the name of the context to create; may not be empty
* @return the newly created context
* @throws javax.naming.NameAlreadyBoundException if name is already bound
* @throws javax.naming.directory.InvalidAttributesException if creation of the subcontext requires specification of
* mandatory attributes
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #createSubcontext(String)
* @see javax.naming.directory.DirContext#createSubcontext
* @deprecated it is impossible to create a directory object without attributes (RFC 2251 - sec 4.7)
*/
public Context createSubcontext(Name name) throws NamingException
{
throw new InvalidAttributesException("cannot create directory object without attributes");
}
/**
* Binds a new name to the object bound to an old name, and unbinds
* the old name. Both names are relative to this context.
* Any attributes associated with the old name become associated
* with the new name.
* Intermediate contexts of the old name are not changed.
*
* @param oldName the name of the existing binding; may not be empty
* @param rdn the RDN of the new binding; may not be empty
* @throws javax.naming.NameAlreadyBoundException if <tt>newName</tt> is already bound
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #rename(String, String)
* @see #bind(javax.naming.Name, Object)
* @see #rebind(javax.naming.Name, Object)
*/
public void rename(Name oldName, Name rdn) throws NamingException
{
log.finest("rename (" + oldName + " to " + rdn);
String oldNameString = oldName.toString();
if (rdn.size() != 1)
throw new NamingException("cannot perform rename operation '" + rdn + "' is not an RDN \n" );
String newRDN = rdn.toString();
/*
String newNameString = newName.toString();
if (!oldNameString.endsWith(contextName))
oldNameString = oldNameString + "," + contextName;
if (!newNameString.endsWith(contextName))
{
newNameString = newNameString + "," + contextName;
newName = new DN(newNameString);
}
if (newName.size() != 1 && newName.size() != oldName.size())
throw new NamingException("cannot perform rename operation - DNs are at different levels:\n" + oldNameString + "\n" + newNameString);
int nameSize = newName.size();
if (!oldName.getPrefix(nameSize - 2).equals(newName.getPrefix(nameSize - 2)))
throw new NamingException("cannot perform rename operation - RDNs have different parents:\n" + oldNameString + "\n" + newNameString);
String newRDN = newName.get(newName.size() - 1).toString();
*/
// construct XML
StringBuffer modDNRequestBuffer = constructModDNRequest(oldNameString, newRDN);
// send XML to server
String response = sendDSMLRequest(modDNRequestBuffer);
// parse response XML
parseModDNResponse(response);
}
/**
* Retrieves the parser associated with the named context. Note that the
* DsmlContext does not support composite names, and so DsmlParser.parser
* is returned regardless of what name is passed.
* See {@link #getNameParser(javax.naming.Name)} for details.
*
* @param name the name of the context from which to get the parser
* @return a name parser that can parse compound names into their atomic
* components
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NameParser getNameParser(String name) throws NamingException
{
return DsmlParser.parser;
}
/**
* Retrieves the parser associated with the named context. Note that the
* DsmlContext does not support composite names, and so DsmlParser.parser
* is returned regardless of what name is passed.
*
* @param name the name of the context from which to get the parser (not used)
* @return a name parser that can parse compound DSML/LDAP names into their atomic
* components
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #getNameParser(String)
* @see javax.naming.CompoundName
*/
public NameParser getNameParser(Name name) throws NamingException
{
return DsmlParser.parser;
}
/**
* Enumerates the names bound in the named context.
* See {@link #list(javax.naming.Name)} for details.
*
* @param name the name of the context to list
* @return an enumeration of the names and class names of the
* bindings in this context. Each element of the
* enumeration is of type <tt>NameClassPair</tt>.
* @throws javax.naming.NamingException if a naming exception is encountered
*/
public NamingEnumeration list(String name) throws NamingException
{
log.finest("list (" + name.toString() + ")");
return doDsmlSearch(name, SINGLELEVEL, SEARCHING, 0, 0, false, "(objectClass=*)", null);
}
/**
* Enumerates the names bound in the named context, along with the
* objects bound to them.
* See {@link #listBindings(javax.naming.Name)} for details.
*
* @param name the name of the context to list
* @return an enumeration of the bindings in this context.
* Each element of the enumeration is of type
* <tt>Binding</tt>.
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public NamingEnumeration listBindings(String name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Enumerates the names bound in the named context, along with the
* class names of objects bound to them.
* The contents of any subcontexts are not included.
* <p/>
* <p> If a binding is added to or removed from this context,
* its effect on an enumeration previously returned is undefined.
*
* @param name the name of the context to list
* @return an enumeration of the names and class names of the
* bindings in this context. Each element of the
* enumeration is of type <tt>NameClassPair</tt>.
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #list(String)
* @see #listBindings(javax.naming.Name)
* @see javax.naming.NameClassPair
*/
public NamingEnumeration list(Name name) throws NamingException
{
return list(name.toString());
}
/**
* Enumerates the names bound in the named context, along with the
* objects bound to them.
* The contents of any subcontexts are not included.
* <p/>
* <p> If a binding is added to or removed from this context,
* its effect on an enumeration previously returned is undefined.
*
* @param name the name of the context to list
* @return an enumeration of the bindings in this context.
* Each element of the enumeration is of type
* <tt>Binding</tt>.
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #listBindings(String)
* @see #list(javax.naming.Name)
* @see javax.naming.Binding
* @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
*/
public NamingEnumeration listBindings(Name name) throws NamingException
{
throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
}
/**
* Adds a new environment property to the environment of this
* context. If the property already exists, its value is overwritten.
* See class description for more details on environment properties.
*
* @param propName the name of the environment property to add; may not be null
* @param propVal the value of the property to add; may not be null
* @return the previous value of the property, or null if the property was
* not in the environment before
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #getEnvironment()
* @see #removeFromEnvironment(String)
*/
public Object addToEnvironment(String propName, Object propVal) throws NamingException
{
log.finest("addToEnvironment (" + propName + ": " + propVal.toString());
if (propName == null)
return null;
if (propVal == null)
return environment.remove(propName);
else
return environment.put(propName, propVal);
}
/**
* Not implemented - use DN methods instead if you want to create new Names.
* DsmlContext does not support composite names (stupid idea anyway).
*
* @param name a name relative to this context
* @param prefix the name of this context relative to one of its ancestors
* @return the composition of <code>prefix</code> and <code>name</code>
* @throws javax.naming.NamingException if a naming exception is encountered
* @deprecated
*/
public String composeName(String name, String prefix) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support composing names - use DN methods directly");
}
/**
* Not implemented - use DN methods instead if you want to create new Names.
* DsmlContext does not support composite names (stupid idea anyway).
*
* @param name a name relative to this context
* @param prefix the name of this context relative to one of its ancestors
* @return the composition of <code>prefix</code> and <code>name</code>
* @throws javax.naming.NamingException if a naming exception is encountered
* @see #composeName(String, String)
* @deprecated
*/
public Name composeName(Name name, Name prefix) throws NamingException
{
throw new OperationNotSupportedException("DsmlContext does not support composing names - use DN methods directly");
}
/*
* ---------- DSML Specific Code ------------
*/
/**
* This is the central coordinating search method. It takes all the raw paramaters, and hands them off
* to various sub methods do construct the search request, send it to the DSML server and get the result.
*
* @param name
* @param scope
* @param derefAliases
* @param sizeLimit
* @param timeLimit
* @param typesOnly
* @param filter
* @param attributesToReturn
*
* @return the search results
*
* @throws NamingException
*/
public NamingEnumeration doDsmlSearch(String name, String scope, String derefAliases, long sizeLimit, int timeLimit, boolean typesOnly, String filter, String[] attributesToReturn)
throws NamingException
{
// construct XML
StringBuffer searchRequestBuffer = constructSearchRequest(name, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributesToReturn);
// send XML to server
String response = sendDSMLRequest(searchRequestBuffer);
// parse response XML
return parseSearchResponse(response, name);
}
// static patterns for parsing search results.
// private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry (.*)</searchResultEntry>");
// nb. Extreme caution required here to match DNs that may contain '"' and '>' characters. Note that no legal dn
// can contain the sequence '">', since the " and > both need escaping. There *might* be a requestID in there
// as well, but I've never seen this in the wild (what would the point be?)
// need to cope with option namespace prefixes...
// private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry.*?dn=\"(.*?)\">(.*?)</searchResultEntry>", Pattern.DOTALL);
// private static Pattern searchResultAttribute = Pattern.compile("<attr name=\"(.*?)\">(.*?)</attr>", Pattern.DOTALL);
// private static Pattern searchResultAttributeValues = Pattern.compile("<value(.*?)>(.*?)</value>", Pattern.DOTALL);
private static Pattern searchResultEntry = Pattern.compile("searchResultEntry.*?dn=\"(.*?)\">(.*?)searchResultEntry>", Pattern.DOTALL);
private static Pattern searchResultAttribute = Pattern.compile("attr name=\"(.*?)\">(.*?)attr>", Pattern.DOTALL);
private static Pattern searchResultAttributeValues = Pattern.compile("value(.*?)>(.*?)<.*?value>", Pattern.DOTALL);
private static Pattern searchTestType = Pattern.compile("type=\"(.*?)\"", Pattern.DOTALL);
/**
* This is one of the central methods of the provider. It takes the raw XML returned and extracts the
* DNs and attributes of the returned entries into a standard namingenumeration of SearchResult objects.
*
* @param response the DSML XML response
* @param searchBase supply the search base so that this method can check if the DNs returned
* in the search result are relative or not. By default a search result's DN is set to relative.
* If the DN returned in the search result includes the search base then it is not relative and
* we need to set that so users of this provider don't re-add the search base after performing a
* isRelative call on the SearchResult.
* @return a naming enumeration of 'SearchResult' objects.
* @throws NamingException if any error is found (including a returned error from the DSML server).
*/
static NamingEnumeration parseSearchResponse(String response, String searchBase)
throws NamingException
{
checkForError(response);
DXNamingEnumeration en = new DXNamingEnumeration();
Matcher responseMatcher = searchResultEntry.matcher(response);
// cycle through the available entries
while (responseMatcher.find())
{
String dn = responseMatcher.group(1);
BasicAttributes atts = new BasicAttributes();
if (dn.indexOf("requestID") > -1) // what numb nut would put a requestID in a searchResultEntry??
{
int reqPos = dn.indexOf("requestID");
int endPos = dn.lastIndexOf('"', reqPos);
dn = dn.substring(0, endPos);
}
if (dn.indexOf("&") > -1)
dn = unescape(dn);
log.finest("Parsing DSML: read DN: " + dn);
// turn pure pristine LDAP DN into un unbelievably screwed up JNDI Composite name, and then
// store it as a string SO NO ONE CAN EVER REALLY TELL WHAT THE HELL IT IS. God JNDI is
// broken - what a (*&*^ joke. Object Oriented? Yeah, we've heard of that...
dn = new CompositeName(dn).toString();
log.finest("converted DN to: " + dn);
String attribute = responseMatcher.group(2);
Matcher attributeMatcher = searchResultAttribute.matcher(attribute);
// cycle through individual attributes
while (attributeMatcher.find())
{
String attributeName = attributeMatcher.group(1);
BasicAttribute att = new BasicAttribute(attributeName);
String attributeValues = attributeMatcher.group(2);
log.finest("Parsing DSML: Attribute Name " + attributeName + " length: " + attributeValues.length());
Matcher valueMatcher = searchResultAttributeValues.matcher(attributeValues);
while (valueMatcher.find())
{
String typeInfo = valueMatcher.group(1);
String type = "string"; // the default value is string
if (typeInfo != null && typeInfo.length() > 6)
{
// don't bother using regexp - faster to use string handling (if insanely clever might modify attributeValues regexp??)
int typeInfoStart = typeInfo.indexOf("type=\"");
int typeInfoEnd = typeInfo.indexOf('\"', typeInfoStart + 6);
if (typeInfoStart != -1 && typeInfoEnd > typeInfoStart)
{
type = typeInfo.substring(typeInfoStart + 6, typeInfoEnd); // extract the type; e.g. xsd:base64Binary
if ((typeInfoStart = type.indexOf(':')) > -1) // trim any namespace from the type (e.g. 'xsd:')
type = type.substring(typeInfoStart + 1);
}
}
String value = valueMatcher.group(2);
if (type.equals("string")) // this is the usual case, and the default
{
if (value != null && value.indexOf('&') > -1)
value = unescape(value);
att.add(value);
}
else if (type.equals("anyURI"))
throw new NamingException("CA JNDI DSML Provider does not support 'anyURI' values");
else if (type.equals("base64Binary"))
{
try
{
System.out.println("PROCESSING BINARY VALUE " + attributeName);
byte[] data = CBBase64.decode(value);
System.out.println("RAW DATA: " + value.length() + " byte data " + data.length);
att.add(CBBase64.decode(value));
}
catch (CBBase64EncodingException e)
{
NamingException ne = new NamingException("unable to parse base64 value in entry: " + dn);
ne.setRootCause(e);
throw ne;
}
}
}
atts.put(att);
}
SearchResult currentResult = new SearchResult(dn, null, atts);
// The search result sets 'is relative' to true by default. Check if the
// DN in the search result contains the search base, if so set 'is relative'
// to false so that the search base is NOT added to the DN again. In otherwords
// check if the full DN has been returned in the search result...
if(dn.endsWith(searchBase))
currentResult.setRelative(false);
en.add(currentResult);
}
log.finest("Parsing DSML: final enumeration - \n" + en.toString());
return en;
}
/**
* This converts the string buffer to raw bytes, and passes them to the SoapClient 'sendSoapMsg' method,
* returning the response.
*
* @param searchRequestBuffer
*
* @return the response from the request
*
* @throws NamingException
*/
private String sendDSMLRequest(StringBuffer searchRequestBuffer)
throws NamingException
{
String response;
try
{
// note for performance junkies - assigning an unshared string buffer to a string is a cheap operation that shares the internal char array.
String searchRequest = new String(searchRequestBuffer);
byte[] rawBytes = searchRequest.getBytes("UTF-8");
String URL = environment.get(PROVIDER_URL).toString();
log.finest("----SENDING XML OVER WIRE-----\nURL: " + URL + "\nlength: " + rawBytes.length);
response = SoapClient.sendSoapMsg(URL, rawBytes, "#batchRequest");
}
catch (UnsupportedEncodingException e) // should never happen. really.
{
NamingException ne = new NamingException("unexpected exception encoding UTF-8 string for DSML message");
ne.setRootCause(e);
throw ne;
}
catch (IOException e)
{
NamingException ne = new NamingException("error contacting DSML Server");
ne.setRootCause(e);
throw ne;
}
return response;
}
/**
* Constructs an add request.
*
* @param name the name
* @param atts the attributes
*
* @return the add request
*/
static StringBuffer constructAddRequest(String name, Attributes atts)
throws NamingException
{
StringBuffer message = new StringBuffer(200);
message.append(STANDARDHEADER);
getAddRequestElement(message, name, atts);
message.append(STANDARDFOOTER);
return message;
}
/**
* returns the actual DSML 'delRequest' element.<br>
* e.g.:<br>:
* <dsml:addRequest requestID="1" dn="cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU">:
* <dsml:attr name="cn">:
* <dsml:value>:Alana SHORE</dsml:value>:
* </dsml:attr>:
* <dsml:attr name="objectClass">:
* <dsml:value>:inetOrgPerson</dsml:value>:
* <dsml:value>:organizationalPerson</dsml:value>:
* <dsml:value>:person</dsml:value>:
* <dsml:value>:top</dsml:value>:
* </dsml:attr>:
* <dsml:attr name="sn">:
* <dsml:value>:SHORE</dsml:value>:
* </dsml:attr>:
* </dsml:addRequest>:
*
* @param message
* @param name
*/
/* "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
" <dsml:addRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\">" +
" <dsml:attr name=\"cn\">" +
" <dsml:value>Alana SHORE</dsml:value>" +
" </dsml:attr>" +
" <dsml:attr name=\"objectClass\">" +
" <dsml:value>inetOrgPerson</dsml:value>" +
" <dsml:value>organizationalPerson</dsml:value>" +
" <dsml:value>person</dsml:value>" +
" <dsml:value>top</dsml:value>" +
" </dsml:attr>" +
" <dsml:attr name=\"sn\">" +
" <dsml:value>SHORE</dsml:value>" +
" </dsml:attr>" +
" </dsml:addRequest>" +
"</dsml:batchRequest>"
*/
static void getAddRequestElement(StringBuffer message, String name, Attributes atts)
throws NamingException // doesn't really...
{
message.append(TAB4).append("<dsml:addRequest dn=\"").append(escapeName(name)).append("\">\n");
NamingEnumeration attEnum = atts.getAll();
while (attEnum.hasMore())
{
Attribute att = (Attribute) attEnum.next();
String attName = att.getID();
message.append(TAB5).append("<dsml:attr name=\"").append(attName).append("\">\n");
NamingEnumeration values = att.getAll();
while (values.hasMore())
{
Object value = values.next();
createDsmlValueElement(value, message);
}
message.append(TAB5).append("</dsml:attr>\n");
}
message.append(TAB4).append("</dsml:addRequest>\n");
}
/**
* This returns a <dsml:attr ... > , coding the value either as a string
* or as a base64Binary type value (including the type marker). Note that urls references
* are not supported.
*
* @param value
* @param message
* @throws NamingException
*/
static void createDsmlValueElement(Object value, StringBuffer message)
throws NamingException
{
if (value instanceof String) // expensive check, but can't think of any way around it...
{
String stringVal = escape((String) value);
message.append(TAB6).append("<dsml:value>").append(stringVal).append("</dsml:value>\n");
}
else
{
try
{
byte[] data = (byte[]) value;
String base64Data = new String(CBBase64.encode(data), "US-ASCII");
System.out.println("SENDING BINARY DATA; byte length: " + data.length + " encoded: " + base64Data.length());
message.append(TAB6).append("<dsml:value xsi:type=\"xsd:base64Binary\">").append(base64Data).append("</dsml:value>\n");
}
catch (UnsupportedEncodingException e)
{
NamingException ne = new NamingException("unexpected encoding exception adding attribute value of type " + value.getClass());
ne.setRootCause(e);
throw ne;
}
catch (ClassCastException e)
{
NamingException ne = new NamingException("unexpected error adding attribute value of type " + value.getClass());
ne.setRootCause(e);
throw ne;
}
}
}
// "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
// " <dsml:delRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\"/>" +
// "</dsml:batchRequest>";
/**
* This returns a DSML batchrequest containing a single 'delRequest' element.
*
* @param name the name
*
* @return a DSML batchrequest containing a single 'delRequest' element
*/
static StringBuffer constructDeleteRequest(String name)
{
StringBuffer message = new StringBuffer(200);
message.append(STANDARDHEADER);
getDeleteRequestElement(message, name);
message.append(STANDARDFOOTER);
return message;
}
/**
* returns the actual DSML 'delRequest' element.<br>
* e.g.:<br>
* <dsml:delRequest requestID="1" dn="cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU"/>
*
* @param message
* @param name
*/
static void getDeleteRequestElement(StringBuffer message, String name)
{
message.append(TAB4 + "<dsml:delRequest dn=\"").append(escapeName(name)).append("\"/>\n");
}
/**
* This is a co-ordinating method that builds up the Search Request XML by calling various
* component building methods.
*
* @param name
* @param scope
* @param derefAliases
* @param sizeLimit
* @param timeLimit
* @param typesOnly
* @param filter
* @param attributesToReturn
*
* @return the search request
*
* @throws NamingException
*/
static StringBuffer constructSearchRequest(String name, String scope, String derefAliases, long sizeLimit, int timeLimit, boolean typesOnly, String filter, String[] attributesToReturn)
throws NamingException
{
StringBuffer message = new StringBuffer(500);
message.append(STANDARDHEADER);
getSearchRequestHeader(message, name, scope, derefAliases, sizeLimit, timeLimit, typesOnly);
getSearchRequestFilter(message, filter, TAB4);
getSearchRequestAttributes(message, attributesToReturn);
message.append(SEARCHFOOTER).append(STANDARDFOOTER);
return message;
}
private static Pattern escapeGTPattern = Pattern.compile("(>)", Pattern.DOTALL);
private static Pattern escapeLTPattern = Pattern.compile("(<)", Pattern.DOTALL);
private static Pattern escapeAMPPattern = Pattern.compile("(&)", Pattern.DOTALL);
private static Pattern escapeQUOTPattern = Pattern.compile("(")", Pattern.DOTALL);
private static Pattern escapeAPOSPattern = Pattern.compile("(')", Pattern.DOTALL);
private static Pattern escapeOCTOPattern = Pattern.compile("(&#.*?;)", Pattern.DOTALL);
/**
* Translates escaped XML characters as per Extensible Markup Language (XML) 1.0 (Second Edition)
* section 4.6.
*
* @param value a string containing escaped character sequences such as '&' or '&
*
* @return the un-escaped value
*/
// I wonder what the efficiency hit of this is? Hopefully it won't happen very often,
// but it could be quite ugly for large strings. Redo via StringBuffer?
static String unescape(String value)
{
if (value.indexOf('&') == -1) // if we don't need to do anything...
return value; // ... then don't.
value = escapeGTPattern.matcher(value).replaceAll(">");
value = escapeLTPattern.matcher(value).replaceAll("<");
value = escapeQUOTPattern.matcher(value).replaceAll("\"");
value = escapeAPOSPattern.matcher(value).replaceAll("'");
value = escapeAMPPattern.matcher(value).replaceAll("&");
Matcher OCTOMatcher = escapeOCTOPattern.matcher(value);
// cycle through the available entries
while (OCTOMatcher.find())
{
String escapeChar = OCTOMatcher.group(1);
// TODO: this does not correctly cope with multiple escape sequence pairs -
// note that wierd sequences like: &#60; can occur - we only want the last pair
// (& == '&'). Not sure how to cope with this though, since the pattern match
// only grabs the first?
int len = escapeChar.length();
char char1 = 0;
char char2 = 0;
if (len >= 5)
{
char1 = escapeChar.charAt(len - 3);
char2 = escapeChar.charAt(len - 2);
}
if (Character.isDigit(char1) && Character.isDigit(char2))
{
int val = Character.digit(char1, 16) * 16 + Character.digit(char2, 16);
try
{
String replacement = new String(new byte[]{new Integer(val).byteValue()}, "US-ASCII");
value = OCTOMatcher.replaceFirst(replacement);
}
catch (UnsupportedEncodingException e) // should never happen. It's flippin ASCII after all!
{
log.log(Level.SEVERE, "Unexpected exception trying to decode escaped value " + OCTOMatcher.group(1), e);
}
}
else
{
log.info("unable to decode escaped value: " + escapeChar);
break; // give up (including on any subsequent escape sequences)
}
OCTOMatcher = escapeOCTOPattern.matcher(value);
}
return value;
}
/**
* Handling to DSML escape names. Currently this is done in
* exactly the same way as values, but this method will allow
* us to perform any future trickiness that may be required.
*
* @param value
*
* @return the escaped value
*/
static String escapeName(String value)
{
return escape(value);
}
/**
* As per the standard XML rules, we have to escape 'html' like
* characters >, <, ', ", and &.
* @param value
*
* @return the escaped string
*/
static String escape(String value)
{
int len = value.length();
// quickly check if value needs escaping.
boolean needsEscaping = false;
for (int i = 0; i < len; i++)
{
switch (value.charAt(i))
{
case '>':
needsEscaping = true;
break;
case '<':
needsEscaping = true;
break;
case '&':
needsEscaping = true;
break;
case '\"':
needsEscaping = true;
break;
case '\'':
needsEscaping = true;
break;
}
if (needsEscaping)
i = len;
}
if (!needsEscaping)
return value;
// it does need escaping; so start modifying the string...
StringBuffer buffy = new StringBuffer(value);
for (int i = len - 1; i >= 0; i--)
{
switch (value.charAt(i))
{
case '>':
buffy.replace(i, i + 1, ">");
break;
case '<':
buffy.replace(i, i + 1, "<");
break;
case '&':
buffy.replace(i, i + 1, "&");
break;
case '\"':
buffy.replace(i, i + 1, """);
break;
case '\'':
buffy.replace(i, i + 1, "'");
break;
}
}
return buffy.toString();
}
/**
* <p>This constructs the XML tag and elements for a DSML search filter
* <p/>
* <p>e.g.
* <pre>
* >dsml:filter<
* >dsml:and<
* >dsml:substrings name="sn"<
* >dsml:initial<Li>/dsml:initial<
* >/dsml:substrings<
* >dsml:substrings name="cn"<
* >dsml:initial<Cr>/dsml:initial<
* >/dsml:substrings<
* >/dsml:and<
* >/dsml:filter<
* </pre>
*
* @param message an existing string buffer to append the search filter information to
* @param filter an LDAP RFC 2254 filter to translate into verbose DSML woffle
* @param indent whitespace padding to indent the filter for a pleasent and harmonious viewing experience
*
* @return the search request filter
*/
static String getSearchRequestFilter(StringBuffer message, String filter, String indent)
throws NamingException
{
if (filter == null)
throw new NamingException("null filter expression in get SearchRequestFilter");
filter = filter.trim();
if (filter.length() == 0)
throw new NamingException("empty filter expression in get SearchRequestFilter");
log.finest("translating ldap filter '" + filter + "'");
/*
* This is where we 'grow' the dsml version of the ldap filter
*/
//StringBuffer message = new StringBuffer();
/*
* This is formatting fluff - we keep a margin of whitespace to indent the text nicely
*/
StringBuffer padding = new StringBuffer(indent);
/*
* We keep operator DSML elements on a stack, so that we can output the closing tags easily
*/
Stack stack = new Stack();
/*
* This tokenizer breaks up a RFC 2254 ldap filter into its components
*/
StringTokenizer filterElements = new StringTokenizer(filter, "()", true);
message.append(padding).append("<dsml:filter>\n");
while (filterElements.hasMoreTokens())
{
String filterToken = filterElements.nextToken();
try
{
if (filterToken.charAt(0) == '(')
{
filterToken = filterElements.nextToken(); // skip the bracket, and look at what's next. In ldap, this *cannot* be another '(' or ')'.
padding.append(TAB);
message.append(padding);
if (filterToken.length() == 1)
{
switch (filterToken.charAt(0))
{
case '&':
message.append("<dsml:and>\n");
stack.push("</dsml:and>\n");
break;
case '|':
message.append("<dsml:or>\n");
stack.push("</dsml:or>\n");
break;
case '!':
message.append("<dsml:not>\n");
stack.push("</dsml:not>\n");
break;
default:
throw new NamingException("unexpected token '" + filterToken + "' in ldap filter: " + filter);
}
}
else
{
stack.push(""); // add an empty stack item.
translateFilterItem(filterToken, padding, message); // add the search filter item (e.g. 'sn>=fr', 'cn=*' or 'ou=*research*'
padding.setLength(padding.length() - TABLEN);
}
}
else if (filterToken.charAt(0) == ')')
{
String closingElement = (String) stack.pop();
if (closingElement.length() > 0) // ignore empty stack items.
{
message.append(padding).append(closingElement); // add closing tab
padding.setLength(padding.length() - TABLEN);
}
}
else
{
throw new NamingException("unexpected token '" + filterToken + "' in search filter: " + filter);
}
}
catch (EmptyStackException e)
{
throw new NamingException("unexpected end of ldap search filter (empty or non matching brackets?): " + filter);
}
}
message.append(padding).append("</dsml:filter>\n");
return message.toString();
}
static void translateFilterItem(String filterToken, StringBuffer padding, StringBuffer dsmlFilter)
throws NamingException
{
int equalpos = filterToken.indexOf('=');
if (equalpos < 1 || equalpos > filterToken.length() - 2)
throw new NamingException("Unable to parse ldap filter element '" + filterToken + "'");
String attribute = filterToken.substring(0, equalpos); // e.g. 'cn'
String expression = filterToken.substring(equalpos + 1); // e.g. '*fred*'
char endOfAttribute = attribute.charAt(equalpos - 1); // check to see if it was ~=, >= or <=
// see if it is a 'simple' filter item
if (endOfAttribute == '<' || endOfAttribute == '>' || endOfAttribute == '~')
{
attribute = attribute.substring(0, equalpos - 1); // trim attribute name of the ending </>/~ character
String value = new StringBuffer(100).append(" name=\"").append(attribute).append("\">").append(padding).append(TAB).append("<dsml:value>").append(escape(expression)).append("</dsml:value>").append(padding).toString();
switch (endOfAttribute)
{
case '<':
dsmlFilter.append("<dsml:lessOrEqual").append(value).append("</dsml:lessOrEqual>\n");
break;
case '>':
dsmlFilter.append("<dsml:greaterOrEqual").append(value).append("</dsml:greaterOrEqual>\n");
break;
case '~':
dsmlFilter.append("<dsml:approxMatch").append(value).append("</dsml:approxMatch>\n");
break;
}
}
// see if it is a 'simple' equality match filter item
else if (expression.indexOf('*') == -1)
{
dsmlFilter.append("<dsml:equalityMatch name=\"").append(attribute).append("\">").append(padding).append(" ").append("<dsml:value>").append(escape(expression)).append("</dsml:value>").append(padding).append("</dsml:equalityMatch>\n");
}
// see if it is a 'present' filter item
else if (expression.equals("*"))
{
dsmlFilter.append("<dsml:present name=\"").append(attribute).append("\"/>\n");
}
// yucky hard cases.
else // the expression contains at least one '*', and is longer than 1 character.
{
dsmlFilter.append("<dsml:substrings name=\"").append(attribute).append("\">\n");
padding.append(TAB);
// prepare to parse expressions like 'Fr*n*k*stein"
StringTokenizer substringFilter = new StringTokenizer(expression, "*", true);
String initial = substringFilter.nextToken();
if (initial.equals("*") == false)
dsmlFilter.append(padding).append("<dsml:initial>").append(escape(initial)).append("</dsml:initial>\n"); // were they drunk when they designed this verbose crud?
while (substringFilter.hasMoreTokens())
{
String filterElement = substringFilter.nextToken();
if (filterElement.equals("*") == false) // skip over the stars
{
if (substringFilter.hasMoreTokens() == false) // then this is the last one!
dsmlFilter.append(padding).append("<dsml:final>").append(escape(filterElement)).append("</dsml:final>\n");
else
dsmlFilter.append(padding).append("<dsml:any>").append(escape(filterElement)).append("</dsml:any>\n");
}
}
padding.setLength(padding.length() - TABLEN);
dsmlFilter.append(padding).append("</dsml:substrings>\n");
}
// LDAP v3 Extensions not supported - your code here...
}
}