/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved. * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: IdRepoPluginsCache.java,v 1.8 2009/11/10 01:52:37 hengming Exp $ */ package com.sun.identity.idm.server; import com.iplanet.am.util.SystemProperties; import com.iplanet.sso.SSOException; import com.iplanet.sso.SSOToken; import com.sun.identity.common.DNUtils; import com.sun.identity.idm.IdConstants; import com.sun.identity.idm.IdOperation; import com.sun.identity.idm.IdRepo; import com.sun.identity.idm.IdRepoBundle; import com.sun.identity.idm.IdRepoException; import com.sun.identity.idm.IdRepoListener; import com.sun.identity.idm.IdType; import com.sun.identity.security.AdminTokenAction; import com.sun.identity.shared.Constants; import com.sun.identity.shared.datastruct.OrderedSet; import com.sun.identity.shared.debug.Debug; import com.sun.identity.sm.DNMapper; import com.sun.identity.sm.SMSException; import com.sun.identity.sm.SMSThreadPool; import com.sun.identity.sm.ServiceConfig; import com.sun.identity.sm.ServiceConfigManager; import com.sun.identity.sm.ServiceListener; import com.sun.identity.sm.ServiceManager; import com.sun.identity.sm.ServiceSchemaManager; import java.security.AccessController; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; /** * * @author aravind */ public class IdRepoPluginsCache implements ServiceListener { static Debug debug = Debug.getInstance("amIdm"); static boolean initializedListeners; static ServiceConfigManager idRepoServiceConfigManager; private static int svcRevisionNumber; // Cache of IdRepo Plugins // The Map contains > private Map idrepoPlugins = new HashMap(); // Needs to synchronized for get(), put() and clear() private Map readonlyPlugins = new Hashtable(); protected IdRepoPluginsCache() { // Initialize listeners if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache constructor called"); } initializeListeners(); } protected Set getIdRepoPlugins(String orgName) throws IdRepoException, SSOException { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.getIdRepoPlugins orgName: " + orgName); } // Check the cache Map orgRepos = null; orgName = DNUtils.normalizeDN(orgName); Set readOrgRepos = (Set) readonlyPlugins.get(orgName); if ((readOrgRepos != null) && !readOrgRepos.isEmpty()) { return (readOrgRepos); } synchronized (idrepoPlugins) { orgRepos = (Map) idrepoPlugins.get(orgName); if (orgRepos == null) { try { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.getIdRepoPlugins " + "Not in cache for: " + orgName); } // Initialize the plugins orgRepos = new LinkedHashMap(); ServiceConfig sc = idRepoServiceConfigManager .getOrganizationConfig(orgName, null); if (sc == null) { // Organization does not exist. Error condition debug.error("IdRepoPluginsCache.getIdRepoPlugins " + "Org does not exisit: " + orgName); Object[] args = { orgName }; throw new IdRepoException( IdRepoBundle.BUNDLE_NAME, "312", args); } Set subConfigNames = sc.getSubConfigNames(); if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.getIdRepoPlugins " + "Loading plugins: " + subConfigNames); } if (subConfigNames != null && !subConfigNames.isEmpty()) { for (Iterator items = subConfigNames.iterator(); items.hasNext();) { String idRepoName = (String) items.next(); ServiceConfig reposc = sc.getSubConfig(idRepoName); if (reposc == null) { debug.error("IdRepoPluginsCache." + "getIdRepoPlugins SubConfig is null for" + " orgName: " + orgName + " subConfig Name: " + idRepoName); } IdRepo repo = constructIdRepoPlugin(orgName, reposc.getAttributesForRead(), idRepoName); // Add to cache orgRepos.put(idRepoName, repo); } } // Add internal repos addInternalRepo(orgRepos, orgName); idrepoPlugins.put(orgName, orgRepos); } catch (SMSException ex) { debug.error("IdRepoPluginsCache.getIdRepoPlugins " + "SMS Exception for orgName: " + orgName, ex); } } // Cache a readonly copy if (orgRepos != null) { readOrgRepos = new OrderedSet(); readOrgRepos.addAll(orgRepos.values()); readonlyPlugins.put(orgName, readOrgRepos); } } if (debug.messageEnabled() && (readOrgRepos != null)) { Set ps = new HashSet(); for (Iterator items = readOrgRepos.iterator(); items.hasNext();) { ps.add(items.next().getClass().getName()); } debug.message("IdRepoPluginsCache.getIdRepoPlugins retuned for" + " OrgName: " + orgName + " Plugins: " + ps); } return (readOrgRepos); } protected Set getIdRepoPlugins(String orgName, IdOperation op, IdType type) throws IdRepoException, SSOException { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.getIdRepoPlugins for " + "OrgName: " + orgName + " Op: " + op + " Type: " + type); } String cacheName = DNUtils.normalizeDN(orgName) + op.toString() + type.toString(); Set answer = (Set) readonlyPlugins.get(cacheName); if ((answer != null) && !answer.isEmpty()) { return (answer); } answer = new OrderedSet(); Set plugins = getIdRepoPlugins(orgName); if ((plugins != null) && !plugins.isEmpty()) { for (Iterator items = plugins.iterator(); items.hasNext();) { IdRepo repo = (IdRepo) items.next(); if (repo.getSupportedTypes().contains(type)) { Set ops = repo.getSupportedOperations(type); if (ops.contains(op)) { answer.add(repo); } } } } if (debug.messageEnabled()) { Set ps = new HashSet(); for (Iterator items = answer.iterator(); items.hasNext();) { IdRepo repo = (IdRepo) items.next(); ps.add(repo.getClass().getName()); } debug.message("IdRepoPluginsCache.getIdRepoPlugins retuned for" + " OrgName: " + orgName + " Op: " + op + " Type: " + type + " Plugins: " + ps); } synchronized (idrepoPlugins) { if (answer != null) { readonlyPlugins.put(cacheName, answer); } } return (answer); } /** * Delete an IdRepo plugin */ private void removeIdRepo(String orgName, String name, boolean reinitialize) throws IdRepoException, SSOException { orgName = DNUtils.normalizeDN(orgName); synchronized (idrepoPlugins) { // Clear IdRepo plugins first since other threads should // not access it during shutdown clearReadOnlyPlugins(orgName); Map idrepos = (Map) idrepoPlugins.get(orgName); if (idrepos != null && !idrepos.isEmpty()) { // Iterate through the plugins for (Iterator items = idrepos.keySet().iterator(); items.hasNext();) { String iname = items.next().toString(); if (iname.equalsIgnoreCase(name)) { IdRepo repo = (IdRepo) idrepos.get(iname); // Shutting down idrepo if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.removeIdRepo" + " for OrgName: " + orgName + " Repo Name: " + name); } // Remove from cache first idrepos.remove(iname); ShutdownIdRepoPlugin shutdownrepo = new ShutdownIdRepoPlugin(repo); // Provide a delay of 500ms for existing operations // to complete. the delay is in the forked thread. SMSThreadPool.scheduleTask(shutdownrepo); break; } } if (reinitialize) { // Adding plugin back provides the atomic operation // for the caller. Else, client will get No-plugins // configured exception. // Add the plugin back to the cache addIdRepo(orgName, name); } } } } private void clearReadOnlyPlugins(String orgName) { // clear a readonly copy for the org Name for (Iterator items = readonlyPlugins.keySet().iterator(); items.hasNext();) { String name = items.next().toString(); if (name.startsWith(orgName)) { items.remove(); } } } /** * Delete all IdRepo plugin for the organization */ private void removeIdRepo(String orgName) { orgName = DNUtils.normalizeDN(orgName); Map idrepos = null; synchronized (idrepoPlugins) { // Clear IdRepo plugins first clearReadOnlyPlugins(orgName); idrepos = (Map) idrepoPlugins.remove(orgName); } if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.removeIdRepo for " + "OrgName: " + orgName + " Repo Names: " + idrepos.keySet()); } ShutdownIdRepoPlugin shutdownrepos = new ShutdownIdRepoPlugin(idrepos); // Provide a delay of 500ms for existing operations // to complete. the delay is in the forked thread. SMSThreadPool.scheduleTask(shutdownrepos); } /** * Clears the IdRepo plugin cache */ public void clearIdRepoPluginsCache() { Map cache = null; synchronized (idrepoPlugins) { // Clear readonly cache first. // Don't want other theads to get plugins that are // shutdown. readonlyPlugins.clear(); cache = new HashMap(idrepoPlugins); idrepoPlugins.clear(); readonlyPlugins.clear(); } // Iterate throught the orgName and shutdown the repos for (Iterator onames = cache.keySet().iterator(); onames.hasNext();) { Map repos = (Map) cache.get(onames.next()); for (Iterator items = repos.keySet().iterator(); items.hasNext();) { String name = items.next().toString(); IdRepo repo = (IdRepo) repos.get(name); repo.removeListener(); repo.shutdown(); } } } /** * Adds an IdRepo plugin to an organization given the configuration * @param orgName organization to which IdRepo would be added * @param configMap configuration of the IdRepo */ private void addIdRepo(String orgName, String name) throws IdRepoException, SSOException { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.addIdRepo called for orgName: " + orgName + " IdRepo Name: " + name); } Map configMap = null; try { ServiceConfig sc = idRepoServiceConfigManager .getOrganizationConfig(orgName, null); if (sc == null) { debug.error("IdRepoPluginsCache.addIdRepo orgName: " + orgName + " does not exisit"); Object[] args = {orgName}; throw new IdRepoException( IdRepoBundle.BUNDLE_NAME, "312", args); } sc = sc.getSubConfig(name); if (sc == null) { debug.error("IdRepoPluginsCache.addIdRepo orgName: " + orgName + " subConfig does not exisit: " + name); Object[] args = {orgName + ":" + name}; throw new IdRepoException( IdRepoBundle.BUNDLE_NAME, "312", args); } configMap = sc.getAttributes(); } catch (SMSException smse) { if (debug.warningEnabled()) { debug.warning("IdRepoPluginsCache.addIdRepo SMSException " + "for orgName: " + orgName + " sc name: " + name, smse); } return; } IdRepo repo = constructIdRepoPlugin(orgName, configMap, name); // Add to cache orgName = DNUtils.normalizeDN(orgName); synchronized (idrepoPlugins) { // Clear the readonly plugins first. // Other threads have to wait for the initialization to complete // Will get updated when getPlugins gets called clearReadOnlyPlugins(orgName); Map repos = (Map) idrepoPlugins.get(orgName); boolean addInternalRepos = false; if (repos == null) { repos = new LinkedHashMap(); idrepoPlugins.put(orgName, repos); addInternalRepos = true; } repos.put(name, repo); if (addInternalRepos) { addInternalRepo(repos, orgName); } } } private void addInternalRepo(Map orgRepos, String orgName) throws SSOException, IdRepoException { // Check if AMSDK plugin needs to be added if (ServiceManager.isCoexistenceMode()) { orgRepos.put(IdConstants.AMSDK_PLUGIN_NAME, getAMRepoPlugin(orgName)); } // Check if AgentsRepos needs to be added if (svcRevisionNumber >= 30) { orgRepos.put(IdConstants.AGENTREPO_PLUGIN, getAgentRepoPlugin(orgName)); } // Check if SpecialRepo needs to be added if (ServiceManager.isConfigMigratedTo70() && ServiceManager.getBaseDN().equalsIgnoreCase(orgName)) { orgRepos.put(IdConstants.SPECIAL_PLUGIN, getSpecialRepoPlugin()); } } protected void initializeListeners() { synchronized (debug) { if (!initializedListeners) { // Add listeners to Service Schema and Config Managers if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.initializeListeners: " + "setting up ServiceListener"); } SSOToken token = getAdminToken(); try { // Initialize configuration objects idRepoServiceConfigManager = new ServiceConfigManager(token, IdConstants.REPO_SERVICE, "1.0"); idRepoServiceConfigManager.addListener(this); // Initialize schema objects ServiceSchemaManager idRepoServiceSchemaManager = new ServiceSchemaManager(token, IdConstants.REPO_SERVICE, "1.0"); idRepoServiceSchemaManager.addListener(this); // Get the version number svcRevisionNumber = idRepoServiceSchemaManager .getRevisionNumber(); // Initialize listener for JAXRPCObject IdRepoListener.addRemoteListener( new JAXRPCObjectImplEventListener()); initializedListeners = true; } catch (SMSException smse) { // Exceptions will be throws during install and config // when these services will not be loaded String installTime = SystemProperties.get( Constants.SYS_PROPERTY_INSTALL_TIME, "false"); if (!installTime.equals("true")) { debug.error("IdRepoPluginsCache.initializeListeners: " + "Unable to set up a service listener for IdRepo", smse); } } catch (SSOException ssoe) { debug.error("IdRepoPluginsCache.initializeListeners: " + "Unable to set up a service listener for IdRepo.", ssoe); } } } } /** * Constructs IdRepo plugin object and returns. */ private IdRepo constructIdRepoPlugin(String orgName, Map configMap, String name) throws IdRepoException, SSOException { IdRepo answer = null; if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.constructIdRepoPlugin: config=" + configMap.get("sunIdRepoClass")); } if (configMap == null || configMap.isEmpty()) { if (debug.warningEnabled()) { debug.warning("IdRepoPluginsCache.constructIdRepoPlugin: " + "Cannot construct with empty config data"); } return (null); } Set vals = (Set) configMap.get(IdConstants.ID_REPO); if ((vals != null) && !vals.isEmpty()) { String className = (String) vals.iterator().next(); Class thisClass; try { thisClass = Thread.currentThread().getContextClassLoader() .loadClass(className); answer = (IdRepo) thisClass.newInstance(); } catch (Throwable ex) { debug.error("IdRepoPluginsCached.constructIdRepoPlugin " + " OrgName: " + orgName + " ConfigMap: " + configMap, ex); throw (new IdRepoException(ex.getMessage())); } answer.initialize(configMap); // Add listener to this plugin class! Map listenerConfig = new HashMap(); listenerConfig.put("realm", orgName); listenerConfig.put("plugin-name", name); if (className.equals( IdConstants.AMSDK_PLUGIN)) { listenerConfig.put("amsdk", "true"); } IdRepoListener listener = new IdRepoListener(); listener.setConfigMap(listenerConfig); answer.addListener(getAdminToken(), listener); } return (answer); } private static SSOToken getAdminToken() { return ((SSOToken) AccessController.doPrivileged( AdminTokenAction.getInstance())); } // Internal repos private IdRepo getSpecialRepoPlugin() throws SSOException, IdRepoException { // Valid only for root realm IdRepo pluginClass = null; try { if (debug.messageEnabled()) { debug.message("Special repo being initialized"); } Class thisClass = Thread.currentThread(). getContextClassLoader().loadClass(IdConstants.SPECIAL_PLUGIN); pluginClass = (IdRepo) thisClass.newInstance(); HashMap config = new HashMap(2); config.put("realm", ServiceManager.getBaseDN()); pluginClass.initialize(config); IdRepoListener lter = new IdRepoListener(); lter.setConfigMap(config); pluginClass.addListener(getAdminToken(), lter); } catch (Exception e) { debug.error("IdRepoPluginsCache.getSpecialRepoPlugin: " + "Unable to init plugin: " + IdConstants.SPECIAL_PLUGIN, e); } return pluginClass; } protected IdRepo getAgentRepoPlugin(String orgName) throws SSOException, IdRepoException { IdRepo pluginClass = null; try { if (debug.messageEnabled()) { debug.message("Agents repo being initialized"); } Class thisClass = Thread.currentThread(). getContextClassLoader().loadClass(IdConstants.AGENTREPO_PLUGIN); pluginClass = (IdRepo) thisClass.newInstance(); HashMap config = new HashMap(2); HashSet realmName = new HashSet(); realmName.add(orgName); config.put("agentsRepoRealmName", realmName); pluginClass.initialize(config); } catch (Exception e) { debug.error("IdRepoPluginsCache.getAgentRepoPlugin: " + "Unable to init plugin: " + IdConstants.AGENTREPO_PLUGIN, e); } // Add listener if (pluginClass != null) { Map listenerConfig = new HashMap(); listenerConfig.put("realm", orgName); IdRepoListener lter = new IdRepoListener(); lter.setConfigMap(listenerConfig); pluginClass.addListener(getAdminToken(), lter); } // Retuns the plugin class return pluginClass; } protected IdRepo getAMRepoPlugin(String orgName) throws SSOException, IdRepoException { IdRepo pluginClass = null; try { if (debug.messageEnabled()) { debug.message("AMSDK repo being initialized"); } Class thisClass = Thread.currentThread(). getContextClassLoader().loadClass(IdConstants.AMSDK_PLUGIN); pluginClass = (IdRepo) thisClass.newInstance(); Map amsdkConfig = new HashMap(); Set vals = new HashSet(); vals.add(DNMapper.realmNameToAMSDKName(orgName)); amsdkConfig.put("amSDKOrgName", vals); pluginClass.initialize(amsdkConfig); } catch (Exception e) { debug.error("IdRepoPluginsCache.getAMRepoPlugin: " + "Unable to instantiate plugin for Org: " + orgName, e); } if (pluginClass != null) { // Add listener to this plugin class Map listenerConfig = new HashMap(); listenerConfig.put("realm", orgName); listenerConfig.put("amsdk", "true"); IdRepoListener lter = new IdRepoListener(); lter.setConfigMap(listenerConfig); pluginClass.addListener(getAdminToken(), lter); } return pluginClass; } /** * Notification for global config changes to IdRepoService */ public void globalConfigChanged(String serviceName, String version, String groupName, String serviceComponent, int type) { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache: Global Config changed called " + "ServiceName: " + serviceName + " groupName: " + groupName + " serviceComp: " + serviceComponent + " Type: " + type); } if (serviceComponent.equals("") || serviceComponent.equals("/")) { return; } // FIXME: Clients don't have to call this !! if (!serviceComponent.startsWith("/users/") && !serviceComponent.startsWith("/roles/")) { if (type != 1) { clearIdRepoPluginsCache(); } } else { // Special identities have changed, clear the cache ((IdServicesImpl) IdServicesImpl.getInstance()) .clearSpecialIdentityCache(); } } /** * Notification for organization config changes to IdRepoService */ public void organizationConfigChanged(String serviceName, String version, String orgName, String groupName, String serviceComponent, int type) { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache: Org Config changed called " + "ServiceName: " + serviceName + " orgName: " + orgName + " groupName: " + groupName + " serviceComp: " + serviceComponent + " Type: " + type); } // ignore componenet is "". // if component is /" and type is 2(delete), need to remove hidden // plugins. if ((type == ServiceListener.REMOVED) && (serviceComponent.length() == 0)) { // Organization has been deleted removeIdRepo(orgName); } else if ((serviceComponent.length() != 0) && !serviceComponent.equals("/") && !serviceComponent.equals("")) { // IdRepo has been either added, removed or modified String idRepoName = null; StringTokenizer st = new StringTokenizer(serviceComponent, "/"); if (st.hasMoreTokens()) { idRepoName = st.nextToken(); } try { if (type == ServiceListener.ADDED) { addIdRepo(orgName, idRepoName); } else if (type == ServiceListener.MODIFIED) { if (!IdServicesImpl.isShutdownCalled()) { // Reinitialize the plugin after shutdown removeIdRepo(orgName, idRepoName, true); } else { removeIdRepo(orgName, idRepoName, false); } } else if (type == ServiceListener.REMOVED) { removeIdRepo(orgName, idRepoName, false); } } catch (Exception e) { debug.error("IdRepoPluginsCached.organizationConfigChanged " + "ServiceName: " + serviceName + " orgName: " + orgName + " groupName: " + groupName + " serviceComp: " + serviceComponent + " Type: " + type, e); } } } /** * Notification for schema changes to IdRepoService */ public void schemaChanged(String serviceName, String version) { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache: Schema changed called" + " Service name: " + serviceName); } clearIdRepoPluginsCache(); } // Timer task to shutdown IdRepo plugins private class ShutdownIdRepoPlugin implements Runnable { IdRepo plugin; Map idrepos; public ShutdownIdRepoPlugin(IdRepo plugin) { this.plugin = plugin; } public ShutdownIdRepoPlugin(Map idrepos) { this.idrepos = idrepos; } public void run() { // Shutdown the repo try { // Provide a delay of 500ms for caller operations // to complete Thread.sleep(500); } catch (InterruptedException e) { if (debug.messageEnabled()) { debug.message("IdRepoPluginsCache.ShutdownIdRepoPlugin: " + e ); } } if (plugin != null) { plugin.removeListener(); plugin.shutdown(); plugin = null; } if (idrepos != null && !idrepos.isEmpty()) { // Iterate through the plugins for (Iterator items = idrepos.keySet().iterator(); items.hasNext();) { String name = items.next().toString(); IdRepo repo = (IdRepo) idrepos.get(name); // Shutting down idrepo repo.removeListener(); repo.shutdown(); } idrepos = null; } } } }