You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2011/10/15 00:36:15 UTC

svn commit: r1183537 [11/11] - in /directory/apacheds/trunk/interceptors: admin/ admin/.settings/ authn/ authn/.settings/ authz/.settings/ changelog/ changelog/src/ changelog/src/main/ changelog/src/main/java/ changelog/src/main/java/org/ changelog/src...

Added: directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerInterceptor.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerInterceptor.java?rev=1183537&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerInterceptor.java (added)
+++ directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerInterceptor.java Fri Oct 14 22:36:08 2011
@@ -0,0 +1,615 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+
+package org.apache.directory.server.core.trigger;
+
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.entry.ClonedServerEntry;
+import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
+import org.apache.directory.server.core.api.interceptor.InterceptorChain;
+import org.apache.directory.server.core.api.interceptor.NextInterceptor;
+import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.OperationContext;
+import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
+import org.apache.directory.server.core.api.partition.ByPassConstants;
+import org.apache.directory.server.core.shared.sp.StoredProcEngine;
+import org.apache.directory.server.core.shared.sp.StoredProcEngineConfig;
+import org.apache.directory.server.core.shared.sp.StoredProcExecutionManager;
+import org.apache.directory.server.core.shared.sp.java.JavaStoredProcEngineConfig;
+import org.apache.directory.server.core.subtree.SubentryInterceptor;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Value;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationErrorException;
+import org.apache.directory.shared.ldap.model.exception.LdapOtherException;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.ldap.model.name.Rdn;
+import org.apache.directory.shared.ldap.model.schema.NormalizerMappingResolver;
+import org.apache.directory.shared.ldap.model.schema.normalizers.OidNormalizer;
+import org.apache.directory.shared.ldap.trigger.ActionTime;
+import org.apache.directory.shared.ldap.trigger.LdapOperation;
+import org.apache.directory.shared.ldap.trigger.TriggerSpecification;
+import org.apache.directory.shared.ldap.trigger.TriggerSpecification.SPSpec;
+import org.apache.directory.shared.ldap.trigger.TriggerSpecificationParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The Trigger Service based on the Trigger Specification.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class TriggerInterceptor extends BaseInterceptor
+{
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( TriggerInterceptor.class );
+
+    /** the entry trigger attribute string: entryTrigger */
+    private static final String ENTRY_TRIGGER_ATTR = "entryTriggerSpecification";
+
+    /** a triggerSpecCache that responds to add, delete, and modify attempts */
+    private TriggerSpecCache triggerSpecCache;
+
+    /** a normalizing Trigger Specification parser */
+    private TriggerSpecificationParser triggerParser;
+
+    /** */
+    private InterceptorChain chain;
+
+    /** whether or not this interceptor is activated */
+    private boolean enabled = true;
+
+    /** a Trigger Execution Authorizer */
+    private TriggerExecutionAuthorizer triggerExecutionAuthorizer = new SimpleTriggerExecutionAuthorizer();
+
+    private StoredProcExecutionManager manager;
+    
+    /**
+     * Adds prescriptiveTrigger TriggerSpecificaitons to a collection of
+     * TriggerSpeficaitions by accessing the triggerSpecCache.  The trigger
+     * specification cache is accessed for each trigger subentry associated
+     * with the entry.
+     * Note that subentries are handled differently: their parent, the administrative
+     * entry is accessed to determine the perscriptiveTriggers effecting the AP
+     * and hence the subentry which is considered to be in the same context.
+     *
+     * @param triggerSpecs the collection of trigger specifications to add to
+     * @param dn the normalized distinguished name of the entry
+     * @param entry the target entry that is considered as the trigger source
+     * @throws Exception if there are problems accessing attribute values
+     * @param proxy the partition nexus proxy 
+     */
+    private void addPrescriptiveTriggerSpecs( OperationContext opContext, List<TriggerSpecification> triggerSpecs,
+        Dn dn, Entry entry ) throws LdapException
+    {
+
+        /*
+         * If the protected entry is a subentry, then the entry being evaluated
+         * for perscriptiveTriggerss is in fact the administrative entry.  By
+         * substituting the administrative entry for the actual subentry the
+         * code below this "if" statement correctly evaluates the effects of
+         * perscriptiveTrigger on the subentry.  Basically subentries are considered
+         * to be in the same naming context as their access point so the subentries
+         * effecting their parent entry applies to them as well.
+         */
+        if ( entry.contains( OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
+        {
+            Dn parentDn = dn.getParent();
+
+            entry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+        }
+
+        Attribute subentries = entry.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT );
+
+        if ( subentries == null )
+        {
+            return;
+        }
+
+        for ( Value<?> value : subentries )
+        {
+            String subentryDn = value.getString();
+            triggerSpecs.addAll( triggerSpecCache.getSubentryTriggerSpecs( subentryDn ) );
+        }
+    }
+
+
+    /**
+     * Adds the set of entryTriggers to a collection of trigger specifications.
+     * The entryTrigger is parsed and tuples are generated on they fly then
+     * added to the collection.
+     *
+     * @param triggerSpecs the collection of trigger specifications to add to
+     * @param entry the target entry that is considered as the trigger source
+     * @throws Exception if there are problems accessing attribute values
+     */
+    private void addEntryTriggerSpecs( List<TriggerSpecification> triggerSpecs, Entry entry ) throws LdapException
+    {
+        Attribute entryTrigger = entry.get( ENTRY_TRIGGER_ATTR );
+
+        if ( entryTrigger == null )
+        {
+            return;
+        }
+
+        for ( Value<?> value : entryTrigger )
+        {
+            String triggerString = value.getString();
+            TriggerSpecification item;
+
+            try
+            {
+                item = triggerParser.parse( triggerString );
+            }
+            catch ( ParseException e )
+            {
+                String msg = I18n.err( I18n.ERR_72, triggerString );
+                LOG.error( msg, e );
+                throw new LdapOperationErrorException( msg );
+            }
+
+            triggerSpecs.add( item );
+        }
+    }
+
+
+    /**
+     * Return a selection of trigger specifications for a certain type of trigger action time.
+     * 
+     * @note This method serves as an extion point for new Action Time types.
+     * 
+     * @param triggerSpecs the trigger specifications
+     * @param ldapOperation the ldap operation being performed
+     * @return the set of trigger specs for a trigger action 
+     */
+    public Map<ActionTime, List<TriggerSpecification>> getActionTimeMappedTriggerSpecsForOperation(
+        List<TriggerSpecification> triggerSpecs, LdapOperation ldapOperation )
+    {
+        List<TriggerSpecification> afterTriggerSpecs = new ArrayList<TriggerSpecification>();
+        Map<ActionTime, List<TriggerSpecification>> triggerSpecMap = new HashMap<ActionTime, List<TriggerSpecification>>();
+
+        for ( TriggerSpecification triggerSpec : triggerSpecs )
+        {
+            if ( triggerSpec.getLdapOperation().equals( ldapOperation ) )
+            {
+                if ( triggerSpec.getActionTime().equals( ActionTime.AFTER ) )
+                {
+                    afterTriggerSpecs.add( triggerSpec );
+                }
+                else
+                {
+
+                }
+            }
+        }
+
+        triggerSpecMap.put( ActionTime.AFTER, afterTriggerSpecs );
+
+        return triggerSpecMap;
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Interceptor Overrides
+    ////////////////////////////////////////////////////////////////////////////
+
+    public void init( DirectoryService directoryService ) throws LdapException
+    {
+        super.init( directoryService );
+
+        triggerSpecCache = new TriggerSpecCache( directoryService );
+
+        triggerParser = new TriggerSpecificationParser( new NormalizerMappingResolver()
+        {
+            public Map<String, OidNormalizer> getNormalizerMapping() throws Exception
+            {
+                return schemaManager.getNormalizerMapping();
+            }
+        } );
+
+        chain = directoryService.getInterceptorChain();
+
+        //StoredProcEngineConfig javaxScriptSPEngineConfig = new JavaxStoredProcEngineConfig();
+        StoredProcEngineConfig javaSPEngineConfig = new JavaStoredProcEngineConfig();
+        List<StoredProcEngineConfig> spEngineConfigs = new ArrayList<StoredProcEngineConfig>();
+        //spEngineConfigs.add( javaxScriptSPEngineConfig );
+        spEngineConfigs.add( javaSPEngineConfig );
+        String spContainer = "ou=Stored Procedures,ou=system";
+        manager = new StoredProcExecutionManager( spContainer, spEngineConfigs );
+
+        this.enabled = true; // TODO: Get this from the configuration if needed.
+    }
+
+
+    public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException
+    {
+        Dn name = addContext.getDn();
+        Entry entry = addContext.getEntry();
+
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.add( addContext );
+            return;
+        }
+
+        // Gather supplementary data.
+        StoredProcedureParameterInjector injector = new AddStoredProcedureParameterInjector( addContext, name, entry );
+
+        // Gather Trigger Specifications which apply to the entry being added.
+        List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( addContext, triggerSpecs, name, entry );
+
+        /**
+         *  NOTE: We do not handle entryTriggerSpecs for ADD operation.
+         */
+
+        Map<ActionTime, List<TriggerSpecification>> triggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            triggerSpecs, LdapOperation.ADD );
+
+        next.add( addContext );
+        triggerSpecCache.subentryAdded( name, entry );
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
+        executeTriggers( addContext, afterTriggerSpecs, injector );
+    }
+
+
+    public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws LdapException
+    {
+        Dn name = deleteContext.getDn();
+
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.delete( deleteContext );
+            return;
+        }
+
+        // Gather supplementary data.
+        Entry deletedEntry = deleteContext.getEntry();
+
+        StoredProcedureParameterInjector injector = new DeleteStoredProcedureParameterInjector( deleteContext, name );
+
+        // Gather Trigger Specifications which apply to the entry being deleted.
+        List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( deleteContext, triggerSpecs, name, deletedEntry );
+        addEntryTriggerSpecs( triggerSpecs, deletedEntry );
+
+        Map<ActionTime, List<TriggerSpecification>> triggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            triggerSpecs, LdapOperation.DELETE );
+
+        next.delete( deleteContext );
+
+        triggerSpecCache.subentryDeleted( name, deletedEntry );
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
+        executeTriggers( deleteContext, afterTriggerSpecs, injector );
+    }
+
+
+    public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException
+    {
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.modify( modifyContext );
+            return;
+        }
+
+        Dn normName = modifyContext.getDn();
+
+        // Gather supplementary data.
+        Entry originalEntry = modifyContext.getEntry();
+
+        StoredProcedureParameterInjector injector = new ModifyStoredProcedureParameterInjector( modifyContext );
+
+        // Gather Trigger Specifications which apply to the entry being modified.
+        List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( modifyContext, triggerSpecs, normName, originalEntry );
+        addEntryTriggerSpecs( triggerSpecs, originalEntry );
+
+        Map<ActionTime, List<TriggerSpecification>> triggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            triggerSpecs, LdapOperation.MODIFY );
+
+        next.modify( modifyContext );
+
+        triggerSpecCache.subentryModified( modifyContext, originalEntry );
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
+        executeTriggers( modifyContext, afterTriggerSpecs, injector );
+    }
+
+
+    public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws LdapException
+    {
+        Dn name = renameContext.getDn();
+        Rdn newRdn = renameContext.getNewRdn();
+        boolean deleteOldRn = renameContext.getDeleteOldRdn();
+
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.rename( renameContext );
+            return;
+        }
+
+        // Gather supplementary data.        
+        Entry renamedEntry = ((ClonedServerEntry)renameContext.getEntry()).getClonedEntry();
+
+        // @TODO : To be completely reviewed !!!
+        Rdn oldRdn = name.getRdn();
+        Dn oldSuperiorDn = name.getParent();
+        Dn newSuperiorDn = oldSuperiorDn;
+        Dn oldDn = name;
+        Dn newDn = name;
+        newDn = newDn.add( newRdn );
+
+        StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector( renameContext,
+            deleteOldRn, oldRdn, newRdn, oldSuperiorDn, newSuperiorDn, oldDn, newDn);
+
+        // Gather Trigger Specifications which apply to the entry being renamed.
+        List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( renameContext, triggerSpecs, name, renamedEntry );
+        addEntryTriggerSpecs( triggerSpecs, renamedEntry );
+
+        Map<ActionTime, List<TriggerSpecification>> triggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            triggerSpecs, LdapOperation.MODIFYDN_RENAME );
+
+        next.rename( renameContext );
+        triggerSpecCache.subentryRenamed( name, newDn);
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
+        executeTriggers( renameContext, afterTriggerSpecs, injector );
+    }
+
+
+    public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
+    {
+        Dn oldDn = moveAndRenameContext.getDn();
+        Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
+        Rdn newRdn = moveAndRenameContext.getNewRdn();
+        boolean deleteOldRn = moveAndRenameContext.getDeleteOldRdn();
+
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.moveAndRename( moveAndRenameContext );
+            return;
+        }
+
+        // Gather supplementary data.        
+        Entry movedEntry = moveAndRenameContext.getOriginalEntry();
+
+        Rdn oldRdn = oldDn.getRdn();
+        Dn oldSuperiorDn = oldDn.getParent();
+        Dn oldDN = oldDn;
+        Dn newDn = moveAndRenameContext.getNewDn();
+
+        StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector( moveAndRenameContext,
+            deleteOldRn, oldRdn, newRdn, oldSuperiorDn, newSuperiorDn, oldDN, newDn);
+
+        // Gather Trigger Specifications which apply to the entry being exported.
+        List<TriggerSpecification> exportTriggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( moveAndRenameContext, exportTriggerSpecs, oldDn, movedEntry );
+        addEntryTriggerSpecs( exportTriggerSpecs, movedEntry );
+
+        // Get the entry again without operational attributes
+        // because access control subentry operational attributes
+        // will not be valid at the new location.
+        // This will certainly be fixed by the SubentryInterceptor,
+        // but after this service.
+        Entry importedEntry = moveAndRenameContext.lookup( oldDn, ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+        // As the target entry does not exist yet and so
+        // its subentry operational attributes are not there,
+        // we need to construct an entry to represent it
+        // at least with minimal requirements which are object class
+        // and access control subentry operational attributes.
+        SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+            .getSimpleName() );
+        Entry fakeImportedEntry = subentryInterceptor.getSubentryAttributes(newDn, importedEntry );
+
+        for ( Attribute attribute : importedEntry )
+        {
+            fakeImportedEntry.put( attribute );
+        }
+
+        // Gather Trigger Specifications which apply to the entry being imported.
+        // Note: Entry Trigger Specifications are not valid for Import.
+        List<TriggerSpecification> importTriggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( moveAndRenameContext, importTriggerSpecs, newDn, fakeImportedEntry );
+
+        Map<ActionTime, List<TriggerSpecification>> exportTriggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            exportTriggerSpecs, LdapOperation.MODIFYDN_EXPORT );
+
+        Map<ActionTime, List<TriggerSpecification>> importTriggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            importTriggerSpecs, LdapOperation.MODIFYDN_IMPORT );
+
+        next.moveAndRename( moveAndRenameContext );
+        triggerSpecCache.subentryRenamed( oldDN, newDn);
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterExportTriggerSpecs = exportTriggerMap.get( ActionTime.AFTER );
+        List<TriggerSpecification> afterImportTriggerSpecs = importTriggerMap.get( ActionTime.AFTER );
+        executeTriggers( moveAndRenameContext, afterExportTriggerSpecs, injector );
+        executeTriggers( moveAndRenameContext, afterImportTriggerSpecs, injector );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void move( NextInterceptor next, MoveOperationContext moveContext ) throws LdapException
+    {
+        // Bypass trigger handling if the service is disabled.
+        if ( !enabled )
+        {
+            next.move( moveContext );
+            return;
+        }
+
+        Rdn rdn = moveContext.getRdn();
+        Dn dn = moveContext.getDn();
+        Dn newDn = moveContext.getNewDn();
+        Dn oldSuperior = moveContext.getOldSuperior();
+        Dn newSuperior = moveContext.getNewSuperior();
+
+        // Gather supplementary data.        
+        Entry movedEntry = moveContext.getOriginalEntry();
+
+        //Rdn newRDN = dn.getRdn();
+
+        StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector( moveContext, false,
+            rdn, rdn, oldSuperior, newSuperior, dn, newDn );
+
+        // Gather Trigger Specifications which apply to the entry being exported.
+        List<TriggerSpecification> exportTriggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( moveContext, exportTriggerSpecs, dn, movedEntry );
+        addEntryTriggerSpecs( exportTriggerSpecs, movedEntry );
+
+        // Get the entry again without operational attributes
+        // because access control subentry operational attributes
+        // will not be valid at the new location.
+        // This will certainly be fixed by the SubentryInterceptor,
+        // but after this service.
+        Entry importedEntry = moveContext.lookup( dn, ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+        // As the target entry does not exist yet and so
+        // its subentry operational attributes are not there,
+        // we need to construct an entry to represent it
+        // at least with minimal requirements which are object class
+        // and access control subentry operational attributes.
+        SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+            .getSimpleName() );
+        Entry fakeImportedEntry = subentryInterceptor.getSubentryAttributes( newDn, importedEntry );
+
+        for ( Attribute attribute : importedEntry )
+        {
+            fakeImportedEntry.put( attribute );
+        }
+
+        // Gather Trigger Specifications which apply to the entry being imported.
+        // Note: Entry Trigger Specifications are not valid for Import.
+        List<TriggerSpecification> importTriggerSpecs = new ArrayList<TriggerSpecification>();
+        addPrescriptiveTriggerSpecs( moveContext, importTriggerSpecs, newDn, fakeImportedEntry );
+
+        Map<ActionTime, List<TriggerSpecification>> exportTriggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            exportTriggerSpecs, LdapOperation.MODIFYDN_EXPORT );
+
+        Map<ActionTime, List<TriggerSpecification>> importTriggerMap = getActionTimeMappedTriggerSpecsForOperation(
+            importTriggerSpecs, LdapOperation.MODIFYDN_IMPORT );
+
+        next.move( moveContext );
+        triggerSpecCache.subentryRenamed( dn, newDn );
+
+        // Fire AFTER Triggers.
+        List<TriggerSpecification> afterExportTriggerSpecs = exportTriggerMap.get( ActionTime.AFTER );
+        List<TriggerSpecification> afterImportTriggerSpecs = importTriggerMap.get( ActionTime.AFTER );
+        executeTriggers( moveContext, afterExportTriggerSpecs, injector );
+        executeTriggers( moveContext, afterImportTriggerSpecs, injector );
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Utility Methods
+    ////////////////////////////////////////////////////////////////////////////
+
+    private Object executeTriggers( OperationContext opContext, List<TriggerSpecification> triggerSpecs,
+        StoredProcedureParameterInjector injector ) throws LdapException
+    {
+        Object result = null;
+
+        for ( TriggerSpecification triggerSpec : triggerSpecs )
+        {
+            // TODO: Replace the Authorization Code with a REAL one.
+            if ( triggerExecutionAuthorizer.hasPermission( opContext ) )
+            {
+                /**
+                 * If there is only one Trigger to be executed, this assignment
+                 * will make sense (as in INSTEADOF search Triggers).
+                 */
+                result = executeTrigger( opContext, triggerSpec, injector );
+            }
+        }
+
+        /**
+         * If only one Trigger has been executed, returning its result
+         * can make sense (as in INSTEADOF Search Triggers).
+         */
+        return result;
+    }
+
+
+    private Object executeTrigger( OperationContext opContext, TriggerSpecification tsec,
+        StoredProcedureParameterInjector injector ) throws LdapException
+    {
+        List<Object> returnValues = new ArrayList<Object>();
+        List<SPSpec> spSpecs = tsec.getSPSpecs();
+        
+        for ( SPSpec spSpec : spSpecs )
+        {
+            List<Object> arguments = new ArrayList<Object>();
+            arguments.addAll( injector.getArgumentsToInject( opContext, spSpec.getParameters() ) );
+            Object[] values = arguments.toArray();
+            Object returnValue = executeProcedure( opContext, spSpec.getName(), values );
+            returnValues.add( returnValue );
+        }
+
+        return returnValues;
+    }
+
+
+    private Object executeProcedure( OperationContext opContext, String procedure, Object[] values ) throws LdapException
+    {
+        try
+        {
+            Entry spUnit = manager.findStoredProcUnit( opContext.getSession(), procedure );
+            StoredProcEngine engine = manager.getStoredProcEngineInstance( spUnit );
+            
+            return engine.invokeProcedure( opContext.getSession(), procedure, values );
+        }
+        catch ( Exception e )
+        {
+            LdapOtherException lne = new LdapOtherException( e.getMessage(), e );
+            lne.initCause( e );
+            throw lne;
+        }
+    }
+}

