package com.idega.core.ldap.client.jndi; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.Name; import javax.naming.NameClassPair; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.DirContext; /** * The AdvancedOps class extends BasicOps to allow for complex directory * operations such as manipulating entire trees. <p> * * It requires initialisation with a Directory Context, through which * all the low level directory calls are passed (basicOps is a wrapper * to jndi). * * It contains a number of functions (pop(), push() and inc() ) * that may be over-ridden by * classes derived from this that with to track progress. * */ public class AdvancedOps extends BasicOps { // protected BasicOps dirOp; protected NameParser parser; private final static Logger log = Logger.getLogger("com.idega.core.ldap.client.jndi.BasicOps"); /** * Initialise the AdvancedOps object with a BasicOps object. <p> * Warning: the basic ops object is used to obtain a Name Parser * for the current context, which is asumed to be homogenous: * AdvancedOps does *not* support tree operations across multiple * name spaces. */ public AdvancedOps(DirContext c) { super(c); this.parser = getBaseNameParser(); } /** * Factory Method to create BasicOps objects, initialised * with an ldap context created from the connectionData, * and maintaining a reference to that connectionData. * * @param cData the details of the directory to connect to * @return an AdvancedOps object (although it must be cast to * this from the BasicOps required by the method sig - is there * a better way of doing this?). */ public static BasicOps getInstance(ConnectionData cData) throws NamingException { AdvancedOps newObject = new AdvancedOps(openContext(cData)); newObject.setConnectionData(cData); return newObject; } /** * overload this method for progress tracker. */ public void open(String heading, String operationName) {} /** * overload this method for progress tracker. */ public void close() {} /** * overload this method for progress tracker. */ public void pop() {} /** * overload this method for progress tracker. Note that elements * is passed to allow determination of the number of objects - but * the Enumeration must be returned without being reset, so be carefull * when using it... */ public NamingEnumeration push(NamingEnumeration elements) {return elements;} /** * overload this method for progress tracker. */ public void inc() {} /* * * TREE FUNCTIONS * */ public boolean deleteTree(Name nodeDN) // may be a single node. { log.log(Level.FINER,"recursively delete Tree " + nodeDN.toString()); open("Deleting " + nodeDN.toString(), "deleted "); boolean ret = recDeleteTree(nodeDN); close(); return ret; } /** * deletes a subtree by recursively deleting sub-sub trees. * * @param dn the distinguished name of the sub-tree apex to delete. * @return true if the tree was deleted, false otherwise. */ public boolean recDeleteTree(Name dn) { try { NamingEnumeration children = list(dn); if (children!=null && children.hasMore()) { children = push(children); // inform progress tracker that we're going down a level. while (children.hasMoreElements()) { String subDN = ((NameClassPair)children.next()).getName(); if (recDeleteTree(this.parser.parse(subDN))==false) { return false; // ... this also abandons updating progress bar } } pop(); // inform progress tracker that we've come up. } deleteObject(dn); inc(); // inform progress tracker that we've deleted an object. return true; } catch (NamingException e) { error("Failed to remove tree", e); close(); return false; } } /* * * MOVE TREE FUNCTIONS * */ /** * Moves a DN to a new DN, including all subordinate entries. * (nb it is up to the implementer how this is done; e.g. if it is an * ldap broker, it may choose rename, or copy-and-delete, as appropriate) * * @param nodeDN the original DN of the sub tree root (may be a single * entry). * @param progBarDisplayer (may be null) - a Component within which to display * a progress tracker if the operation is taking a long time... * @return the operation's success status */ public boolean moveTree(Name oldNodeDN, Name newNodeDN) // may be a single node. { log.log(Level.FINER,"recursively move tree from " + oldNodeDN.toString() + " to " + newNodeDN.toString()); open("Moving " + oldNodeDN.toString(), "moving"); boolean ret = recMoveTree(oldNodeDN, newNodeDN); close(); return ret; } /** * <p>Moves a tree. If the new position is a sibling of the current * position a <i>rename</i> is performed, otherwise a new tree must * be created, with all its children, and then the old tree deleted.<p> * * <p>If the new tree creation fails during creation, an attempt is made * to delete the new tree, and the operation fails. If the new tree * creation succeeds, but the old tree deletion fails, the operation * fails, leaving the new tree and the partial old tree in existence. * (This last should be unlikely.)</p> * * <p>This move *deletes* the old value of the RDN when the node is * moved.</p> * * @param the root DN of the tree to be moved * @param the root DN of the new tree position * @return the success status of the operation. */ public boolean recMoveTree(Name from, Name to) // may be a single node. { if (from.size() == to.size() && from.startsWith(to.getPrefix(to.size()-1))) // DNs are siblings... { return renameObject (from, to, true); } else // DNs are not siblings; so copy them { // from tree, and then delete the original //TE: does the 'from' DN exist? What if someone gets the DNs around the wrong way? For example // in JXweb a user can enter the DN of where to move from & to...what if they, by mistake, // make the 'to' field the 'from' field? The actual real data will be deleted b/c the copy will // fail due to the 'from' DN not existing and this will fall through to recDeletTree(to)! if(!exists(from)) { error("The DN that you are trying to move does not exist.", null); return false; } if (recCopyTree(from, to)==true) { if (recDeleteTree(from)==true) { return true; // EXIT ON SUCCESS } } else { recDeleteTree(to); // Try to clean up } } return false; } /* * * COPY TREE FUNCTIONS * */ /** * Copies a DN representing a subtree to a new subtree, including * copying all subordinate entries. * * @param oldNodeDN the original DN of the sub tree root * to be copied (may be a single entry). * @param newNodeDN the target DN for the tree to be moved to. * @param progBarDisplayer (may be null) - a Component within which to display * a progress tracker if the operation is taking a long time... * @return the operation's success status */ public boolean copyTree(Name oldNodeDN, Name newNodeDN) // may be a single node. { log.log(Level.FINER,"recursively copy tree from " + oldNodeDN.toString() + " to " + newNodeDN.toString()); open("Copying " + oldNodeDN.toString(), "copying"); boolean ret = recCopyTree(oldNodeDN, newNodeDN); close(); return ret; } /** * Takes two DNs, and goes through the first, copying each element * from the top down to the new DN. * * @param from the ldap Name dn to copy the tree from * @param to the ldap Name dn to copy the tree to * @return true if the operation was successfull, false otherwise */ public boolean recCopyTree(Name from, Name to) { if (copyObject(from,to)==false) { return false; } try { NamingEnumeration children = list(from); if (children.hasMoreElements()) { children = push(children); while (children.hasMoreElements()) { String subDNString = ((NameClassPair)children.next()).getName(); Name subDN = this.parser.parse(subDNString); String childDN = subDN.get(subDN.size()-1); if (recCopyTree(this.parser.parse(childDN+","+from.toString()), this.parser.parse(childDN+","+to.toString()))==false) { recDeleteTree(this.parser.parse(childDN+","+to.toString())); // error! - try to clean up return false; // and return false...(abandoning pbar updates) } } pop(); } inc(); return true; } catch (NamingException e) { error("Failed to copy tree", e); close(); return false; } } }