001/* 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: EventService.java,v 1.19 2009/09/28 21:47:33 ww203982 Exp $ 026 * 027 * Portions Copyrighted 2010-2017 ForgeRock AS. 028 */ 029 030package com.iplanet.services.ldap.event; 031 032import java.math.BigInteger; 033import java.security.AccessController; 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Map; 040import java.util.Random; 041import java.util.Set; 042 043import com.iplanet.am.sdk.ldap.ACIEventListener; 044import com.iplanet.am.sdk.ldap.EntryEventListener; 045import com.iplanet.am.util.SystemProperties; 046import com.iplanet.services.ldap.DSConfigMgr; 047import com.iplanet.services.ldap.LDAPServiceException; 048import com.iplanet.services.ldap.LDAPUser; 049import com.iplanet.services.util.I18n; 050import com.iplanet.sso.SSOException; 051import com.iplanet.ums.IUMSConstants; 052import com.sun.identity.idm.IdConstants; 053import com.sun.identity.security.AdminTokenAction; 054import com.sun.identity.shared.Constants; 055import com.sun.identity.shared.debug.Debug; 056import com.sun.identity.sm.SMSException; 057import com.sun.identity.sm.ServiceSchema; 058import com.sun.identity.sm.ServiceSchemaManager; 059import com.sun.identity.sm.ldap.LDAPEventManager; 060import org.forgerock.opendj.ldap.ConnectionFactory; 061import org.forgerock.opendj.ldap.DN; 062import org.forgerock.opendj.ldap.Entry; 063import org.forgerock.opendj.ldap.Filter; 064import org.forgerock.opendj.ldap.LdapException; 065import org.forgerock.opendj.ldap.SearchScope; 066import org.forgerock.opendj.ldap.controls.PersistentSearchChangeType; 067import org.forgerock.opendj.ldap.responses.SearchResultEntry; 068import org.forgerock.util.thread.listener.ShutdownListener; 069import org.forgerock.util.thread.listener.ShutdownManager; 070 071/** 072 * 073 * 074 * @supported.api 075 */ 076public class EventService { 077 078 private static Debug logger = Debug.getInstance("amEventService"); 079 private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG); 080 private static DSConfigMgr cm = null; 081 private static final String EVENT_CONNECTION_RETRY_INTERVAL = 082 "com.iplanet.am.event.connection.delay.between.retries"; 083 private static final int RETRY_INTERVAL = SystemProperties.getAsInt(EVENT_CONNECTION_RETRY_INTERVAL, 3000); 084 private static final String EVENT_LISTENER_DISABLE_LIST = "com.sun.am.event.connection.disable.list"; 085 private static final Class<? extends IDSEventListener> ACI_EVENT_LISTENER_CLASS_NAME = ACIEventListener.class; 086 private static final Class<? extends IDSEventListener> ENTRY_EVENT_LISTENER_CLASS_NAME = EntryEventListener.class; 087 private static final Class<? extends IDSEventListener> LDAP_EVENT_LISTENER_CLASS_NAME = LDAPEventManager.class; 088 089 private static volatile boolean isShutdownCalled = false; 090 private static volatile boolean isRunning = false; 091 092 private ConnectionFactory adminConnectionFactory; 093 private ConnectionFactory smsConnectionFactory; 094 private final Map<Class<? extends IDSEventListener>, ListenerSearch> persistentSearches = new HashMap<>(); 095 096 private static final class ListenerSearch { 097 private final IDSEventListener listener; 098 private final EventServicePersistentSearch search; 099 private ListenerSearch(IDSEventListener listener, EventServicePersistentSearch search) { 100 this.listener = listener; 101 this.search = search; 102 } 103 } 104 105 private enum Singleton { 106 INSTANCE; 107 108 private EventService eventService; 109 private EventException eventException; 110 111 Singleton() { 112 try { 113 eventService = new EventService(); 114 ShutdownManager shutdownMan = com.sun.identity.common.ShutdownManager.getInstance(); 115 shutdownMan.addShutdownListener( 116 new ShutdownListener() { 117 public void shutdown() { 118 if (eventService != null) { 119 eventService.stopPSearches(); 120 } 121 } 122 }); 123 } catch (EventException e) { 124 eventException = e; 125 } 126 } 127 128 private EventService getEventService() throws EventException { 129 if (eventException != null) { 130 throw eventException; 131 } 132 return eventService; 133 } 134 } 135 136 private EventService() throws EventException { 137 try { 138 cm = DSConfigMgr.getDSConfigMgr(); 139 } catch (LDAPServiceException lse) { 140 logger.error("EventService.getConfigManager() - Failed to get handle to Configuration Manager", lse); 141 throw new EventException(i18n.getString(IUMSConstants.DSCFG_NOCFGMGR), lse); 142 } 143 } 144 145 /** 146 * 147 * 148 * @supported.api 149 */ 150 public synchronized static EventService getEventService() throws EventException, LdapException { 151 if (isShutdownCalled) { 152 return null; 153 } 154 return Singleton.INSTANCE.getEventService(); 155 } 156 157 //Question: is ok to not actually restart running psearches if the listener is still enabled? 158 public synchronized void restartPSearches() { 159 List<Class<? extends IDSEventListener>> listenersClasses = getEnabledListenersClasses(); 160 161 for (Iterator<Class<? extends IDSEventListener>> iterator = persistentSearches.keySet().iterator(); 162 iterator.hasNext();) { 163 Class<? extends IDSEventListener> pSearchListenerClass = iterator.next(); 164 //remove any running psearches that do not have enabled listeners 165 if (!listenersClasses.contains(pSearchListenerClass)) { 166 persistentSearches.get(pSearchListenerClass).search.stopSearch(); 167 iterator.remove(); 168 } else { 169 listenersClasses.remove(pSearchListenerClass); 170 } 171 } 172 173 for (Iterator<Class<? extends IDSEventListener>> iterator = listenersClasses.iterator(); iterator.hasNext();) { 174 Class<? extends IDSEventListener> listenerClass = iterator.next(); 175 176 try { 177 IDSEventListener listener = listenerClass.newInstance(); 178 179 EventServicePersistentSearch pSearch = new EventServicePersistentSearch(RETRY_INTERVAL, 180 DN.valueOf(listener.getBase()), Filter.valueOf(listener.getFilter()), 181 SearchScope.valueOf(listener.getScope()), getConnectionFactory(listener.getClass()), 182 "objectclass"); 183 184 pSearch.addListener(listener, new BigInteger(130, new Random()).toString()); 185 186 pSearch.startSearch(); 187 persistentSearches.put(listenerClass, new ListenerSearch(listener, pSearch)); 188 logger.message("EventService.restartPSearches() - successfully initialized: {}", listenerClass); 189 iterator.remove(); 190 } catch (Exception e) { 191 logger.error("EventService.restartPSearches() Unable to start listener {}", listenerClass, e); 192 } 193 } 194 195 if (!listenersClasses.isEmpty()) { 196 for (Class<? extends IDSEventListener> listenerClass : listenersClasses) { 197 logger.error("EventService.restartPSearches(): unable add listener: {}", listenerClass); 198 } 199 } 200 isRunning = true; 201 } 202 203 public synchronized void stopPSearches() { 204 isShutdownCalled = true; 205 for (ListenerSearch pSearch : persistentSearches.values()) { 206 pSearch.search.removeListener(pSearch.listener); 207 pSearch.search.stopSearch(); 208 } 209 } 210 211 public static boolean isStarted() { 212 return isRunning && !isShutdownCalled; 213 } 214 215 public IDSEventListener getListener(Class<? extends IDSEventListener> listenerClass) { 216 return persistentSearches.get(listenerClass).listener; 217 } 218 219 private static List<Class<? extends IDSEventListener>> getEnabledListenersClasses() { 220 221 Collection<String> disabledListeners = getDisabledListeners(); 222 boolean disableACI = disabledListeners.contains("aci"); 223 boolean disableUM = disabledListeners.contains("um"); 224 boolean disableSM = disabledListeners.contains("sm"); 225 226 if (!disableUM || !disableACI) { 227 // Check if AMSDK is configured 228 if (!isAMSDKConfigured()) { 229 disableUM = true; 230 disableACI = true; 231 if (logger.messageEnabled()) { 232 logger.message("EventService.getListenerList(): AMSDK is not configured or config time. " 233 + "Disabling UM and ACI event listeners"); 234 } 235 } 236 } 237 238 //psearch terminated if you disable the DB notifications, or add 'sm' to the list of disabled 239 if (!disableSM) { 240 disableSM = !Boolean.parseBoolean(SystemProperties.get(Constants.SMS_ENABLE_DB_NOTIFICATION)); 241 } 242 243 if (logger.messageEnabled()) { 244 logger.message("EventService.getListenerList(): SMS listener is enabled: {}", !disableSM); 245 } 246 247 List<Class<? extends IDSEventListener>> listeners = new ArrayList<>(); 248 // Disable the selected listeners 249 if (!disableACI) { 250 listeners.add(ACI_EVENT_LISTENER_CLASS_NAME); 251 } 252 if (!disableUM) { 253 listeners.add(ENTRY_EVENT_LISTENER_CLASS_NAME); 254 } 255 if (!disableSM) { 256 listeners.add(LDAP_EVENT_LISTENER_CLASS_NAME); 257 } 258 259 if (disableACI && disableUM && disableSM) { 260 logger.message("EventService.getListenerList() - all listeners are disabled, EventService won't start"); 261 } 262 263 return listeners; 264 } 265 266 private ConnectionFactory getConnectionFactory(Class<? extends IDSEventListener> listenerClass) 267 throws LDAPServiceException { 268 if (LDAPEventManager.class.equals(listenerClass) && cm.getServerGroup("sms") != null) { 269 return getSmsConnectionFactory(); 270 } else { 271 return getAdminConnectionFactory(); 272 } 273 } 274 275 private ConnectionFactory getAdminConnectionFactory() throws LDAPServiceException { 276 if (adminConnectionFactory == null) { 277 adminConnectionFactory = DSConfigMgr.getDSConfigMgr().getNewAdminConnectionFactory(); 278 } 279 return adminConnectionFactory; 280 } 281 282 private ConnectionFactory getSmsConnectionFactory() throws LDAPServiceException { 283 if (smsConnectionFactory == null) { 284 smsConnectionFactory = DSConfigMgr.getDSConfigMgr() 285 .getNewConnectionFactory("sms", LDAPUser.Type.AUTH_ADMIN); 286 } 287 return smsConnectionFactory; 288 } 289 290 private void dispatchException(Exception e, String requestId, IDSEventListener listener) { 291 logger.error("EventService.dispatchException() - dispatching exception to the listener: {} Listener: {}", 292 requestId, listener, e); 293 listener.eventError(e.toString()); 294 } 295 296 private void dispatchEvent(DSEvent dirEvent, IDSEventListener listener) { 297 listener.entryChanged(dirEvent); 298 } 299 300 private DSEvent createDSEvent(Entry entry, PersistentSearchChangeType changeType, String requestId, 301 IDSEventListener listener) throws Exception { 302 DSEvent dsEvent = new DSEvent(); 303 304 logger.message("EventService.createDSEvent() - Notifying event to: {}", listener); 305 306 // Get the dn from the entry 307 String dn = entry.getName().toString(); 308 dsEvent.setID(dn); 309 310 // Get information on the type of change made 311 dsEvent.setEventType(changeType.intValue()); 312 313 // Pass the search ID as the event's change info 314 dsEvent.setSearchID(requestId); 315 316 // set the object class name 317 String className = entry.getAttribute("objectclass").toString(); 318 dsEvent.setClassName(className); 319 320 return dsEvent; 321 } 322 323 private static Collection<String> getDisabledListeners() { 324 List<String> disabledListeners = new ArrayList<>(); 325 326 String list = SystemProperties.get(EVENT_LISTENER_DISABLE_LIST, ""); 327 logger.message("EventService.getListenerList(): {}: {}", EVENT_LISTENER_DISABLE_LIST, list); 328 329 for (String disabledListener : list.split(",")) { 330 disabledListeners.add(disabledListener.trim()); 331 } 332 333 return disabledListeners; 334 } 335 336 private static boolean isDuringConfigurationTime() { 337 return Boolean.parseBoolean(SystemProperties.get(Constants.SYS_PROPERTY_INSTALL_TIME)); 338 } 339 340 private static boolean isAMSDKConfigured() { 341 boolean isAMSDKConfigured = false; 342 343 boolean configTime = isDuringConfigurationTime(); 344 logger.message("EventService.getListenerList(): {}: {}", Constants.SYS_PROPERTY_INSTALL_TIME, configTime); 345 if (!configTime) { 346 try { 347 ServiceSchemaManager scm = new ServiceSchemaManager( 348 AccessController.doPrivileged(AdminTokenAction.getInstance()), IdConstants.REPO_SERVICE, "1.0"); 349 ServiceSchema idRepoSubSchema = scm.getOrganizationSchema(); 350 Set idRepoPlugins = idRepoSubSchema.getSubSchemaNames(); 351 if (idRepoPlugins.contains("amSDK")) { 352 isAMSDKConfigured = true; 353 } 354 } catch (SMSException ex) { 355 logger.warning("EventService.getListenerList() - Unable to obtain idrepo service", ex); 356 } catch (SSOException ex) { 357 // Should not happen, ignore the exception 358 } 359 } 360 return isAMSDKConfigured; 361 } 362 363 private final class EventServicePersistentSearch extends LDAPv3PersistentSearch<IDSEventListener, String> { 364 365 private final SearchResultEntryHandler resultEntryHandler = new PSearchResultEntryHandler(); 366 367 public EventServicePersistentSearch(int retryInterval, DN pSearchBaseDN, Filter pSearchFilter, 368 SearchScope pSearchScope, ConnectionFactory factory, String... attributeNames) { 369 super(retryInterval, pSearchBaseDN, pSearchFilter, pSearchScope, factory, attributeNames); 370 } 371 372 @Override 373 protected void clearCaches() { 374 for (IDSEventListener listener : getListeners().keySet()) { 375 listener.allEntriesChanged(); 376 } 377 } 378 379 @Override 380 protected void connectionLost() { 381 //this section intentionally left blank 382 } 383 384 @Override 385 protected void connectionReestablished() { 386 //this section intentionally left blank 387 } 388 389 @Override 390 protected SearchResultEntryHandler getSearchResultEntryHandler() { 391 return resultEntryHandler; 392 } 393 394 private final class PSearchResultEntryHandler implements LDAPv3PersistentSearch.SearchResultEntryHandler { 395 396 private final Exception EXCEPTION = 397 new Exception("EventService - Cannot create NamingEvent, no change control info"); 398 399 @Override 400 public boolean handle(SearchResultEntry entry, String dn, DN previousDn, PersistentSearchChangeType type) { 401 for (Map.Entry<IDSEventListener, String> listener : getListeners().entrySet()) { 402 if (type != null) { 403 logger.message("EventService.processSearchResultMessage() changeCtrl = {}", type.toString()); 404 // Convert control into a DSEvent and dispatch to listeners 405 try { 406 DSEvent event = createDSEvent(entry, type, listener.getValue(), listener.getKey()); 407 dispatchEvent(event, listener.getKey()); 408 } catch (Exception ex) { 409 dispatchException(ex, listener.getValue(), listener.getKey()); 410 } 411 } else { 412 dispatchException(EXCEPTION, listener.getValue(), listener.getKey()); 413 } 414 } 415 return true; 416 } 417 } 418 } 419}