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