Added: directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerSpecCache.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerSpecCache.java?rev=1183537&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerSpecCache.java (added)
+++ directory/apacheds/trunk/interceptors/trigger/src/main/java/org/apache/directory/server/core/trigger/TriggerSpecCache.java Fri Oct 14 22:36:08 2011
@@ -0,0 +1,262 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.apache.directory.server.core.trigger;
+
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.directory.SearchControls;
+
+import org.apache.directory.server.constants.ApacheSchemaConstants;
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.shared.DefaultCoreSession;
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.LdapPrincipal;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.directory.server.core.api.partition.PartitionNexus;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.shared.ldap.model.constants.AuthenticationLevel;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Modification;
+import org.apache.directory.shared.ldap.model.entry.StringValue;
+import org.apache.directory.shared.ldap.model.entry.Value;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationException;
+import org.apache.directory.shared.ldap.model.filter.EqualityNode;
+import org.apache.directory.shared.ldap.model.filter.ExprNode;
+import org.apache.directory.shared.ldap.model.message.AliasDerefMode;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.ldap.model.schema.AttributeType;
+import org.apache.directory.shared.ldap.model.schema.NormalizerMappingResolver;
+import org.apache.directory.shared.ldap.model.schema.SchemaManager;
+import org.apache.directory.shared.ldap.model.schema.normalizers.OidNormalizer;
+import org.apache.directory.shared.ldap.trigger.TriggerSpecification;
+import org.apache.directory.shared.ldap.trigger.TriggerSpecificationParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A cache for Trigger Specifications which responds to specific events to
+ * perform cache house keeping as trigger subentries are added, deleted
+ * and modified.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class TriggerSpecCache
+{
+    /** the attribute id for prescriptive trigger: prescriptiveTrigger */
+    private static final String PRESCRIPTIVE_TRIGGER_ATTR = "prescriptiveTriggerSpecification";
+
+    /** the logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( TriggerSpecCache.class );
+
+    /** a map of strings to TriggerSpecification collections */
+    private final Map<String, List<TriggerSpecification>> triggerSpecs = new HashMap<String, List<TriggerSpecification>>();
+    /** a handle on the partition nexus */
+    private final PartitionNexus nexus;
+    /** a normalizing TriggerSpecification parser */
+    private final TriggerSpecificationParser triggerSpecParser;
+
+
+    /**
+     * Creates a TriggerSpecification cache.
+     *
+     * @param directoryService the directory service core
+     * @throws LdapException with problems initializing cache
+     */
+    public TriggerSpecCache( DirectoryService directoryService ) throws LdapException
+    {
+        this.nexus = directoryService.getPartitionNexus();
+        final SchemaManager schemaManager = directoryService.getSchemaManager();
+
+        triggerSpecParser = new TriggerSpecificationParser( new NormalizerMappingResolver()
+            {
+                public Map<String, OidNormalizer> getNormalizerMapping() throws Exception
+                {
+                    return schemaManager.getNormalizerMapping();
+                }
+            });
+
+        initialize( directoryService );
+    }
+
+
+    private void initialize( DirectoryService directoryService ) throws LdapException
+    {
+        // search all naming contexts for trigger subentenries
+        // generate TriggerSpecification arrays for each subentry
+        // add that subentry to the hash
+        Set<String> suffixes = nexus.listSuffixes();
+
+        AttributeType objectClassAt = directoryService.getSchemaManager().
+            getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
+
+        for ( String suffix:suffixes )
+        {
+            Dn baseDn = directoryService.getDnFactory().create( suffix );
+            ExprNode filter = new EqualityNode<String>( objectClassAt,
+                    new StringValue( ApacheSchemaConstants.TRIGGER_EXECUTION_SUBENTRY_OC ) );
+            SearchControls ctls = new SearchControls();
+            ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+
+            Dn adminDn = directoryService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
+            CoreSession adminSession = new DefaultCoreSession(
+                new LdapPrincipal( directoryService.getSchemaManager(), adminDn, AuthenticationLevel.STRONG ), directoryService );
+
+            SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, baseDn,
+                filter, ctls );
+            searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
+
+            EntryFilteringCursor results = nexus.search( searchOperationContext );
+
+            try
+            {
+                while ( results.next() )
+                {
+                    Entry resultEntry = results.get();
+                    Dn subentryDn = resultEntry.getDn();
+                    Attribute triggerSpec = resultEntry.get( PRESCRIPTIVE_TRIGGER_ATTR );
+
+                    if ( triggerSpec == null )
+                    {
+                        LOG.warn( "Found triggerExecutionSubentry '" + subentryDn + "' without any " + PRESCRIPTIVE_TRIGGER_ATTR );
+                        continue;
+                    }
+
+                    Dn normSubentryName = subentryDn.apply( directoryService.getSchemaManager() );
+                    subentryAdded( normSubentryName, resultEntry );
+                }
+
+                results.close();
+            }
+            catch ( Exception e )
+            {
+                throw new LdapOperationException( e.getMessage(), e );
+            }
+        }
+    }
+
+
+    private boolean hasPrescriptiveTrigger( Entry entry ) throws LdapException
+    {
+        // only do something if the entry contains prescriptiveTrigger
+        Attribute triggerSpec = entry.get( PRESCRIPTIVE_TRIGGER_ATTR );
+
+        return triggerSpec != null;
+    }
+
+
+    public void subentryAdded( Dn normName, Entry entry ) throws LdapException
+    {
+        // only do something if the entry contains prescriptiveTrigger
+        Attribute triggerSpec = entry.get( PRESCRIPTIVE_TRIGGER_ATTR );
+
+        if ( triggerSpec == null )
+        {
+            return;
+        }
+
+        List<TriggerSpecification> subentryTriggerSpecs = new ArrayList<TriggerSpecification>();
+
+        for ( Value<?> value:triggerSpec )
+        {
+            TriggerSpecification item = null;
+
+            try
+            {
+                item = triggerSpecParser.parse( value.getString() );
+                subentryTriggerSpecs.add( item );
+            }
+            catch ( ParseException e )
+            {
+                String msg = I18n.err( I18n.ERR_73, item );
+                LOG.error( msg, e );
+            }
+
+        }
+
+        triggerSpecs.put( normName.getNormName(), subentryTriggerSpecs );
+    }
+
+
+    public void subentryDeleted( Dn normName, Entry entry ) throws LdapException
+    {
+        if ( !hasPrescriptiveTrigger( entry ) )
+        {
+            return;
+        }
+
+        triggerSpecs.remove( normName.toString() );
+    }
+
+
+    public void subentryModified( ModifyOperationContext opContext, Entry entry ) throws LdapException
+    {
+        if ( !hasPrescriptiveTrigger( entry ) )
+        {
+            return;
+        }
+
+        Dn normName = opContext.getDn();
+        List<Modification> mods = opContext.getModItems();
+
+        boolean isTriggerSpecModified = false;
+
+        for ( Modification mod : mods )
+        {
+            isTriggerSpecModified |= mod.getAttribute().contains( PRESCRIPTIVE_TRIGGER_ATTR );
+        }
+
+        if ( isTriggerSpecModified )
+        {
+            subentryDeleted( normName, entry );
+            subentryAdded( normName, entry );
+        }
+    }
+
+
+    public List<TriggerSpecification> getSubentryTriggerSpecs( String subentryDn )
+    {
+        List<TriggerSpecification> subentryTriggerSpecs = triggerSpecs.get( subentryDn );
+        if ( subentryTriggerSpecs == null )
+        {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList( subentryTriggerSpecs );
+    }
+
+
+    public void subentryRenamed( Dn oldName, Dn newName )
+    {
+        triggerSpecs.put( newName.getNormName(), triggerSpecs.remove( oldName.getNormName() ) );
+    }
+}