View Javadoc

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