SMSEventListenerManager.java revision bee2440354b4bc8796e1de0b6cbd60e1f68deba0
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk/*
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk *
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk *
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * The contents of this file are subject to the terms
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * of the Common Development and Distribution License
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * (the License). You may not use this file except in
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * compliance with the License.
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk *
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * You can obtain a copy of the License at
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * https://opensso.dev.java.net/public/CDDLv1.0.html or
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk * opensso/legal/CDDLv1.0.txt
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * See the License for the specific language governing
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk * 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: SMSEventListenerManager.java,v 1.12 2009/01/28 05:35:03 ww203982 Exp $
*
* Portions Copyright 2015 ForgeRock AS.
*/
package com.sun.identity.sm;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.iplanet.sso.SSOToken;
import com.sun.identity.shared.debug.Debug;
import org.forgerock.openam.ldap.LDAPUtils;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.SearchScope;
/**
* Receives notifications for all SMS object changes from
* <class>SMSNotificationManager</class> and dispatches the same to <class>
* CachedSMSEntry</class> and <class>CachedSubEntries</class>. This class
* also handles the case of sending deleted event for recursive deletes.
*/
class SMSEventListenerManager implements SMSObjectListener {
// All Notification Objects list
protected static Map<String, NotificationObject> notificationObjects =
Collections.synchronizedMap(new HashMap<String, NotificationObject>());
// CachedSMSEntry objects
protected static Map<String, Set<?>> nodeChanges =
Collections.synchronizedMap(new HashMap());
// CachedSubEntries objects
protected static Map<String, Set<?>> subNodeChanges =
Collections.synchronizedMap(new HashMap());
// Static Initialization variables
private static Debug debug = SMSEntry.eventDebug;
protected static boolean initialized;
static void initialize(SSOToken token) {
if (!initialized) {
try {
if (SMSNotificationManager.isCacheEnabled()) {
SMSNotificationManager.getInstance()
.registerCallbackHandler(new SMSEventListenerManager());
}
if (debug.messageEnabled()) {
debug.message("Initialized SMS Event listner");
}
initialized = true;
} catch (Exception e) {
debug.error("SMSEventListenerManager::initialize " +
"Unable to intialize SMS listener: " + e);
}
}
}
SMSEventListenerManager() {
// do nothing
}
// Processes object changed notifications
public void objectChanged(String odn, int event) {
objectChanged(odn, event, false);
}
// Processes object changed notifications. The flag isLocal is used
// distingush the self generated DELETE notifications for recursive
// deletes, especially when data store notifications are disabled.
// In which case, delete notifications will never be generated.
private void objectChanged(String odn, int event, boolean isLocal) {
if (debug.messageEnabled()) {
debug.message("SMSEventListener::entry changed for: " + odn +
" type: " + event);
}
// Normalize the DN
DN sdn = DN.valueOf(odn);
String dn = sdn.toString().toLowerCase();
// If event is delete, need to send notifications for sub-entries
// Even if backend datastore notification is enabled, they woould
// arrive much later causing write-through cache issues.
if (!isLocal && (event == SMSObjectListener.DELETE)) {
// Collect the immediate children of the current sdn
// from nodeChanges. All "subNodeChanges" entries would
// have an entry in "nodeChanges", hence donot have to
// iterate throught it
Set childDNs = new HashSet();
synchronized (nodeChanges) {
Iterator keyitems = nodeChanges.keySet().iterator();
while (keyitems.hasNext()) {
String cdn = (String) keyitems.next();
if (DN.valueOf(cdn).isInScopeOf(sdn, SearchScope.SUBORDINATES)) {
childDNs.add(cdn);
}
}
}
// Send the notifications
if (debug.messageEnabled()) {
debug.message("SMSEventListener::objectChanged: Sending " +
"delete event of: " + dn + " to child nodes: " + childDNs);
}
for (Iterator items = childDNs.iterator(); items.hasNext();) {
String item = (String) items.next();
objectChanged(item, event, true);
// Send notifications to external listeners also if
// data store notification is not enabled
if (!SMSNotificationManager.isDataStoreNotificationEnabled()) {
SMSNotificationManager.getInstance().sendNotifications(
item, event, true);
}
}
}
// Send notifications to CachedSMSEntries
sendNotifications((Set) nodeChanges.get(dn), odn, event);
// Process sub-entry changed events, not interested in attribute mods
if ((event == SMSObjectListener.ADD) ||
(event == SMSObjectListener.DELETE)) {
// Send notifications to CachedSubEntries
if (debug.messageEnabled()) {
debug.message("SMSEventListener::entry changed for: " + dn +
" sending notifications to its parents");
}
sendNotifications((Set) subNodeChanges.get(DN.valueOf(dn).parent().toString().toLowerCase()), odn, event);
}
}
public void allObjectsChanged() {
if (debug.messageEnabled()) {
debug.message("SMSEventListenerManager::allObjectsChanged called");
}
// Collect all the DNs from "nodeChanges" and send notifications
Set<String> dns = new HashSet<>();
synchronized (nodeChanges) {
for (String item : nodeChanges.keySet()) {
dns.add(item);
}
}
// Send MODIFY notifications
for (String item : dns) {
objectChanged(item, SMSObjectListener.MODIFY);
}
}
/**
* Registers notification for changes to nodes
*/
protected static String notifyChangesToNode(SSOToken token, String dn,
Method method, Object object, Object[] args) {
initialize(token);
String ndn = DN.valueOf(dn).toString().toLowerCase();
return (addNotificationObject(nodeChanges, ndn, method, object, args));
}
/**
* Registers notification for changes to its sub-nodes
*/
protected static String notifyChangesToSubNodes(SSOToken token, String dn,
Object o) {
initialize(token);
String ndn = DN.valueOf(dn).toString().toLowerCase();
return (addNotificationObject(subNodeChanges, ndn, null, o, null));
}
/**
* Removes notification objects
*/
protected static void removeNotification(String notificationID) {
NotificationObject no = notificationObjects.get(notificationID);
if (no != null) {
no.set.remove(no);
notificationObjects.remove(notificationID);
}
}
/**
* Adds notification method to the map
*/
private static String addNotificationObject(Map<String, Set<?>> nChangesMap, String dn,
Method method, Object object, Object[] args) {
Set nObjects = null;
synchronized (nChangesMap) {
nObjects = (Set) nChangesMap.get(dn);
if (nObjects == null) {
nObjects = Collections.synchronizedSet(new HashSet());
nChangesMap.put(dn, nObjects);
}
}
NotificationObject no = new NotificationObject(method, object, args,
nObjects);
nObjects.add(no);
notificationObjects.put(no.getID(), no);
return (no.getID());
}
/**
* Sends notification to methods and objects within the set
*/
private static void sendNotifications(Set nObjects, String dn, int event) {
if ((nObjects == null) || (nObjects.isEmpty())) {
return;
}
HashSet nobjs = new HashSet(2);
synchronized (nObjects) {
nobjs.addAll(nObjects);
}
Iterator items = nobjs.iterator();
while (items.hasNext()) {
try {
NotificationObject no = (NotificationObject) items.next();
if ((dn != null) && (no.object instanceof CachedSubEntries)) {
CachedSubEntries cse = (CachedSubEntries) no.object;
// We do not cache Realm names.
// We cache only service names and policy names.
if (!dn.startsWith(SMSEntry.ORG_PLACEHOLDER_RDN)) {
if (event == SMSObjectListener.ADD) {
cse.add(LDAPUtils.rdnValueFromDn(dn));
} else {
cse.remove(LDAPUtils.rdnValueFromDn(dn));
}
}
} else {
no.method.invoke(no.object, no.args);
}
} catch (Exception e) {
debug.error("SMSEvent notification: " +
"Unable to send notification: ", e);
}
}
}
private static class NotificationObject {
String id;
Method method;
Object object;
Object[] args;
Set set;
NotificationObject(Method m, Object o, Object[] a, Set s) {
id = SMSUtils.getUniqueID();
method = m;
object = o;
args = a;
set = s;
}
String getID() {
return (id);
}
// @Override
public int hashCode() {
int hash = 3;
hash = 13 * hash + (id != null ? id.hashCode() : 0);
return hash;
}
public boolean equals(Object o) {
if (o instanceof NotificationObject) {
NotificationObject no = (NotificationObject) o;
return (id.equals(no.id));
}
return (false);
}
}
}