package com.idega.core.ldap.client.naming;
import javax.naming.CompositeName;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.idega.core.ldap.client.cbutil.CBArray;
import com.idega.core.ldap.client.cbutil.CBIntText;
import com.idega.core.ldap.client.cbutil.CBUtility;
import com.idega.core.ldap.client.jndi.AdvancedOps;
/**
* A wrapper for BasicOps that converts the jndi primative names into
* com.ca.commons.naming objects...
*/
public class DXOps extends AdvancedOps {
private final static Logger log = Logger
.getLogger("com.idega.core.ldap.client.naming.DXOps");
/**
* Initialise with the directory context.
*/
public DXOps(DirContext ctx) {
super(ctx);
}
/**
* This preparses a name, preparitory to passing to the jndi operation.
* Usefull to over-ride if a Name needs to be escaped or re-formatted.
*
* @param name
* the pre jndi operation name.
* @return the version used by the operation.
*/
public Name preParse(Name name) {
// (assuming jndi doesn't mess with the names it's given, we don't need
// this...)
// DN newName = (name instanceof DN)?new DN((DN)name):new DN(name);
return name;
}
/**
* This postparses a name, after it has been returned from the jndi
* operation. Usefull to over-ride if the name needs to be unescaped or
* reformatted.
*
* @param name
* the post jndi operation name.
* @return the re-formatted version used by the application.
*/
public Name postParse(Name name) {
return postParse(name.toString());
}
/**
* This postparses a name, after it has been returned from the jndi
* operation. It assumes that it has got a jndi <i>CompositeName</i> that
* needs to be converted to a legal ldap dn (i.e. an ldap
* <i>CompoundName</i>). If this is *not* the case, there will be trouble...
*
* @param name
* the post jndi operation name.
* @return the re-formatted version used by the application, as a DN object.
*/
public Name postParse(String name) {
/*
* EMERGENCY HACK (JNDI apparently does not handle terminating spaces
* correctly - it retains the escape characters, but trims the actual
* space, resulting in an illegal ldap dn)
*/
if (name.charAt(name.length() - 1) == '\\') {
name = NameUtility.checkEndSpaces(name);
}
try {
Name cn = new CompositeName(name);
if (cn.size() == 0) {
return new DN(); // ... just return an empty DN
}
return new DN(cn.get(cn.size() - 1)); // get the last element of the
// composite name, which
// will be the ldap compound
// name, and init the DN
// with that.
} catch (NamingException e) // should never happen :-) (ROTFL)
{
log.log(Level.WARNING,
"unexpected error: bad name back from jndi ftn in CBOps.postParse("
+ name + ")?\n" + e.toString());
e.printStackTrace();
// System.exit(-1);
return new DN(name); // bad server response? return (possibly)
// corrupt name anyway...
}
}
/**
* This postparses a name, after it has been returned from the jndi
* operation. It assumes that it has got a jndi <i>CompositeName</i> that
* needs to be converted to a legal ldap dn (i.e. an ldap
* <i>CompoundName</i>). If this is *not* the case, there will be trouble...
*
* @param name
* the post jndi operation name.
* @return the re-formatted version used by the application as an ldap
* String.
*/
public String postParseString(String name) {
/*
* EMERGENCY HACK (JNDI apparently does not handle terminating spaces
* correctly - it retains the escape characters, but trims the actual
* space, resulting in an illegal ldap dn)
*/
if (name.length() == 0) {
return name;
}
if (name.charAt(name.length() - 1) == '\\') {
name = NameUtility.checkEndSpaces(name);
}
try {
Name cn = new CompositeName(name);
if (cn.size() == 0) {
return ""; // ... just return an empty DN
}
return cn.get(cn.size() - 1); // get the last element of the
// composite name, which will be the
// ldap compound name, and init the
// DN with that.
} catch (NamingException e) // should never happen :-) (ROTFL)
{
log.log(Level.WARNING,
"unexpected error: bad name back from jndi ftn in CBOps.postParseString("
+ name + ")?\n" + e.toString());
e.printStackTrace();
return name; // bad server response? return (possibly) corrupt name
// anyway...
}
}
/**
* This postparses a namingEnumeration of NameClassPairs, after it has been
* returned from the jndi operation. Usefull to over-ride if the names in
* the enumeration need to be unescaped or reformatted.
*
* @param e
* the post jndi operation namingEnumeration.
* @param base
* the 'base' dn from which the names in the enumeration (may) be
* relative. If the Names in the enumeration are suffixed by the
* searchBase, they are unaltered, otherwise the searchBase is
* added to the names to give the full DN in the namespace.
* @return the re-formatted version used by the application.
*/
public NamingEnumeration postParseNameClassPairs(NamingEnumeration e,
Name base) throws NamingException {
log.log(Level.FINER, "parsing with base :" + base.toString());
DXNamingEnumeration dxe = new DXNamingEnumeration();
String baseString = null;
if (base != null && base.isEmpty() == false) {
baseString = base.toString();
}
try {
while (e.hasMore()) {
NameClassPair ncp = (NameClassPair) e.next();
String rawName = postParseString(ncp.getName()).toString();
// IMPORTANT!
// This appends the 'base' DN to the enumerated DNs in order to
// get absolute DNs...
if (ncp.isRelative() && baseString != null) {
if (rawName.length() != 0) {
rawName = rawName + "," + baseString;
} else {
rawName = baseString;
}
}
log.log(Level.FINER, "ended up with: '" + rawName + "'");
ncp.setName(rawName);
dxe.add(ncp);
}
} catch (NamingException ex) {
CBUtility.error(CBIntText.get("Search partially failed! - only ")
+ dxe.size() + CBIntText.get(" entries returned."), ex);
}
return dxe;
}
/**
* Update an entry with the designated DN.
*
* @param oldEntry
* the old entry containing the old set of attributes.
* @param newEntry
* the new entry containing the replacement set of attributes.
* @return the operation's success status.
*/
public boolean modifyEntry(DXEntry oldEntry, DXEntry newEntry) {
if (oldEntry == null && newEntry == null) {
return true; // nothing to do.
}
if (oldEntry != null) {
oldEntry.removeEmptyAttributes();
}
if (newEntry != null) {
newEntry.removeEmptyAttributes();
}
if (oldEntry == null || (newEntry != null)
&& (newEntry.getStatus() == DXEntry.NEW)) // add
{
return addEntryToDirectory(newEntry);
} else if (newEntry == null) // delete
{
deleteTree(oldEntry.getDN());
return true;
}
// sanity check
if (oldEntry.getDN() == null || newEntry.getDN() == null) {
CBUtility
.log("Internal Error: Entry with null DN passed to JNDIBroker unthreadedModify! Modify Request Cancelled!");
return false;
}
// see if the name has changed, and modify it if it has
if (handleAnyNameChange(oldEntry, newEntry) == false) {
return false;
}
// check for change of attributes done in modify()
return updateEntry(oldEntry, newEntry);
}
/**
* Add the new entry to the directory & sets the status.
*
* @param newEntry
* the new entry containing the replacement set of attributes.
*/
private boolean addEntryToDirectory(DXEntry newEntry) {
if (addEntry(newEntry)) {
newEntry.setStatus(DXEntry.NEW_WRITTEN); // once it's been added,
// it's no longer new...
return true;
} else {
return false;
}
}
/**
* Add the new entry to the directory.
*
* @param newEntry
* the new entry containing the replacement set of attributes.
* @return the operation's success status
*/
public boolean addEntry(DXEntry newEntry) {
if (newEntry == null) {
CBUtility
.log("Internal Error: null Entry passed to DXOps addEntry");
return false;
} else if (newEntry.getDN() == null) {
CBUtility
.log("Internal Error: Entry with null DN passed to DXOps addEntry");
return false;
}
return addObject(newEntry.getDN(), newEntry);
}
/**
* If the entry has changed its name, make the required calls to set up the
* display tree and make the directory changes.
*
* @param oldEntry
* the old entry containing teh old set of attributes.
* @param newEntry
* the new entry containing the replacement set of attributes.
*/
private boolean handleAnyNameChange(DXEntry oldEntry, DXEntry newEntry) {
// check for 'simple' rename from the tree, with no attributes involved.
RDN oldRDN = oldEntry.getRDN();
RDN newRDN = newEntry.getRDN();
DN oldDN = oldEntry.getDN();
DN newDN = newEntry.getDN();
if (oldDN.equals(newDN)) {
return true; // nothing to see here, just move along.
}
if (oldEntry.size() == 0 && newEntry.size() == 0) // a very simple
// rename, probably
// from the tree
{
return moveTree(oldDN, newDN);
} else if (oldRDN.isMultiValued() == false
&& newRDN.isMultiValued() == false) {
return renameSingleValuedRDNS(oldEntry, newEntry);
} else {
return renameComplexMultiValuedRDNs(oldEntry, newEntry);
}
}
/**
* do complex multi-valued RDN rename WARNING - this assumes that the size
* and the values of the RDNs will not BOTH CHANGE AT THE SAME TIME!
*/
private boolean renameComplexMultiValuedRDNs(DXEntry oldEntry,
DXEntry newEntry) {
String[] oldRDNs = oldEntry.getRDN().getElements();
String[] newRDNs = newEntry.getRDN().getElements();
DN oldDN = oldEntry.getDN();
DN newDN = newEntry.getDN();
/*
* Create a list of 'lost RDNs' -> one's that are in the old RDN set but
* not in the new one.
*/
Object[] temp = CBArray.difference(oldRDNs, newRDNs);
String[] lostRDNs = new String[temp.length];
for (int i = 0; i < temp.length; i++) {
lostRDNs[i] = temp[i].toString();
}
/*
* Cycle through the list of 'lost' RDNs, working out whether they are
* *all* missing from the new Entry (in which case we can do a rename
* with 'deleteOldRDNs=true'), or they are *all* in the new Entry (in
* which case we can do a rename with 'deleteOldRDNs=false'). If some
* are and some aren't, throw a hopefully useful error message.
*/
final int NOT_SET = 0;
final int IN_NEW_ENTRY = 1;
final int NOT_IN_NEW_ENTRY = -1;
int deleteRDNState = NOT_SET;
for (int i = 0; i < lostRDNs.length; i++) {
String RDN = lostRDNs[i]; // get Attribute Value Assertions from
// lostRDNs
String type = RDN.substring(0, RDN.indexOf('='));
String value = RDN.substring(RDN.indexOf('=') + 1);
if (newEntry.get(type).contains(value) == true) {
if (deleteRDNState == NOT_SET) {
deleteRDNState = IN_NEW_ENTRY;
}
if (deleteRDNState != IN_NEW_ENTRY) {
return setWierdoRDNError(oldDN, newDN);
}
} else {
if (deleteRDNState == NOT_SET) {
deleteRDNState = NOT_IN_NEW_ENTRY;
}
if (deleteRDNState != NOT_IN_NEW_ENTRY) {
return setWierdoRDNError(oldDN, newDN);
}
}
}
/*
* perform the actual rename operation, followed by any entry tweaking
* that is required to make everything consistant.
*/
if (deleteRDNState == NOT_SET || deleteRDNState == IN_NEW_ENTRY) {
return renameObject(oldDN, newDN, false);
} else {
if (renameObject(oldDN, newDN, true) == true) {
for (int i = 0; i < lostRDNs.length; i++) {
String RDN = lostRDNs[i]; // get Attribute Value Assertions
// from lostRDNs
String type = RDN.substring(0, RDN.indexOf('='));
String value = RDN.substring(RDN.indexOf('=') + 1);
oldEntry.get(type).remove(value); // remove old value so it
// doesn't get double
// deleted...
}
return true;
} else {
return false;
}
}
}
private boolean renameSingleValuedRDNS(DXEntry oldEntry, DXEntry newEntry) {
RDN oldRDN = oldEntry.getRDN();
String type = oldRDN.getAtt();
String value = oldRDN.getRawVal();
Attribute oldNamingAttInNewEntry = newEntry.get(type);
// if the old naming value does not exist in the new entry, drop it!
if (!oldNamingAttInNewEntry.contains(value)) {
if (renameObject(oldEntry.getDN(), newEntry.getDN(), true) == true) {
oldEntry.get(type).remove(value); // remove old value so it
// doesn't get double
// deleted...
return true;
} else {
return false;
}
}
// if it *does* exist in the new entry, keep it.
else {
return renameObject(oldEntry.getDN(), newEntry.getDN(), false);
}
}
/**
* This sets the error state when the user has attempted a fiendishly
* complex and stoopid request that requires a mixture of
* 'deleteOldRDN=false' and 'deleteOldRDN=true'. Since JX does not handle
* indeterminate quantum states, we throw an error instead.
*
* @param oldDN
* @param newDN
* @return
*/
private boolean setWierdoRDNError(DN oldDN, DN newDN) {
String msg = CBIntText
.get("The rename operation is too complex to proceed. Try to break it up into smaller stages.")
+ "\n " + oldDN.toString() + "\n => " + newDN.toString();
return error(msg, new NamingException(msg));
}
/*
* See if the name has changed, and make required mods if it has.
*/
// XXX - note case sensitive string compare to allow user to change
// capitalization...
// TE: XXX can't change case in RDNs???? IF delete RDN = false changing case
// will fail b/c in the
// TE: XXX dir the cn's are case insensitive so renaming from cn=A to cn=a
// is asking the dir to
// TE: XXX have two cn's in the entry that are the same because the old cn
// will not be deleted i.e the
// TE: XXX can't rename to an existing entry...
/*
* if (oldEntry.getDN().equals(newEntry.getDN()) == false) { //Do the
* rename!
*
* moveTree(oldEntry.getDN(), newEntry.getDN()); } }
*/
/**
* Update an entry with the designated DN.
*
* @param oldSet
* the old entry containing teh old set of attributes.
* @param newSet
* the new entry containing the replacement set of attributes.
* @return the operation's success status.
*/
public boolean updateEntry(DXEntry oldSet, DXEntry newSet) {
try {
if (DXAttributes.attributesEqual(oldSet, newSet)) {
return true; // nothing to do.
}
DN nodeDN = newSet.getDN();
RDN newRDN = nodeDN.getLowestRDN();
DXAttributes adds = null; // use modify-add for new attribute values
// (inc entirely new attribute)
DXAttributes reps = null; // use modify-replace for changed
// attribute values
DXAttributes dels = null; // use modify-delete for deleted attribute
// values (inc deleting entire
// attribute)
reps = DXAttributes.getReplacementSet(newRDN, oldSet, newSet);
dels = DXAttributes.getDeletionSet(newRDN, oldSet, newSet);
adds = DXAttributes.getAdditionSet(newRDN, oldSet, newSet);
CBUtility.log("updateNode: ", 4);
ModificationItem[] mods;
mods = new ModificationItem[dels.size() + reps.size() + adds.size()];
int modIndex = 0;
modIndex = loadMods(mods, dels.getAll(),
DirContext.REMOVE_ATTRIBUTE, modIndex);
modIndex = loadMods(mods, adds.getAll(), DirContext.ADD_ATTRIBUTE,
modIndex);
modIndex = loadMods(mods, reps.getAll(),
DirContext.REPLACE_ATTRIBUTE, modIndex);
return modifyAttributes(nodeDN, mods); // TE: This may fail,
// returning false.
} catch (NamingException e) {
error("Unable to update node " + oldSet.getDN() + "! "
+ e.toString(), e);
e.printStackTrace();
return false;
} catch (Exception e) {
error("Unexpected Error updating node " + oldSet.getDN() + "! "
+ e.toString(), e);
e.printStackTrace();
return false;
}
}
/**
* Utility ftn for updateNode - takes a list of attributes to modify, and
* the type of modification, and adds them to an array of modifications
* (starting at a particular index).
*
* @param mods
* the array of modification items
* @param atts
* an enumeration of attributes to add to the mod array
* @param TYPE
* the type of modification (DELETE,REPLACE,ADD)
* @param index
* the position in the modification array to start filling
* entries in
* @return return the final index position reached.
*/
private int loadMods(ModificationItem[] mods, NamingEnumeration atts,
int TYPE, int index) throws NamingException {
while (atts.hasMore()) {
Attribute temp = (Attribute) atts.next();
mods[index++] = new ModificationItem(TYPE, temp);
}
return index;
}
/**
* Optional debug code. Very useful. NEVER REMOVE!!!
*
* @param oldSet
* old entry.
* @param newSet
* new entry.
* @param adds
* list of attributes to add.
* @param reps
* list of attributes to replace.
* @param dels
* list of attributes to delete.
*/
private void printDebug(DXEntry oldSet, DXEntry newSet, DXAttributes adds,
DXAttributes reps, DXAttributes dels) {
System.out.println("\n*** entries are ***\n\nold:\n"
+ oldSet.toString() + "\n\nnew:\n" + newSet.toString());
System.out.println("\n-----------------\nreps:\n" + reps.toString());
System.out.println("\n-----------------\ndels:\n" + dels.toString());
System.out.println("\n-----------------\nadds:\n" + adds.toString());
// Thread.currentThread().dumpStack();
}
}