/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005 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: EntryEventListener.java,v 1.4 2009/01/28 05:34:48 ww203982 Exp $
 *
 * Portions Copyright 2015 ForgeRock AS.
 */

package com.iplanet.am.sdk.ldap;

import java.security.AccessController;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.iplanet.am.sdk.AMObjectListener;
import com.iplanet.am.sdk.common.ICachedDirectoryServices;
import com.iplanet.am.sdk.common.IDirectoryServices;
import com.iplanet.services.ldap.event.DSEvent;
import com.iplanet.services.ldap.event.IDSEventListener;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.SMSException;
import com.sun.identity.sm.SchemaType;
import com.sun.identity.sm.ServiceSchema;
import com.sun.identity.sm.ServiceSchemaManager;
import org.forgerock.openam.ldap.LDAPUtils;
import org.forgerock.openam.ldap.PersistentSearchChangeType;
import org.forgerock.opendj.ldap.DN;

/**
 * The <code>AMEntryEventListener</code> handles all the events that are
 * triggered by any modification/deletion/renaming LDAP entires which do not
 * contain ACI's. This class implements the <code>com.iplanet.services
 * .ldap.event.IDSEventListener</code>
 * interface.
 */
public class EntryEventListener implements IDSEventListener {

    // Filter to separate entires which do not contain aci's
    protected static final String SEARCH_FILTER = "(&(objectclass=*)"
            + "(!(|(objectclass=sunService)(objectclass=sunServiceComponent)"
            + "(aci=*))))";

    protected static final int OPERATIONS = PersistentSearchChangeType.ALL_OPERATIONS;

    // Instance variables
    private Debug debug = EventManager.getDebug();

    private SSOToken internalToken;

    private Map listeners = new HashMap();

    public EntryEventListener() {
        try {
            internalToken = (SSOToken) AccessController
                    .doPrivileged(AdminTokenAction.getInstance());
        } catch (Exception le) {
            debug.error("EntryEventListener() Exception occurred while "
                    + "getting SSOToken", le);
        }
    }

    private Set getDynamicAttributeNames(String serviceName) {
        Set attrNames = Collections.EMPTY_SET;
        try {
            ServiceSchemaManager sm = new ServiceSchemaManager(serviceName,
                    internalToken);
            ServiceSchema sschema = sm.getSchema(SchemaType.DYNAMIC);
            attrNames = sschema.getAttributeSchemaNames();
        } catch (SMSException smse) {
            if (debug.warningEnabled()) {
                debug.warning("EntryEventListener.entryChanged(): "
                        + "caught SMSException: ", smse);
            }
        } catch (SSOException ssoe) {
            if (debug.warningEnabled()) {
                debug.error("EntryEventListener.entryChanged(): "
                        + "caught SSOException: ", ssoe);
            }
        }
        return attrNames;
    }

