001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    
021    package org.apache.directory.server.core.trigger;
022    
023    
024    import java.text.ParseException;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    
030    import org.apache.directory.server.core.DirectoryService;
031    import org.apache.directory.server.core.entry.ClonedServerEntry;
032    import org.apache.directory.server.core.interceptor.BaseInterceptor;
033    import org.apache.directory.server.core.interceptor.InterceptorChain;
034    import org.apache.directory.server.core.interceptor.NextInterceptor;
035    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
036    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
037    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
038    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
039    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
040    import org.apache.directory.server.core.interceptor.context.OperationContext;
041    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
042    import org.apache.directory.server.core.partition.ByPassConstants;
043    import org.apache.directory.server.core.sp.StoredProcEngine;
044    import org.apache.directory.server.core.sp.StoredProcEngineConfig;
045    import org.apache.directory.server.core.sp.StoredProcExecutionManager;
046    import org.apache.directory.server.core.sp.java.JavaStoredProcEngineConfig;
047    import org.apache.directory.server.core.subtree.SubentryInterceptor;
048    import org.apache.directory.server.i18n.I18n;
049    import org.apache.directory.shared.ldap.constants.SchemaConstants;
050    import org.apache.directory.shared.ldap.entry.EntryAttribute;
051    import org.apache.directory.shared.ldap.entry.ServerEntry;
052    import org.apache.directory.shared.ldap.entry.Value;
053    import org.apache.directory.shared.ldap.exception.LdapOperationErrorException;
054    import org.apache.directory.shared.ldap.exception.LdapOtherException;
055    import org.apache.directory.shared.ldap.name.DN;
056    import org.apache.directory.shared.ldap.name.RDN;
057    import org.apache.directory.shared.ldap.schema.NormalizerMappingResolver;
058    import org.apache.directory.shared.ldap.schema.SchemaManager;
059    import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer;
060    import org.apache.directory.shared.ldap.trigger.ActionTime;
061    import org.apache.directory.shared.ldap.trigger.LdapOperation;
062    import org.apache.directory.shared.ldap.trigger.TriggerSpecification;
063    import org.apache.directory.shared.ldap.trigger.TriggerSpecificationParser;
064    import org.apache.directory.shared.ldap.trigger.TriggerSpecification.SPSpec;
065    import org.slf4j.Logger;
066    import org.slf4j.LoggerFactory;
067    
068    
069    /**
070     * The Trigger Service based on the Trigger Specification.
071     *
072     * @org.apache.xbean.XBean
073     *
074     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
075     * @version $Rev:$
076     */
077    public class TriggerInterceptor extends BaseInterceptor
078    {
079        /** the logger for this class */
080        private static final Logger LOG = LoggerFactory.getLogger( TriggerInterceptor.class );
081        
082        /** the entry trigger attribute string: entryTrigger */
083        private static final String ENTRY_TRIGGER_ATTR = "entryTriggerSpecification";
084    
085        /** a triggerSpecCache that responds to add, delete, and modify attempts */
086        private TriggerSpecCache triggerSpecCache;
087        
088        /** a normalizing Trigger Specification parser */
089        private TriggerSpecificationParser triggerParser;
090        
091        /** */
092        private InterceptorChain chain;
093        
094        /** whether or not this interceptor is activated */
095        private boolean enabled = true;
096    
097        /** a Trigger Execution Authorizer */
098        private TriggerExecutionAuthorizer triggerExecutionAuthorizer = new SimpleTriggerExecutionAuthorizer();
099        
100        private StoredProcExecutionManager manager;
101    
102        /**
103         * Adds prescriptiveTrigger TriggerSpecificaitons to a collection of
104         * TriggerSpeficaitions by accessing the triggerSpecCache.  The trigger
105         * specification cache is accessed for each trigger subentry associated
106         * with the entry.
107         * Note that subentries are handled differently: their parent, the administrative
108         * entry is accessed to determine the perscriptiveTriggers effecting the AP
109         * and hence the subentry which is considered to be in the same context.
110         *
111         * @param triggerSpecs the collection of trigger specifications to add to
112         * @param dn the normalized distinguished name of the entry
113         * @param entry the target entry that is considered as the trigger source
114         * @throws Exception if there are problems accessing attribute values
115         * @param proxy the partition nexus proxy 
116         */
117        private void addPrescriptiveTriggerSpecs( OperationContext opContext, List<TriggerSpecification> triggerSpecs, 
118            DN dn, ServerEntry entry ) throws Exception
119        {
120            
121            /*
122             * If the protected entry is a subentry, then the entry being evaluated
123             * for perscriptiveTriggerss is in fact the administrative entry.  By
124             * substituting the administrative entry for the actual subentry the
125             * code below this "if" statement correctly evaluates the effects of
126             * perscriptiveTrigger on the subentry.  Basically subentries are considered
127             * to be in the same naming context as their access point so the subentries
128             * effecting their parent entry applies to them as well.
129             */
130            if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
131            {
132                DN parentDn = ( DN ) dn.clone();
133                parentDn.remove( dn.size() - 1 );
134                
135                entry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS );
136            }
137    
138            EntryAttribute subentries = entry.get( SchemaConstants.TRIGGER_EXECUTION_SUBENTRIES_AT );
139            
140            if ( subentries == null )
141            {
142                return;
143            }
144            
145            for ( Value<?> value:subentries )
146            {
147                String subentryDn = value.getString();
148                triggerSpecs.addAll( triggerSpecCache.getSubentryTriggerSpecs( subentryDn ) );
149            }
150        }
151    
152        /**
153         * Adds the set of entryTriggers to a collection of trigger specifications.
154         * The entryTrigger is parsed and tuples are generated on they fly then
155         * added to the collection.
156         *
157         * @param triggerSpecs the collection of trigger specifications to add to
158         * @param entry the target entry that is considered as the trigger source
159         * @throws Exception if there are problems accessing attribute values
160         */
161        private void addEntryTriggerSpecs( List<TriggerSpecification> triggerSpecs, ServerEntry entry ) throws Exception
162        {
163            EntryAttribute entryTrigger = entry.get( ENTRY_TRIGGER_ATTR );
164            
165            if ( entryTrigger == null )
166            {
167                return;
168            }
169    
170            for ( Value<?> value:entryTrigger )
171            {
172                String triggerString = value.getString();
173                TriggerSpecification item;
174    
175                try
176                {
177                    item = triggerParser.parse( triggerString );
178                }
179                catch ( ParseException e )
180                {
181                    String msg = I18n.err( I18n.ERR_72, triggerString );
182                    LOG.error( msg, e );
183                    throw new LdapOperationErrorException( msg );
184                }
185    
186                triggerSpecs.add( item );
187            }
188        }
189        
190        /**
191         * Return a selection of trigger specifications for a certain type of trigger action time.
192         * 
193         * @note This method serves as an extion point for new Action Time types.
194         * 
195         * @param triggerSpecs the trigger specifications
196         * @param ldapOperation the ldap operation being performed
197         * @return the set of trigger specs for a trigger action 
198         */
199        public Map<ActionTime, List<TriggerSpecification>> getActionTimeMappedTriggerSpecsForOperation( List<TriggerSpecification> triggerSpecs, LdapOperation ldapOperation )
200        {
201            List<TriggerSpecification> afterTriggerSpecs = new ArrayList<TriggerSpecification>();
202            Map<ActionTime, List<TriggerSpecification>> triggerSpecMap = new HashMap<ActionTime, List<TriggerSpecification>>();
203    
204            for ( TriggerSpecification triggerSpec : triggerSpecs )
205            {
206                if ( triggerSpec.getLdapOperation().equals( ldapOperation ) )
207                {
208                    if ( triggerSpec.getActionTime().equals( ActionTime.AFTER ) )
209                    {
210                        afterTriggerSpecs.add( triggerSpec );
211                    }
212                    else
213                    {
214    
215                    }
216                }
217            }
218            
219            triggerSpecMap.put( ActionTime.AFTER, afterTriggerSpecs );
220            
221            return triggerSpecMap;
222        }
223        
224        ////////////////////////////////////////////////////////////////////////////
225        // Interceptor Overrides
226        ////////////////////////////////////////////////////////////////////////////
227        
228        public void init( DirectoryService directoryService ) throws Exception
229        {
230            super.init( directoryService );
231            
232            triggerSpecCache = new TriggerSpecCache( directoryService );
233            final SchemaManager schemaManager = directoryService.getSchemaManager();
234    
235            triggerParser = new TriggerSpecificationParser
236                ( new NormalizerMappingResolver()
237                    {
238                        public Map<String, OidNormalizer> getNormalizerMapping() throws Exception
239                        {
240                            return schemaManager.getNormalizerMapping();
241                        }
242                    }
243                );
244            chain = directoryService.getInterceptorChain();
245            
246            //StoredProcEngineConfig javaxScriptSPEngineConfig = new JavaxStoredProcEngineConfig();
247            StoredProcEngineConfig javaSPEngineConfig = new JavaStoredProcEngineConfig();
248            List<StoredProcEngineConfig> spEngineConfigs = new ArrayList<StoredProcEngineConfig>();
249            //spEngineConfigs.add( javaxScriptSPEngineConfig );
250            spEngineConfigs.add( javaSPEngineConfig );
251            String spContainer = "ou=Stored Procedures,ou=system";
252            manager = new StoredProcExecutionManager( spContainer, spEngineConfigs );
253            
254            this.enabled = true; // TODO: Get this from the configuration if needed.
255        }
256    
257        
258        public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
259        {
260            DN name = addContext.getDn();
261            ServerEntry entry = addContext.getEntry();
262            
263            // Bypass trigger handling if the service is disabled.
264            if ( !enabled )
265            {
266                next.add( addContext );
267                return;
268            }
269            
270            // Gather supplementary data.
271            StoredProcedureParameterInjector injector = new AddStoredProcedureParameterInjector( addContext, name, entry );
272    
273            // Gather Trigger Specifications which apply to the entry being added.
274            List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
275            addPrescriptiveTriggerSpecs( addContext, triggerSpecs, name, entry );
276    
277            /**
278             *  NOTE: We do not handle entryTriggerSpecs for ADD operation.
279             */
280            
281            Map<ActionTime, List<TriggerSpecification>> triggerMap 
282                = getActionTimeMappedTriggerSpecsForOperation( triggerSpecs, LdapOperation.ADD );
283            
284            next.add( addContext );
285            triggerSpecCache.subentryAdded( name, entry );
286            
287            // Fire AFTER Triggers.
288            List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
289            executeTriggers( addContext, afterTriggerSpecs, injector );
290        }
291    
292        
293        public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws Exception
294        {
295            DN name = deleteContext.getDn();
296            
297            // Bypass trigger handling if the service is disabled.
298            if ( !enabled )
299            {
300                next.delete( deleteContext );
301                return;
302            }
303            
304            // Gather supplementary data.
305            ClonedServerEntry deletedEntry = deleteContext.lookup( name , ByPassConstants.LOOKUP_BYPASS );
306            
307            StoredProcedureParameterInjector injector = new DeleteStoredProcedureParameterInjector( deleteContext, name );
308    
309            // Gather Trigger Specifications which apply to the entry being deleted.
310            List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
311            addPrescriptiveTriggerSpecs( deleteContext, triggerSpecs, name, deletedEntry );
312            addEntryTriggerSpecs( triggerSpecs, deletedEntry );
313            
314            Map<ActionTime, List<TriggerSpecification>> triggerMap = 
315                getActionTimeMappedTriggerSpecsForOperation( triggerSpecs, LdapOperation.DELETE );
316            
317            next.delete( deleteContext );
318            triggerSpecCache.subentryDeleted( name, deletedEntry );
319            
320            // Fire AFTER Triggers.
321            List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
322            executeTriggers( deleteContext, afterTriggerSpecs, injector );
323        }
324        
325        
326        public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
327        {
328            // Bypass trigger handling if the service is disabled.
329            if ( !enabled )
330            {
331                next.modify( opContext );
332                return;
333            }
334            
335            DN normName = opContext.getDn();
336            
337            // Gather supplementary data.
338            ClonedServerEntry modifiedEntry = opContext.lookup( normName, ByPassConstants.LOOKUP_BYPASS );
339            
340            StoredProcedureParameterInjector injector = new ModifyStoredProcedureParameterInjector( opContext );
341    
342            // Gather Trigger Specifications which apply to the entry being modified.
343            List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
344            addPrescriptiveTriggerSpecs( opContext, triggerSpecs, normName, modifiedEntry );
345            addEntryTriggerSpecs( triggerSpecs, modifiedEntry );
346            
347            Map<ActionTime, List<TriggerSpecification>> triggerMap = getActionTimeMappedTriggerSpecsForOperation( triggerSpecs, LdapOperation.MODIFY );
348            
349            next.modify( opContext );
350            triggerSpecCache.subentryModified( opContext, modifiedEntry );
351            
352            // Fire AFTER Triggers.
353            List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
354            executeTriggers( opContext, afterTriggerSpecs, injector );
355        }
356        
357    
358        public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws Exception
359        {
360            DN name = renameContext.getDn();
361            RDN newRdn = renameContext.getNewRdn();
362            boolean deleteOldRn = renameContext.getDelOldDn();
363            
364            // Bypass trigger handling if the service is disabled.
365            if ( !enabled )
366            {
367                next.rename( renameContext );
368                return;
369            }
370            
371            // Gather supplementary data.        
372            ServerEntry renamedEntry = (ServerEntry)renameContext.getEntry().getClonedEntry();
373            
374            // @TODO : To be completely reviewed !!!
375            DN oldRDN = new DN( name.getRdn().getName() );
376            DN oldSuperiorDN = ( DN ) name.clone();
377            oldSuperiorDN.remove( oldSuperiorDN.size() - 1 );
378            DN newSuperiorDN = ( DN ) oldSuperiorDN.clone();
379            DN oldDN = ( DN ) name.clone();
380            DN newDN = ( DN ) name.clone();
381            newDN.add( newRdn );
382            
383            StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector(
384                renameContext, deleteOldRn, oldRDN, newRdn, oldSuperiorDN, newSuperiorDN, oldDN, newDN );
385            
386            // Gather Trigger Specifications which apply to the entry being renamed.
387            List<TriggerSpecification> triggerSpecs = new ArrayList<TriggerSpecification>();
388            addPrescriptiveTriggerSpecs( renameContext, triggerSpecs, name, renamedEntry );
389            addEntryTriggerSpecs( triggerSpecs, renamedEntry );
390            
391            Map<ActionTime, List<TriggerSpecification>> triggerMap = 
392                getActionTimeMappedTriggerSpecsForOperation( triggerSpecs, LdapOperation.MODIFYDN_RENAME );
393            
394            next.rename( renameContext );
395            triggerSpecCache.subentryRenamed( name, newDN );
396            
397            // Fire AFTER Triggers.
398            List<TriggerSpecification> afterTriggerSpecs = triggerMap.get( ActionTime.AFTER );
399            executeTriggers( renameContext, afterTriggerSpecs, injector );
400        }
401        
402        
403        public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) 
404            throws Exception
405        {
406            DN oriChildName = opContext.getDn();
407            DN parent = opContext.getParent();
408            RDN newRdn = opContext.getNewRdn();
409            boolean deleteOldRn = opContext.getDelOldDn();
410    
411            // Bypass trigger handling if the service is disabled.
412            if ( !enabled )
413            {
414                next.moveAndRename( opContext );
415                return;
416            }
417            
418            // Gather supplementary data.        
419            ClonedServerEntry movedEntry = opContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
420            
421            DN oldRDN = new DN( oriChildName.getRdn().getName() );
422            DN oldSuperiorDN = ( DN ) oriChildName.clone();
423            oldSuperiorDN.remove( oldSuperiorDN.size() - 1 );
424            DN newSuperiorDN = ( DN ) parent.clone();
425            DN oldDN = ( DN ) oriChildName.clone();
426            DN newDN = ( DN ) parent.clone();
427            newDN.add( newRdn.getName() );
428    
429            StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector(
430                opContext, deleteOldRn, oldRDN, newRdn, oldSuperiorDN, newSuperiorDN, oldDN, newDN );
431    
432            // Gather Trigger Specifications which apply to the entry being exported.
433            List<TriggerSpecification> exportTriggerSpecs = new ArrayList<TriggerSpecification>();
434            addPrescriptiveTriggerSpecs( opContext, exportTriggerSpecs, oriChildName, movedEntry );
435            addEntryTriggerSpecs( exportTriggerSpecs, movedEntry );
436            
437            // Get the entry again without operational attributes
438            // because access control subentry operational attributes
439            // will not be valid at the new location.
440            // This will certainly be fixed by the SubentryInterceptor,
441            // but after this service.
442            ClonedServerEntry importedEntry = opContext.lookup( oriChildName, 
443                ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
444            
445            // As the target entry does not exist yet and so
446            // its subentry operational attributes are not there,
447            // we need to construct an entry to represent it
448            // at least with minimal requirements which are object class
449            // and access control subentry operational attributes.
450            SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
451            ServerEntry fakeImportedEntry = subentryInterceptor.getSubentryAttributes( newDN, importedEntry );
452            
453            for ( EntryAttribute attribute:importedEntry )
454            {
455                fakeImportedEntry.put( attribute );
456            }
457            
458            // Gather Trigger Specifications which apply to the entry being imported.
459            // Note: Entry Trigger Specifications are not valid for Import.
460            List<TriggerSpecification> importTriggerSpecs = new ArrayList<TriggerSpecification>();
461            addPrescriptiveTriggerSpecs( opContext, importTriggerSpecs, newDN, fakeImportedEntry );
462            
463            Map<ActionTime, List<TriggerSpecification>> exportTriggerMap = 
464                getActionTimeMappedTriggerSpecsForOperation( exportTriggerSpecs, LdapOperation.MODIFYDN_EXPORT );
465            
466            Map<ActionTime, List<TriggerSpecification>> importTriggerMap = 
467                getActionTimeMappedTriggerSpecsForOperation( importTriggerSpecs, LdapOperation.MODIFYDN_IMPORT );
468            
469            next.moveAndRename( opContext );
470            triggerSpecCache.subentryRenamed( oldDN, newDN );
471            
472            // Fire AFTER Triggers.
473            List<TriggerSpecification> afterExportTriggerSpecs = exportTriggerMap.get( ActionTime.AFTER );
474            List<TriggerSpecification> afterImportTriggerSpecs = importTriggerMap.get( ActionTime.AFTER );
475            executeTriggers( opContext, afterExportTriggerSpecs, injector );
476            executeTriggers( opContext, afterImportTriggerSpecs, injector );
477        }
478        
479        
480        public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
481        {
482            // Bypass trigger handling if the service is disabled.
483            if ( !enabled )
484            {
485                next.move( opContext );
486                return;
487            }
488            
489            DN oriChildName = opContext.getDn();
490            DN newParentName = opContext.getParent();
491            
492            // Gather supplementary data.        
493            ClonedServerEntry movedEntry = opContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
494            
495            DN oldRDN = new DN( oriChildName.getRdn().getName() );
496            RDN newRDN = new RDN( oriChildName.getRdn().getName() );
497            DN oldSuperiorDN = ( DN ) oriChildName.clone();
498            oldSuperiorDN.remove( oldSuperiorDN.size() - 1 );
499            DN newSuperiorDN = ( DN ) newParentName.clone();
500            DN oldDN = ( DN ) oriChildName.clone();
501            DN newDN = ( DN ) newParentName.clone();
502            newDN.add( newRDN.getName() );
503    
504            StoredProcedureParameterInjector injector = new ModifyDNStoredProcedureParameterInjector(
505                opContext, false, oldRDN, newRDN, oldSuperiorDN, newSuperiorDN, oldDN, newDN );
506    
507            // Gather Trigger Specifications which apply to the entry being exported.
508            List<TriggerSpecification> exportTriggerSpecs = new ArrayList<TriggerSpecification>();
509            addPrescriptiveTriggerSpecs( opContext, exportTriggerSpecs, oriChildName, movedEntry );
510            addEntryTriggerSpecs( exportTriggerSpecs, movedEntry );
511            
512            // Get the entry again without operational attributes
513            // because access control subentry operational attributes
514            // will not be valid at the new location.
515            // This will certainly be fixed by the SubentryInterceptor,
516            // but after this service.
517            ClonedServerEntry importedEntry = opContext.lookup( oriChildName, 
518                ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
519    
520            // As the target entry does not exist yet and so
521            // its subentry operational attributes are not there,
522            // we need to construct an entry to represent it
523            // at least with minimal requirements which are object class
524            // and access control subentry operational attributes.
525            SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
526            ServerEntry fakeImportedEntry = subentryInterceptor.getSubentryAttributes( newDN, importedEntry );
527            
528            for ( EntryAttribute attribute:importedEntry )
529            {
530                fakeImportedEntry.put( attribute );
531            }
532            
533            // Gather Trigger Specifications which apply to the entry being imported.
534            // Note: Entry Trigger Specifications are not valid for Import.
535            List<TriggerSpecification> importTriggerSpecs = new ArrayList<TriggerSpecification>();
536            addPrescriptiveTriggerSpecs( opContext, importTriggerSpecs, newDN, fakeImportedEntry );
537            
538            Map<ActionTime, List<TriggerSpecification>> exportTriggerMap = getActionTimeMappedTriggerSpecsForOperation( exportTriggerSpecs, LdapOperation.MODIFYDN_EXPORT );
539            
540            Map<ActionTime, List<TriggerSpecification>> importTriggerMap = getActionTimeMappedTriggerSpecsForOperation( importTriggerSpecs, LdapOperation.MODIFYDN_IMPORT );
541            
542            next.move( opContext );
543            triggerSpecCache.subentryRenamed( oldDN, newDN );
544            
545            // Fire AFTER Triggers.
546            List<TriggerSpecification> afterExportTriggerSpecs = exportTriggerMap.get( ActionTime.AFTER );
547            List<TriggerSpecification> afterImportTriggerSpecs = importTriggerMap.get( ActionTime.AFTER );
548            executeTriggers( opContext, afterExportTriggerSpecs, injector );
549            executeTriggers( opContext, afterImportTriggerSpecs, injector );
550        }
551        
552        ////////////////////////////////////////////////////////////////////////////
553        // Utility Methods
554        ////////////////////////////////////////////////////////////////////////////
555        
556        
557        private Object executeTriggers( OperationContext opContext, List<TriggerSpecification> triggerSpecs, 
558            StoredProcedureParameterInjector injector ) throws Exception
559        {
560            Object result = null;
561    
562            for ( TriggerSpecification triggerSpec : triggerSpecs )
563            {
564                // TODO: Replace the Authorization Code with a REAL one.
565                if ( triggerExecutionAuthorizer.hasPermission( opContext ) )
566                {
567                    /**
568                     * If there is only one Trigger to be executed, this assignment
569                     * will make sense (as in INSTEADOF search Triggers).
570                     */
571                    result = executeTrigger( opContext, triggerSpec, injector );
572                }
573            }
574            
575            /**
576             * If only one Trigger has been executed, returning its result
577             * can make sense (as in INSTEADOF Search Triggers).
578             */
579            return result;
580        }
581    
582        private Object executeTrigger( OperationContext opContext, TriggerSpecification tsec, 
583            StoredProcedureParameterInjector injector ) throws Exception
584        {
585            List<Object> returnValues = new ArrayList<Object>();
586            List<SPSpec> spSpecs = tsec.getSPSpecs();
587            for ( SPSpec spSpec : spSpecs )
588            {
589                List<Object> arguments = new ArrayList<Object>();
590                arguments.addAll( injector.getArgumentsToInject( opContext, spSpec.getParameters() ) );
591                Object[] values = arguments.toArray();
592                Object returnValue = executeProcedure( opContext, spSpec.getName(), values );
593                returnValues.add( returnValue );
594            }
595            
596            return returnValues; 
597        }
598    
599        
600        private Object executeProcedure( OperationContext opContext, String procedure, Object[] values ) throws Exception
601        {
602            
603            try
604            {
605                ClonedServerEntry spUnit = manager.findStoredProcUnit( opContext.getSession(), procedure );
606                StoredProcEngine engine = manager.getStoredProcEngineInstance( spUnit );
607                return engine.invokeProcedure( opContext.getSession(), procedure, values );
608            }
609            catch ( Exception e )
610            {
611                LdapOtherException lne = new LdapOtherException( e.getMessage() );
612                lne.initCause( e );
613                throw lne;
614            }
615        }
616    
617    }