package jeffaschenk.commons.frameworks.cnxidx.admin; import jeffaschenk.commons.frameworks.cnxidx.utility.ldap.*; import java.util.*; import java.io.*; import javax.naming.*; import javax.naming.directory.*; /** * Java Daemon Server thread. * * @author jeff.schenk * @version 2.0 $Revision * Developed 2001-2002 */ /** * IRRChangePoller * Class to run MetaLink Trigger Poller Thread. */ class IRRChangePoller implements Runnable, idxCMDReturnCodes { /** * IRRChangePoller * Class to Interface to MetaLink Triggers. */ Thread t; private BufferedWriter CWQ; private IRRChangeStatus ChangeStatus; private static String MP = "IRRChangePoller: "; private static final String XMLHDR = "<?xml version='1.0' encoding='utf-8'?>"; private static final String EOPYES = XMLHDR + "<irrchangeloggerprocess endofcycle='no' endofprocess='yes'/>" + "\n"; private static final String EOCYES = XMLHDR + "<irrchangeloggerprocess endofcycle='yes' endofprocess='no'/>" + "\n"; private static idxManageContext IRRSource = null; private static String IRRHost = null; private static String IRRPrincipal = null; private static String IRRCredentials = null; private static String IRRMLTuid = null; private static String OUTPUT_DIRECTORY = null; private static boolean VERBOSE = false; private static boolean DEBUG = false; private idxTimeStamp CurrentTimeStamp = new idxTimeStamp(); private boolean ExitOnException = false; private boolean RUNNING = true; /** * IRRChangePoller Contructor class driven. * * @param BPQout Pipe Queue. * @param _ChangeStatus Object. * @param _IRRHost Source IRR LDAP URL. * @param _IRRPrincipal Source IRR Principal. * @param _IRRCredentials Source IRR Credentials. * @param _IRRMLTuid MetaLink Trigger UID. * @param _OUTPUT_DIRECTORY Output Directory Name for Trapping a Stop. * @param _VERBOSE Indicate Verbosity. * @param _DEBUG Indicate DEBUGGING. * @param _ExitOnException Indicate Exit on Exceptions. */ IRRChangePoller(Writer BPQout, IRRChangeStatus _ChangeStatus, String _IRRHost, String _IRRPrincipal, String _IRRCredentials, String _IRRMLTuid, String _OUTPUT_DIRECTORY, boolean _VERBOSE, boolean _DEBUG, boolean _ExitOnException) { // **************************************** // Set My Incoming Parameters. // IRRHost = _IRRHost; IRRPrincipal = _IRRPrincipal; IRRCredentials = _IRRCredentials; IRRMLTuid = _IRRMLTuid; OUTPUT_DIRECTORY = _OUTPUT_DIRECTORY; VERBOSE = _VERBOSE; DEBUG = _DEBUG; ExitOnException = _ExitOnException; ChangeStatus = _ChangeStatus; // **************************************** // Ready the Synchronized Object and start // the Thread. // try { this.CWQ = new BufferedWriter(BPQout); t = new Thread(this, "IRRChangePoller"); t.start(); // Start the Thread. } catch (Exception e) { // TODO Handle } } // End of Contructor. public void run() { // *********************************************** // Initialize long memfree; long CollectorCycle = 0; long CollectorChangesDetected = 0; String tname = Thread.currentThread().getName(); CurrentTimeStamp.enableLocalTime(); // Show Local Time Not GMT. System.out.println(MP + CurrentTimeStamp.getFTS() + " Thread Established for:[" + tname + "]"); // *********************************************** // Initialize my LAP Timers idxLapTime LP_OBTAINING_TRIGGER = new idxLapTime(); idxLapTime LP_PROCESSING_TRIGGER_LIST = new idxLapTime(); idxLapTime LP_ENTRY_TO_PIPE = new idxLapTime(); // ************************************************** // Obtain Runtime Object. Runtime rt = Runtime.getRuntime(); // ************************************************** // Shutdown Indicator Filename. String SHUTDOWN_FILENAME = OUTPUT_DIRECTORY + System.getProperty("file.separator") + "IRRCHGLOG_SHUTDOWN_PROCESS"; // *********************************************** // Now initiate a Connection to the Directory // for a LDAP Source Context for obtain leaf entries. System.out.println(MP + CurrentTimeStamp.getFTS() + " Attempting Aux Object Directory Connection to Host URL:[ldap://" + IRRHost + "]"); IRRSource = new idxManageContext("ldap://" + IRRHost, IRRPrincipal, IRRCredentials, "IRRChangePoller Aux Source"); // ************************************************ // Exit on all Exceptions. IRRSource.setExitOnException(false); // ************************************************ // Now Try to Open and Obtain Context. try { IRRSource.open(); } catch (Exception e) { System.err.println(MP + e); ChangeStatus.setError(EXIT_IRR_CLOSE_FAILURE); return; } // End of exception // ***************************************** // Disable the Factories. try { IRRSource.disableDSAEFactories(); } catch (Exception e) { System.err.println(MP + e); ChangeStatus.setError(EXIT_GENERIC_FAILURE); return; } // End of exception // **************************************** // Indicate the Poller is starting. System.out.println(MP + CurrentTimeStamp.getFTS() + " Starting MetaLink Poller..."); // **************************************** // Now Enter our LOOP to obtain changes // from the MetaLink Trigger... while (RUNNING) { // *********************************************** // Now Determine if I should stop the process // based upon the existenance of a "SHUTDOWN" file // in the Output Directory. // File sFile = new File(SHUTDOWN_FILENAME); if (sFile.exists()) { System.out.println(MP + CurrentTimeStamp.getFTS() + " Detected Stop Request," + " Shutdown of Change Log Process in progress."); break; } // *********************************************** // Now initiate a Connection to the Directory // to obtain the MLT Connection Context. CollectorCycle++; if (VERBOSE) { System.out.println(MP + CurrentTimeStamp.getFTS() + " Cycle:[" + CollectorCycle + "], MetaLink Poller Directory Connecting to Host:[" + IRRHost + "]"); } // ******************************************* // Establish MetaLink Trigger Token. //MLTrigConnectionTokenSimple MLTtoken = new MLTrigConnectionTokenSimple( IRRHost, // IRRPrincipal, // IRRCredentials); // *********************************************** // Now initiate a Connection to the Directory // for a Trigger Context //MLTrigConnection MLTcontext = new MLTrigConnection(); //try { //MLTcontext.start( MLTtoken, IRRMLTuid ); //} catch (MLTrigException e) { // System.err.println(MP+"MLT Exception Starting Connection, "+e); // ChangeStatus.setError( EXIT_IRR_UNABLE_TO_OBTAIN_CONTEXT ); // *************************************** // Tell the LDIF Thread to Finish. // try { // CWQ.write( EOPYES ); // CWQ.flush(); // } catch(Exception xe) {} // ********************** // End Thread. // return; // End Thread. //} // End of Exception // **************************************** // If Debug, show total Memory. if (DEBUG) { System.out.println(MP + "Total Memory: [" + rt.totalMemory() + "]."); memfree = rt.freeMemory(); System.out.println(MP + "Current Free Memory: [" + memfree + "]."); } // *********************************************** // Wait for a Change Trigger to be popped System.out.println(MP + CurrentTimeStamp.getFTS() + " Cycle:[" + CollectorCycle + "], Waiting for Changes...."); //try { LP_OBTAINING_TRIGGER.Start(); //MLTrigChangeList lchangelist = MLTcontext.getNextChanges(); LP_OBTAINING_TRIGGER.Stop(); // *************************************** // Ok, we have a change list, process LP_PROCESSING_TRIGGER_LIST.Start(); // TODO -- this while will not go into effect... //while( lchangelist.hasMoreChanges() ) //{ CollectorChangesDetected++; String TypeofChange; //MLTrigChange lchange = lchangelist.getNextChange(); //MLTrigChangeRename lRchange = null; /** switch( lchange.getOperation() ) { case MLTrigChange.ADD: TypeofChange = "ADD"; break; case MLTrigChange.MODIFY: TypeofChange = "MODIFY"; break; case MLTrigChange.DELETE: TypeofChange = "DELETE"; break; case MLTrigChange.RENAME: TypeofChange = "RENAME"; lRchange = (MLTrigChangeRename)lchange; break; default: TypeofChange = "UNKNOWN"; break; } // End of Switch **/ TypeofChange = "UNKNOWN"; // ********************************************* // Show the Change Information // And construct a XML Document to send over the // Pipe to our other Thread Buddy. if (VERBOSE) { System.out.println(MP + CurrentTimeStamp.getFTS() + " Operation Perform on Entry:[" + TypeofChange + "]"); } /** if ( lchange.getOperation() == MLTrigChange.RENAME ) { if (VERBOSE) { System.out.println(MP+CurrentTimeStamp.getFTS()+" olddn: " + lchange.getDN() ); System.out.println(MP+CurrentTimeStamp.getFTS()+" dn: " + lRchange.getNewDN() ); } LP_ENTRY_TO_PIPE.Start(); CWQ.write( XMLHDR+"<irrchange "+ "typename=\042"+TypeofChange+"\042 "+ "type=\042"+lchange.getOperation()+"\042 "+ "dn=\042"+lRchange.getNewDN()+"\042 "+ "olddn=\042"+lchange.getDN()+"\042 "+ "/>\n" ); LP_ENTRY_TO_PIPE.Stop(); CWQ.flush(); // ************************************* // Now we need to feed the pipe with // any leaf entries that were affected // by the rename of a parent. // TreeMap LEAFmap = new TreeMap(); try { LEAFmap = obtainLeafEntries( IRRSource.irrctx, lRchange.getNewDN(), lchange.getDN()); } catch(Exception e) { System.out.println(MP+CurrentTimeStamp.getFTS()+ "Exception obtaining leaf Entries, Ignoring Exception:["+e+"]"); } // End of Exception. // ************************************* // Now iterate through the Treemap // and produce additional rename // operations for leaf entries. // if ( (LEAFmap != null) && (LEAFmap.size() > 0 ) ) { Set _Rset = LEAFmap.entrySet(); Iterator iterator = _Rset.iterator(); while (iterator.hasNext()) { Map.Entry _Rentry = (Map.Entry)iterator.next(); String _X500newDN = (String)_Rentry.getKey(); String _oldDN = (String)LEAFmap.get(_X500newDN); LP_ENTRY_TO_PIPE.Start(); CWQ.write( XMLHDR+"<irrchange "+ "typename=\042"+TypeofChange+"\042 "+ "type=\042"+lchange.getOperation()+"\042 "+ "dn=\042"+convertX500NameToLDAPName(_X500newDN)+"\042 "+ "olddn=\042"+_oldDN+"\042 "+ "/>\n" ); LP_ENTRY_TO_PIPE.Stop(); CWQ.flush(); if (VERBOSE) { System.out.println(MP+CurrentTimeStamp.getFTS()+ " leaf olddn: " + _oldDN); System.out.println(MP+CurrentTimeStamp.getFTS()+ " leaf dn: " + convertX500NameToLDAPName(_X500newDN) ); } // End of If. } // End of While. } else { System.out.println(MP+CurrentTimeStamp.getFTS()+ " No Leaf Entries for Rename to be Processed."); } // End of Else. // *********************************** // All other operations here. } else { if (VERBOSE) { System.out.println(MP+CurrentTimeStamp.getFTS()+" dn: " + lchange.getDN() ); } LP_ENTRY_TO_PIPE.Start(); CWQ.write( XMLHDR+"<irrchange "+ "typename=\042"+TypeofChange+"\042 "+ "type=\042"+lchange.getOperation()+"\042 "+ "dn=\042"+lchange.getDN()+"\042 "+ "/>\n" ); LP_ENTRY_TO_PIPE.Stop(); CWQ.flush(); } // End of Else. **/ // } // End of ChangeList Inner While. LP_PROCESSING_TRIGGER_LIST.Stop(); // ***************************************** // Now we have scanned through a change // List, tell the MLT Facility to stop // and that we have processed all. // System.out.println(MP + CurrentTimeStamp.getFTS() + " Closing MetaLink Trigger Context, Changed Collected for this cycle:[" + CollectorChangesDetected + "]."); //MLTcontext.stop(true); CollectorChangesDetected = 0; // *************************************** // Show the Lap Timings. if (VERBOSE) { System.out.println(MP + CurrentTimeStamp.getFTS() + " Cycle:[" + CollectorCycle + "], Lap Time for Pipe Communications: " + LP_ENTRY_TO_PIPE); System.out.println(MP + CurrentTimeStamp.getFTS() + " Cycle:[" + CollectorCycle + "], Lap Time for Obtaining a Trigger List: " + LP_OBTAINING_TRIGGER); System.out.println(MP + CurrentTimeStamp.getFTS() + " Cycle:[" + CollectorCycle + "], Lap Time for Processing a Trigger List: " + LP_PROCESSING_TRIGGER_LIST); } // End of If. // *********************************************** // Reset my LAP Timers for Next Cycle. LP_OBTAINING_TRIGGER.Reset(); LP_PROCESSING_TRIGGER_LIST.Reset(); LP_ENTRY_TO_PIPE.Reset(); // **************************************** // Tell the Writer Thread to CheckPoint // to next output file. // try { CWQ.write(EOCYES); CWQ.flush(); } catch (Exception e) { System.err.println(MP + "IRR Exception Writing to Thread Pipe,\n" + e); ChangeStatus.setError(EXIT_GENERIC_FAILURE); return; // End Thread. } // End Of Exception. /** } catch (MLTrigException e) { // ************************************** // If we have an ReturnCode of a Server // Operation Failure, then we simply // need to fall through and obtain // a connection again. // If a different error, then we // fail. // if ( e.getErrorCode() != MLTrigException.SERVER_OP_FAILED ) { System.err.println(MP+"MLT Exception on getNextChanges RC:[" + e.getErrorCode() + "],"+ e); e.printStackTrace(); ChangeStatus.setError( EXIT_GENERIC_FAILURE ); // ************************ // Tell other thread to end try { CWQ.write( EOPYES ); CWQ.flush(); } catch (Exception xe) {} return; // End of Thread. } // End of If. else { if (VERBOSE) { System.out.println(MP+CurrentTimeStamp.getFTS()+ " Cycle:["+CollectorCycle+"], MetaLink Poller Ended, will retry Cycle."); } CollectorCycle--; } // End of Else. } catch (Exception e) { System.err.println(MP+"IRR Exception on Obtaining Child Entries,\n" + e); e.printStackTrace(); ChangeStatus.setError( EXIT_GENERIC_FAILURE ); // ************************ // Tell other thread to end try { CWQ.write( EOPYES ); CWQ.flush(); } catch (Exception xe) {} return; // End Thread. } // End of exception **/ } // End of Outer While. // *************************************** // Tell the LDIF Thread to Finish. try { CWQ.write(EOPYES); CWQ.flush(); // ******************************* // Wait for LDIF Thread to Finish. // Thread.sleep(10000); // 10 Seconds. } catch (Exception e) { System.err.println(MP + "IRR Exception Writing to Thread Pipe,\n" + e); ChangeStatus.setError(EXIT_GENERIC_FAILURE); return; // End Thread. } // End of Exception // *************************************** // Done. return; } // End of run. /** * obtainLeafEntries * Method to obtain all current leaf entries for the * existing New DN. * <p/> * Since if a Rename occurrs that affects it's leaf entries * we must generate the necessary rename for those leafs * as well. * * @param _irrctx of IRR Instance. * @param _ParentNewDN Parent New DN. * @param _ParentOldDN Parent Old DN. * @return TreeMap of Entries. */ private TreeMap obtainLeafEntries(DirContext _irrctx, String _ParentNewDN, String _ParentOldDN) throws Exception { // *********************************** // Obtain the Notification List File // Information. // TreeMap<String,String> LEAFmap = new TreeMap<>(); // *********************************** // Set up the Search Controls. String[] NO_AttrIDs = {"1.1"}; SearchControls SUBTREE_OS_ctls = new SearchControls(); SUBTREE_OS_ctls.setReturningAttributes(NO_AttrIDs); SUBTREE_OS_ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); // ***************************************** // Parse the Destination Entry DN to be sure // we have any Quotes.... idxParseDN Naming_Source = new idxParseDN(_ParentNewDN); // ***************************************** // Obtain the Namespace. String NameSpace = null; try { NameSpace = _irrctx.getNameInNamespace(); if (NameSpace.equals("")) { NameSpace = _ParentNewDN; } } catch (Exception e) { NameSpace = _ParentNewDN; } // End of exception // ***************************************** // Now obtain the Leaf Entries. // try { NamingEnumeration sl = _irrctx.search(_ParentNewDN, "(objectclass=*)", SUBTREE_OS_ctls); // ******************************** // If the Result is Null, Ignore. if (sl == null) { return (LEAFmap); } // *********************************** // Loop to obtain all Leaf Entry DNs long Rename_DNcount = 0; while (sl.hasMore()) { SearchResult si = (SearchResult) sl.next(); String DN = null; if (NameSpace.equals("")) { DN = si.getName(); } else if (si.getName().equals("")) { DN = NameSpace; } else { DN = si.getName() + "," + NameSpace; } // ************************************ // Parse the Current DN. idxParseDN pDN = new idxParseDN(DN); // ******************************** // Formulate the Old DN. // Rename_DNcount++; String formulatedOldDN = _ParentOldDN; if (Rename_DNcount != 1) { // **************************************** // Calculate the depth we need to obtain // with Parent and Current DN. // int PrefixSize = ((pDN.depth() - Naming_Source.depth()) - 1); String childRDN = ""; // *************************************************************** // Parse for a specific depth of the DN. // idxParseDN XDN = new idxParseDN(pDN.getDN()); for (int zI = 0; zI <= PrefixSize; zI++) { if ((XDN.getPDN() == null) || ("".equalsIgnoreCase(XDN.getPDN()))) { break; } if ((childRDN == null) || (childRDN.equalsIgnoreCase(""))) { childRDN = XDN.getRDN(); } else { childRDN = childRDN + "," + XDN.getRDN(); } XDN = new idxParseDN(XDN.getPDN()); } // End of For Loop. // ************************************** // Fully Formulated Old Child DN. formulatedOldDN = childRDN + "," + _ParentOldDN; ; // ******************************************* // Place Entry into TreeMap. LEAFmap.put(pDN.getX500Name(), formulatedOldDN); } // End of Inner If. } // End of While Loop. } catch (NameNotFoundException nfe) { return (LEAFmap); } // End of exception // ******************************** // Return the TreeMap. return (LEAFmap); } // End of obtainLeafEntries. /** * convertX500NameToLDAPName. * * @param _x500name X500Name * @return String LDAPName */ private String convertX500NameToLDAPName(String _x500name) { // *************************************** // Initialize. String _ldapname = ""; // *************************************** // Now Parse out the X500 Domains to // Create the GLuE Nodes. // StringTokenizer NODES = new StringTokenizer(_x500name, "/"); while (NODES.hasMoreTokens()) { String node = (String) NODES.nextToken(); if ((node == null) || (node.equals(""))) { continue; } // ********************************** // Place the Node at the Begining of // the LDAP Name. if (_ldapname.equals("")) { _ldapname = node; } else { _ldapname = node + "," + _ldapname; } } // End of While. // *************************************** // Return the LDAP Name. return (_ldapname); } // End of convertX500NameToLDAPName. } // End of Class IRRChangePoller