    /**
     * This method will be invoked by the <code>EventService</code> if the
     * events for which this listener registered has been triggered. Since this
     * listener is interested in modifications/deletions/renaming of normal
     * directory entires, it identifies the distinguished names affected by this
     * event and sends a notification to the <code>AMObjectImpl</code> by
     * calling the appropriate method.
     * 
     * @param dsEvent
     *            <code>DSEvent</code> object generated by the
     *            <code>EventService</code>.
     */
    public void entryChanged(DSEvent dsEvent) {
        // Get the "dn" responsible for the event
        DN dn = DN.valueOf(dsEvent.getID());
        String normalizedDN = dn.toString().toLowerCase();

        if (debug.messageEnabled()) {
            debug.message("EntryEventListener.entryChanged(): DSEvent "
                    + "generated for: " + dn);
        }

        // Check if the event was caused by changes/deletions to cos entries
        // (cosdefinitions & costemplates) and figure out the affected dn
        // subtree. Parse the dn in case of cos related events to find out the
        // affected subtree of dns Examples of costemplate dn:
        // "cn="cn=RoleThree,o=hp.com,o=vortex.com",cn=nsCalUser,
        // o=hp.com,o=vortex.com"
        // affectDNs will be all those which suffix match
        // "o=hp.com,o=vortex.com"
        // Examples of cosdefintion dn:
        // "cn=nsCalUser,o=hp.com,o=vortex.com";
        // affectDNs => "o=hp.com,o=vortex.com"
        boolean cosType = true;
        String affectDNs = "";
        Set attrNames = Collections.EMPTY_SET;
        String serviceName = null;
        String objClasses = dsEvent.getClassName();
        if (objClasses.indexOf("cosClassicDefinition") != -1) {// COS
                                                                // definition
            affectDNs = dn.parent().toString().toLowerCase();
            // Get the serviceName this applies to, and get the attribute
            // names of this service which impact the DNs.
            serviceName = LDAPUtils.rdnValueFromDn(dn);
            attrNames = getDynamicAttributeNames(serviceName);
            if (debug.messageEnabled()) {
                debug.message("EntryEventListener.entryChanged() "
                        + "Cos Definition changed for service: " + serviceName
                        + "Dynamic Attributes: " + attrNames);
            }
        } else if (objClasses.indexOf("costemplate") != -1) { // COS template
            affectDNs = dn.parent().parent().toString().toLowerCase();
            serviceName = LDAPUtils.rdnValueFromDn(dn.parent());
            attrNames = getDynamicAttributeNames(serviceName);
            if (debug.messageEnabled()) {
                debug.message("EntryEventListener." + "entryChanged()"
                        + "Cos template changed for service: " + serviceName
                        + "Dynamic Attributes: " + attrNames);
            }
        } else { // Not cos related - only a single dn affected
            cosType = false;
            affectDNs = normalizedDN;
        }
        if (debug.messageEnabled()) {
            debug.message("EntryEventListener.entryChanged(): Affected dn: "
                    + affectDNs + " cosType: " + cosType);
        }

        IDirectoryServices dsServices = DirectoryServicesFactory.getInstance();
        // Call the listeners
        synchronized (listeners) {
            Set keys = listeners.keySet();
            for (Iterator items = keys.iterator(); items.hasNext();) {
                AMObjectListener listener = (AMObjectListener) items.next();
                Map configMap = (Map) listeners.get(listener);
                if (cosType) { // All affected service attributes must be
                    // removed for user entries as well the affected template
                    if (DirectoryServicesFactory.isCachingEnabled()) {
                        ((ICachedDirectoryServices) dsServices).dirtyCache(
                                affectDNs, dsEvent.getEventType(), true, false,
                                attrNames);
                        ((ICachedDirectoryServices) dsServices).dirtyCache(
                                normalizedDN, dsEvent.getEventType(), false,
                                false, Collections.EMPTY_SET);
                    }
                    listener.objectsChanged(affectDNs, dsEvent.getEventType(),
                            attrNames, configMap);
                    // first call removes the attributes. now remove
                    // the template.
                    listener.objectChanged(normalizedDN,
                            dsEvent.getEventType(), configMap);
                } else {
                    if (DirectoryServicesFactory.isCachingEnabled()) {
                        ((ICachedDirectoryServices) dsServices).dirtyCache(
                                affectDNs, dsEvent.getEventType(), false,
                                false, Collections.EMPTY_SET);
                    }
                    listener.objectChanged(affectDNs, dsEvent.getEventType(),
                            configMap);
                }
            }
        }
    }

    /**
     * This method is invoked by the <code>EventService</code> if it
     * encounters an error.
     * 
     * @param errorStr
     *            error string.
     */
    public void eventError(String errorStr) {
        debug.error("EntryEventListener.eventError(): " + errorStr);
    }

    public void allEntriesChanged() {
        debug.error("EntryEventListener: Received all entries changed event"
                + "from event service");
        IDirectoryServices dsServices = DirectoryServicesFactory.getInstance();
        if (DirectoryServicesFactory.isCachingEnabled()) {
            ((ICachedDirectoryServices) dsServices).clearCache();
        }
        // Call the listeners
        synchronized (listeners) {
            Set keys = listeners.keySet();
            for (Iterator items = keys.iterator(); items.hasNext();) {
                AMObjectListener listener = (AMObjectListener) items.next();
                listener.allObjectsChanged();
            }
        }
    }

    public String getBase() {
        return EventManager.EVENT_BASE_NODE;
    }

    public String getFilter() {
        return SEARCH_FILTER;
    }


    public int getOperations() {
        return OPERATIONS;
    }


    public int getScope() {
        return EventManager.EVENT_SCOPE;
    }

    public void setListeners(Map listener) {
        this.listeners = listener;
    }

}
