package com.idega.core.ldap.client.naming;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import com.idega.core.ldap.client.cbutil.CBBase64;
import com.idega.core.ldap.client.cbutil.CBUtility;
import com.idega.core.ldap.client.jndi.SchemaOps;
/**
* This is largely a schema-aware wrapper to BasicAttribute, with some
* utility classes spliced on. <p>
*/
public class DXAttribute extends BasicAttribute
{
boolean binary = false; // whether it's binary (in this context, 'non-string') data
static boolean verboseBinary = false; // whether to add ';binary' to the end of binary att names.
String name; // the name of the attribute (usually identical to the ID)
String syntaxOID; // the OID of the Syntax (i.e. "1.3.6.1.4.1.1466.115.121.1.5" for binary)
String syntaxDesc; // the same thing as a human readable description
String description; // the free form description of the attribute that may exist in schema
static Hashtable knownBinaryAttributes; // a list of known 'binary' attributes.
static SchemaOps schema; // the schema of the most recently connected to directory
private final static Logger log = Logger.getLogger("com.idega.core.ldap.client.naming.DXAttribute"); // ...It's round it's heavy it's wood... It's better than bad, it's good...
static
{
knownBinaryAttributes = new Hashtable(100);
}
/**
* Normal constructor for an Attribute with no (current) value.
*/
public DXAttribute(String ID)
{
super(ID);
setBinaryness();
}
/**
* Normal constructor an Attribute with a single value.
*/
public DXAttribute(String ID, Object value)
{
super(ID, value);
setBinaryness();
}
/**
* Make a copy of a normal, pre-existing attribute.
* @param att the attribute (e.g. BasicAttribute) to wrap.
*/
public DXAttribute(Attribute att)
{
super (att.getID());
try
{
addValues(att.getAll());
}
catch (NamingException e)
{
CBUtility.log("error reading attribute values for attribute " + getID() + "\n" + e.toString());
}
setBinaryness();
setName(att.getID()); // ?? necessary??
}
public DXAttribute(String ID, NamingEnumeration values)
{
super(ID);
addValues(values);
setBinaryness();
}
public void addValues(NamingEnumeration values)
{
try
{
while (values.hasMore())
{
add(values.next());
}
}
catch (NamingException e)
{
CBUtility.log("error adding values for attribute " + getID() + "\n" + e.toString());
}
}
/**
* This sets the standard schema to use while this connection is open.
* (It may be possible in future releases to set schema on a per-Attribute
* basis - it is not clear yet whether this would be useful.)
*/
public static void setDefaultSchema(SchemaOps defaultSchema)
{
schema = defaultSchema;
}
/**
* This method examines the schema to test whether the attribute is 'binary' data.
* In this context, 'binary' simply means 'not string'; i.e. photo, certificate,
* octet etc.
*/
public void setBinaryness()
{
// quickly handle schema atts.
String ID = getID();
if ("SYNTAXNAMENUMERICOIDDESC".indexOf(ID) != -1) {
return;
}
ID = ID.toLowerCase();
if (knownBinaryAttributes.get(ID) != null) // see if we already know this attribute ID...
{
this.binary = (knownBinaryAttributes.get(ID).equals(Boolean.TRUE));
}
if (ID.endsWith(";binary")) {
this.binary = true;
}
if (this.binary == false)
{
this.binary = checkIsBinaryInSchema();
}
if (this.binary == false && schema==null) // backup checks for if no schema or bad schema
{
Object value = null;
try
{
value = get();
this.binary = !(value==null || value instanceof String);
}
catch (Exception e) {}
}
knownBinaryAttributes.put(ID, new Boolean(this.binary));
}
/**
* Does this attribute have a valid value (i.e. not null, or empty string).
*/
public static boolean isEmpty(Attribute att)
{
if (att == null) {
return true;
}
if (att.size()==0) {
return true;
}
if (att.size()==1)
{
Object val = null;
try
{
val = att.get();
}
catch (NamingException e)
{
return true; // assume naming exception means empty attribute... (?)
}
if (val == null || "".equals(val)) {
return true;
}
}
return false;
}
/**
* Used to register the schema with a particular attribute - currently the schema is
* then looked up for Description,name,syntax description; this may be
* inefficient; maybe these should only be looked up when necessary.<p>
* In the event that no particular schema is registered for this attribute, the
* default schema is used.
*/
/*
public void registerSchema(DirContext s)
{
schema = s;
if (schema == null) schema = defaultSchema; // use the backup schema instead
if (defaultSchema == null) return; // can't do anything.
getSyntaxDesc(); // sets syntaxDesc
getSyntaxOID(); // sets syntaxOID & checks binaryness
getName(); // sets name
getDescription(); // sets description
getOptions();
}
*/
/**
* Utility method - returns the schema context in use by this attribute.
*/
//public DirContext getSchema() { return schema; }
/**
* implements the interface spec.
*/
/*
public DirContext getAttributeDefinition()
{
if (schema == null) return null;
try
{
return (DirContext) schema.lookup("AttributeDefinition/" + getID());
}
catch (NamingException e) {return null;}
}
*/
public boolean checkKnownBinaryAttributes(String ID)
{
//System.out.println("cache caught: " + ID + " = " + Boolean.TRUE.equals(knownBinaryAttributes.get(ID)));
//CB this is wierd. was there a reason? return (Boolean.TRUE.equals(knownBinaryAttributes.get(ID)));
return knownBinaryAttributes.contains(ID);
}
public static boolean isBinarySyntax(String syntaxName)
{
if (syntaxName == null) {
return false;
}
int pos = syntaxName.indexOf("1.3.6.1.4.1.1466.115.121.1.");
if (pos == -1) {
return false;
}
String number = syntaxName.substring(pos + "1.3.6.1.4.1.1466.115.121.1.".length());
if (number.length() > 2)
{
number = number.substring(0, 2);
char c = number.charAt(1);
if (Character.isDigit(c) == false) {
number = number.substring(0, 1);
}
}
try
{
int finalNumber = Integer.parseInt(number);
switch (finalNumber)
{
case 4: return true; // 1.3.6.1.4.1.1466.115.121.1.4 - audio
case 5: return true; // 1.3.6.1.4.1.1466.115.121.1.5 - binary
case 8: return true; // 1.3.6.1.4.1.1466.115.121.1.8 - certificate
case 9: return true; // 1.3.6.1.4.1.1466.115.121.1.9 - certificate list
case 10: return true; // 1.3.6.1.4.1.1466.115.121.1.10 - certificate pair
case 28: return true; // 1.3.6.1.4.1.1466.115.121.1.28 - jpeg
case 40: return true; // 1.3.6.1.4.1.1466.115.121.1.40 - octet string
default: return false;
}
}
catch (NumberFormatException e)
{
CBUtility.log("Unexpected error parsing syntax: " + syntaxName + "\n " + e);
return false;
}
}
public boolean checkIsBinaryInSchema()
{
if (schema != null)
{
try
{
//System.out.println("\n" + getID() + " starting to test! ");
Attributes atts = schema.getAttributes("AttributeDefinition/" + getID());
Attribute a;
while ((a = atts.get("SUP")) != null)
{
atts = schema.getAttributes("AttributeDefinition/" + a.get().toString());
}
a = atts.get("SYNTAX");
if (a != null)
{
String syntaxName = a.toString();
if (isBinarySyntax(syntaxName)) {
return true;
}
}
else
{
CBUtility.log("%$%$%$%$%$%$%$%$ Can't find SYNTAX for... " + getID());
}
}
catch (Exception e)
{
try
{
Attributes atts = schema.getAttributes("AttributeDefinition/" + getID() + ";binary");
//XXX check for bad schema
String syntaxName = atts.get("SYNTAX").toString();
// XXX check for string length less than 1.3....5
if (isBinarySyntax(syntaxName)) {
return true;
}
}
catch (Exception e2) // give up
{
//CBUtility.log("error checking for binary attribute " + getID() + "\n" + e.toString());
}
}
}
else
{
CBUtility.log("no schema available", 4);
}
//System.out.println(getID() + " not binary...");
return false;
}
/**
* implements the interface spec.
*/
/*
public DirContext getAttributeSyntaxDefinition()
{
//return schema.getAttributeSyntax(getID());
if (schema == null) return null;
try
{
DirContext attDef = (DirContext) schema.lookup("AttributeDefinition/" + getID());
String syntaxName = attDef.lookup("SYNTAX").toString();
if (isBinarySyntax(syntaxName))
binary=true;
return (DirContext) schema.lookup("SyntaxDefinition/" + syntaxName);
}
catch (NamingException e) {return null;}
}
*/
/**
* Whether the attribute contains binary data; ideally found by checking Syntax, but
* often set by inspection of the attribute value (whether it is a Byte array).
*/
public boolean isBinary() { return this.binary; }
/**
* Sets the binary status of the attribute. Shouldn't be required any more;
* should be set by syntax checking.
*/
public void setBinary(boolean bin)
{
knownBinaryAttributes.put(getID(), new Boolean(bin));
this.binary = bin;
}
/**
* Utility Function: takes a schema Entry class such as 'AttributeDefinition/cn', *
* and returns the result of looking up a particular attribute within that
* (e.g. 'DESC').
* @param schemaEntry the full schema entry name to lookup, e.g. 'ClassDefinition/alias'
* @param schemaAttribute the attribute to look up, e.g. 'MUST'
* @return the value of the schema entry attribute (e.g. '2.5.4.1')
*/
public String schemaLookup(String schemaEntry, String schemaAttribute)
{
if (schema == null) {
return null;
}
else {
return schema.schemaLookup(schemaEntry, schemaAttribute);
/*
String value = null;
try
{
Attributes theWorks = schema.getAttributes(schemaEntry);
DXAttributes temp = new DXAttributes(theWorks);
Attributes searchResult = schema.getAttributes(schemaEntry, new String[] {schemaAttribute.toLowerCase()});
NamingEnumeration searchResults = searchResult.getAll();
//probably only a single parent attribute...
while (searchResults.hasMore()) //XXX redundant - should only be one.
{
Attribute result = (Attribute) searchResults.next();
NamingEnumeration values = result.getAll(); // XXX redundant - only the first value is returned.
while (values.hasMore())
{
value = (String)values.next();
}
}
}
catch (NamingException e)
{
return null;
}
return value;
*/
}
}
public boolean hasOptions()
{
if (this.description==null) {
getDescription();
}
if (this.description==null || this.description == "" || this.description.indexOf("LIST:")<0) {
return false;
}
return true;
}
/**
* IF a description field has been set in the schema, and IF that
* description field LISTs a set of values, read 'em and return them
* as a string array.
*/
public String[] getOptions()
{
if (this.description==null) {
getDescription();
}
if (this.description==null || this.description == "" ) {
return new String[0];
}
// can't be fagged working out java generic parse stuff - do own quickie...
int len = this.description.length();
int pos = this.description.indexOf("LIST:");
if (pos < 0) {
return new String[0];
}
pos += 5;
int next = pos;
Vector resVect = new Vector();
resVect.add("");
// String option;
while (pos < len && pos > 0)
{
next = this.description.indexOf(',', next+1);
if (next < 0)
{
resVect.add(this.description.substring(pos));
pos = 0;
}
else if (this.description.charAt(next-1)!= '\\')
{
resVect.add(this.description.substring(pos, next));
pos = next+1;
}
else
{
next++; // move past the escaped comma
}
}
// dump vector into string array, unescaping as we go.
String[] result = new String[resVect.size()];
for (int i=0; i<resVect.size(); i++) {
result[i] = unEscape((String)resVect.elementAt(i));
}
return result;
}
public String unEscape(String escapeMe)
{
int slashpos = escapeMe.indexOf('\\');
while (slashpos>=0)
{
escapeMe = escapeMe.substring(0,slashpos) + escapeMe.substring(slashpos+1);
slashpos = escapeMe.indexOf('\\');
}
return escapeMe;
}
public String getSyntaxOID()
{
if (this.syntaxOID == null)
{
this.syntaxOID = schemaLookup("AttributeDefinition/" + getID(), "SYNTAX");
if (isBinarySyntax(this.syntaxOID)) {
this.binary = true;
}
}
return this.syntaxOID;
}
/**
* Usefull escape to allow renaming of attributes. Use with caution,
* since an arbitrary name may not correspond to a valid schema name.
*/
public void setName(String newName)
{
this.name = newName;
}
public String getName()
{
if (this.name == null) {
this.name = schemaLookup("AttributeDefinition/" + getID(), "NAME");
}
if (this.name == null) {
this.name = getID();
}
return this.name;
}
/**
* Returns (and caches) the syntax description.
*/
public String getSyntaxDesc()
{
if (this.syntaxDesc == null) {
this.syntaxDesc = schemaLookup("SyntaxDefinition/" + getSyntaxOID(), "DESC");
}
return this.syntaxDesc;
}
/**
* Returns the attribute's 'DESC' field from the attribute schema
* definition, if such exists (it's an extension). Not to be
* confused with the Syntax Description, which is something like "binary".
*/
public String getDescription()
{
if (this.description == null)
{
this.description = schemaLookup("AttributeDefinition/" + getID(), "DESC");
if (this.description == null) {
this.description = ""; // to an empty string, to avoid repeatedly looking it up.
}
}
return this.description;
}
/**
* General descriptive string: used mainly for debugging...
*
*/
public String toString()
{
int count = 1;
try
{
String result = "att: " + getID() + " (size=" + size() + ") ";
NamingEnumeration vals = getAll();
if (this.binary)
{
result = result + " (binary) ";
while (vals.hasMore())
{
try
{
byte[] b = (byte[])vals.next();
result = result + "\n " + (count++) + ":" + ((b==null)?"null":CBBase64.binaryToString(b));
}
catch (ClassCastException cce)
{
result = result + "\n " + (count++) + ": <error - not a byte array>";
}
}
}
else
{
while (vals.hasMore())
{
Object o = vals.next();
result = result + "\n " + (count++) + ":" + ((o==null)?"null":o.toString());
}
}
return result + "\n";
}
catch (NamingException e)
{
CBUtility.log("error listing values for " + getID() + " : " + e.toString());
return (getID() + " (error listing values)");
}
}
// ugly, ugly hack to add ';binary' when writting data to dir, but
// not otherwise.
public static void setVerboseBinary(boolean status)
{
verboseBinary = status;
log.log(Level.FINER,"setting binary attribute status to " + status);
}
public String getID()
{
String id = super.getID();
if (verboseBinary && !(id.endsWith(";binary")))
{
//System.out.println("binaryness = " + binary);
if (this.binary) {
id = id + ";binary";
}
}
log.log(Level.FINER,"returning binary id " + id);
return id;
}
/**
* This method gets all the values of an attribute. Useful only
* for multivalued attributes (use get() otherwise).
* @return all the values of this attribute in a String Array.
* XXX - assumes all values are strings??
*/
public Object[] getValues()
{
Object[] values = new String[size()];
try
{
for(int i=0; i<size(); i++) {
values[i] = get(i);
}
return values;
}
catch(NamingException e)
{
return new String[]{};
}
}
/**
* Gets rid of null values and empty strings from the value set.
*/
public void trim()
{
for (int i=size()-1; i>0; i--)
{
Object o = null;
try
{
o = get(i);
if (o==null || "".equals(o))
{
remove(i);
}
}
catch (NamingException e) // shouldn't happen...
{
CBUtility.log("Bad Attribute value in DXAttribute - removing ");
remove(i); // .. but remove offending entry if it does.
}
}
}
public void setOrdered(boolean state) { this.ordered = state; }
/**
* Returns true if this attribute is a SINGLE-VALUE attribute.
* @return true if this attribute is a SINGLE-VALUE attribute,
* false otherwise.
*/
public boolean isSingleValued()
{/* TE */
return schema.isAttributeSingleValued(getName());
}
/**
* Ye olde equals method - so we can use these thingumies in Hashtables.
*/
/*
public boolean equals(Object obj)
{
System.out.println("testing equality for:\n" + toString() + "\n vs: \n" + obj.toString());
boolean ret = super.equals(obj);
System.out.println(" = " + ret);
return ret;
}
*/
/*
if ((obj instanceof DXAttribute) == false) return false;
DXAttribute test = (DXAttribute)obj;
if (getID().equals(test.getID()) == false) return false;
if (size() != test.size()) return false;
for (int i=0; i<size(); i++)
{
try
{
if (get(i).equals(test.get(i)) == false) return false;
}
catch (NamingException e)
{
return false;
}
}
return true;
}
public int hashCode()
{
}
*/
/**
* A wrapper to BasicAttributes.get() that catches NoSuchElementException
* and gets it to return 'null' instead. (Unclear if this is strictly
* correct, however JXplorer commonly equates a 'null' value to 'no value'.)
*/
/*
public Object get() throws NamingException
{
try
{
return super.get();
}
catch (NoSuchElementException e)
{
return null;
}
}
*/
/*
* *** Attribute Interface Methods ***
*/
/*
public void add(int ix, Object attrVal) { values.add(ix, attrVal); }
public boolean add(Object attrVal) { return values.add(attrVal); }
public void clear() { values.clear(); }
public Object clone() { return new DXAttribute(this); }
public boolean contains(Object attrVal) { return values.contains(attrVal); }
public Object get() { return values.elementAt(0); }
public Object get(int ix) { return values.elementAt(ix); }
public NamingEnumeration getAll() { return new DXNamingEnumeration(values.elements()); }
// defined above DirContext getAttributeDefinition()
// defined above DirContext getAttributeSyntaxDefinition()
public String getID() { return id; }
public boolean isOrdered() { return false; }
public Object remove(int ix) { return values.remove(ix); }
public boolean remove(Object attrval) { return values.remove(attrval); }
public Object set(int ix, Object attrVal) { return values.set(ix, attrVal); }
public int size() { return values.size(); }
*/
}