>From 518841c8527c57f45b1b434685dd0b7276f4f366 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Mon, 22 Sep 2014 03:22:57 -0400 Subject: [PATCH 10/10] Monitor database for changes to LDAP profiles. Use a persistent query to monitor the database for changes to LDAP profiles, and update the contents of the ProfileSubsystem according to the changes (Add/Modify/Delete) that occur. The monitoring occurs within its own thread. --- .../netscape/cmscore/profile/ProfileSubsystem.java | 207 ++++++++++++++++----- 1 file changed, 160 insertions(+), 47 deletions(-) diff --git a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java index ca0e09b785877da2a98fd2dd54977b8d3ebeaa24..2318fcf997801e3a729a7dab7caad3dce182486b 100644 --- a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java +++ b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java @@ -17,15 +17,21 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.cmscore.profile; +import java.lang.Thread; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPControl; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSearchConstraints; import netscape.ldap.LDAPSearchResults; +import netscape.ldap.controls.LDAPEntryChangeControl; +import netscape.ldap.controls.LDAPPersistSearchControl; +import netscape.ldap.util.DN; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.base.EBaseException; @@ -40,7 +46,7 @@ import com.netscape.certsrv.registry.IPluginInfo; import com.netscape.certsrv.registry.IPluginRegistry; import com.netscape.cmscore.base.LDAPConfigStore; -public class ProfileSubsystem implements IProfileSubsystem { +public class ProfileSubsystem implements IProfileSubsystem, Runnable { private static final String PROP_CHECK_OWNER = "checkOwner"; private static final String PROP_ENABLE = "enable"; @@ -52,9 +58,13 @@ public class ProfileSubsystem implements IProfileSubsystem { private Vector mProfileIds; private Hashtable mProfiles; private Hashtable mProfileClassIds; + private String dn; private ILdapConnFactory dbFactory; + private boolean stopped = false; + private Thread monitor; + /** * Retrieves the name of this subsystem. */ @@ -86,9 +96,6 @@ public class ProfileSubsystem implements IProfileSubsystem { mProfiles = new Hashtable(); mProfileClassIds = new Hashtable(); - IPluginRegistry registry = (IPluginRegistry) - CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY); - IConfigStore cs = CMS.getConfigStore(); IConfigStore dbCfg = cs.getSubStore("internaldb"); dbFactory = CMS.getLdapBoundConnFactory(); @@ -106,60 +113,47 @@ public class ProfileSubsystem implements IProfileSubsystem { // read profile id, implementation, and its configuration files String basedn = cs.getString("internaldb.basedn"); - String dn = "ou=certificateProfiles,ou=ca," + basedn; - LDAPConnection conn = dbFactory.getConn(); + dn = "ou=certificateProfiles,ou=ca," + basedn; - String[] attrs = {"cn", "classId"}; - try { - LDAPSearchResults ldapProfiles = conn.search( - dn, LDAPConnection.SCOPE_ONE, "(objectclass=*)", attrs, false); + monitor = new Thread(this, "profileChangeMonitor"); + monitor.start(); + } - while (ldapProfiles.hasMoreElements()) { - String id = ""; - try { - LDAPEntry ldapProfile = ldapProfiles.next(); + /** + * Read the given LDAPEntry into the profile subsystem. + */ + private void readProfile(LDAPEntry ldapProfile) { + IPluginRegistry registry = (IPluginRegistry) + CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY); - id = (String) - ldapProfile.getAttribute("cn").getStringValues().nextElement(); + String profileId = (String) + ldapProfile.getAttribute("cn").getStringValues().nextElement(); - String classid = (String) - ldapProfile.getAttribute("classId").getStringValues().nextElement(); + String classId = (String) + ldapProfile.getAttribute("classId").getStringValues().nextElement(); - IPluginInfo info = registry.getPluginInfo("profile", classid); - if (info == null) { - CMS.debug("Error loading profile: No plugins for type : profile, with id " + classid); - } else { - CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName()); - createProfile(id, classid, info.getClassName()); - CMS.debug("Done Profile Creation - " + id); - } - } catch (LDAPException e) { - CMS.debug("Error reading profile '" + id + "'; skipping."); - } - } - } catch (LDAPException e) { - throw new EBaseException("Error reading profiles: " + e.toString()); - } finally { + IPluginInfo info = registry.getPluginInfo("profile", classId); + if (info == null) { + CMS.debug("Error loading profile: No plugins for type : profile, with classId " + classId); + } else { try { - dbFactory.returnConn(conn); - } catch (Exception e) { - throw new EProfileException("Error releasing the ldap connection" + e.toString()); + CMS.debug("Start Profile Creation - " + profileId + " " + classId + " " + info.getClassName()); + createProfile(profileId, classId, info.getClassName()); + CMS.debug("Done Profile Creation - " + profileId); + } catch (EProfileException e) { + CMS.debug("Error creating profile '" + profileId + "'; skipping."); } } - - Enumeration ee = getProfileIds(); - - while (ee.hasMoreElements()) { - String id = ee.nextElement(); - - CMS.debug("Registered Confirmation - " + id); - } } /** * Creates a profile instance. + * + * createProfile could theoretically be called simultaneously + * with the same profileId from Monitor and ProfileService, + * so the method is synchronized. */ - public IProfile createProfile(String id, String classid, String className) + public synchronized IProfile createProfile(String id, String classid, String className) throws EProfileException { try { String[] objectClasses = {"top", "certProfile"}; @@ -176,7 +170,8 @@ public class ProfileSubsystem implements IProfileSubsystem { IProfile profile = (IProfile) Class.forName(className).newInstance(); profile.setId(id); profile.init(this, subStoreConfig); - mProfileIds.addElement(id); + if (!mProfiles.containsKey(id)) + mProfileIds.addElement(id); mProfiles.put(id, profile); mProfileClassIds.put(id, classid); return profile; @@ -211,11 +206,41 @@ public class ProfileSubsystem implements IProfileSubsystem { } } + forgetProfile(id); + } + + private synchronized void handleMODDN(DN oldDN, LDAPEntry entry) { + DN profilesDN = new DN(dn); + + if (oldDN.isDescendantOf(profilesDN)) + forgetProfile(oldDN.explodeDN(true)[0]); + + if ((new DN(entry.getDN())).isDescendantOf(profilesDN)) + readProfile(entry); + } + + /** + * Forget a profile without deleting it from the database. + * + * This method is used when the profile change monitor receives + * notification that a profile was deleted. + */ + private void forgetProfile(String id) { mProfileIds.removeElement(id); mProfiles.remove(id); mProfileClassIds.remove(id); } + private void forgetProfile(LDAPEntry entry) { + String profileId = (String) + entry.getAttribute("cn").getStringValues().nextElement(); + if (profileId == null) { + CMS.debug("forgetProfile: error retrieving cn (profileId) from LDAPEntry"); + } else { + forgetProfile(profileId); + } + } + /** * Notifies this subsystem if owner is in running mode. */ @@ -229,6 +254,8 @@ public class ProfileSubsystem implements IProfileSubsystem { *

*/ public void shutdown() { + stopped = true; + monitor = null; mProfileIds.clear(); mProfiles.clear(); mProfileClassIds.clear(); @@ -346,6 +373,92 @@ public class ProfileSubsystem implements IProfileSubsystem { } catch (EBaseException e) { throw new EProfileException("CMS_PROFILE_DELETE_UNKNOWNPROFILE"); } - return "cn=" + id + ",ou=certificateProfiles,ou=ca," + basedn; + return "cn=" + id + "," + dn; + } + + public void run() { + int op = LDAPPersistSearchControl.ADD + | LDAPPersistSearchControl.MODIFY + | LDAPPersistSearchControl.DELETE + | LDAPPersistSearchControl.MODDN; + LDAPPersistSearchControl persistCtrl = + new LDAPPersistSearchControl(op, false, true, true); + + LDAPConnection conn; + + CMS.debug("Profile change monitor: starting."); + + while (!stopped) { + try { + conn = dbFactory.getConn(); + } catch (ELdapException e) { + CMS.debug("Profile change monitor: failed to get LDAPConnection. Retrying in 5 seconds."); + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + continue; + } + try { + LDAPSearchConstraints cons = conn.getSearchConstraints(); + cons.setServerControls(persistCtrl); + cons.setBatchSize(1); + cons.setServerTimeLimit(0 /* seconds */); + LDAPSearchResults results = conn.search( + dn, LDAPConnection.SCOPE_ONE, "(objectclass=*)", + null, false, cons); + while (!stopped && results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPEntryChangeControl changeControl = null; + LDAPControl[] changeControls = results.getResponseControls(); + if (changeControls != null) { + for (LDAPControl control : changeControls) { + if (control instanceof LDAPEntryChangeControl) { + changeControl = (LDAPEntryChangeControl) control; + break; + } + } + } + CMS.debug("Profile change monitor: Processed change controls."); + if (changeControl != null) { + int changeType = changeControl.getChangeType(); + switch (changeType) { + case LDAPPersistSearchControl.ADD: + CMS.debug("Profile change monitor: ADD"); + readProfile(entry); + break; + case LDAPPersistSearchControl.DELETE: + CMS.debug("Profile change monitor: DELETE"); + forgetProfile(entry); + break; + case LDAPPersistSearchControl.MODIFY: + CMS.debug("Profile change monitor: MODIFY"); + readProfile(entry); + break; + case LDAPPersistSearchControl.MODDN: + CMS.debug("Profile change monitor: MODDN"); + handleMODDN(new DN(changeControl.getPreviousDN()), entry); + break; + default: + CMS.debug("Profile change monitor: unknown change type: " + changeType); + break; + } + } else { + CMS.debug("Profile change monitor: immediate result"); + readProfile(entry); + } + } + } catch (LDAPException e) { + CMS.debug("Profile change monitor: Caught exception: " + e.toString()); + } finally { + try { + dbFactory.returnConn(conn); + } catch (Exception e) { + CMS.debug("Profile change monitor: Error releasing the LDAPConnection" + e.toString()); + } + } + } + CMS.debug("Profile change monitor: stopping."); } } -- 1.9.3