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 */
020package org.apache.directory.server.core.trigger;
021
022
023import java.text.ParseException;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import javax.naming.directory.SearchControls;
032
033import org.apache.directory.api.ldap.model.constants.SchemaConstants;
034import org.apache.directory.api.ldap.model.entry.Attribute;
035import org.apache.directory.api.ldap.model.entry.Entry;
036import org.apache.directory.api.ldap.model.entry.Modification;
037import org.apache.directory.api.ldap.model.entry.Value;
038import org.apache.directory.api.ldap.model.exception.LdapException;
039import org.apache.directory.api.ldap.model.exception.LdapOperationException;
040import org.apache.directory.api.ldap.model.filter.EqualityNode;
041import org.apache.directory.api.ldap.model.filter.ExprNode;
042import org.apache.directory.api.ldap.model.message.AliasDerefMode;
043import org.apache.directory.api.ldap.model.name.Dn;
044import org.apache.directory.api.ldap.model.schema.AttributeType;
045import org.apache.directory.api.ldap.model.schema.NormalizerMappingResolver;
046import org.apache.directory.api.ldap.model.schema.SchemaManager;
047import org.apache.directory.api.ldap.model.schema.normalizers.OidNormalizer;
048import org.apache.directory.api.ldap.trigger.TriggerSpecification;
049import org.apache.directory.api.ldap.trigger.TriggerSpecificationParser;
050import org.apache.directory.server.constants.ApacheSchemaConstants;
051import org.apache.directory.server.core.api.CoreSession;
052import org.apache.directory.server.core.api.DirectoryService;
053import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
054import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
055import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
056import org.apache.directory.server.core.api.partition.Partition;
057import org.apache.directory.server.core.api.partition.PartitionNexus;
058import org.apache.directory.server.i18n.I18n;
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062
063/**
064 * A cache for Trigger Specifications which responds to specific events to
065 * perform cache house keeping as trigger subentries are added, deleted
066 * and modified.
067 *
068 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
069 */
070public class TriggerSpecCache
071{
072    /** the attribute id for prescriptive trigger: prescriptiveTrigger */
073    private static final String PRESCRIPTIVE_TRIGGER_ATTR = "prescriptiveTriggerSpecification";
074
075    /** the logger for this class */
076    private static final Logger LOG = LoggerFactory.getLogger( TriggerSpecCache.class );
077
078    /** a map of strings to TriggerSpecification collections */
079    private final Map<Dn, List<TriggerSpecification>> triggerSpecs = new HashMap<>();
080
081    /** a handle on the partition nexus */
082    private final PartitionNexus nexus;
083
084    /** a normalizing TriggerSpecification parser */
085    private final TriggerSpecificationParser triggerSpecParser;
086
087
088    /**
089     * Creates a TriggerSpecification cache.
090     *
091     * @param directoryService the directory service core
092     * @throws LdapException with problems initializing cache
093     */
094    public TriggerSpecCache( DirectoryService directoryService ) throws LdapException
095    {
096        this.nexus = directoryService.getPartitionNexus();
097        final SchemaManager schemaManager = directoryService.getSchemaManager();
098
099        triggerSpecParser = new TriggerSpecificationParser( new NormalizerMappingResolver()
100        {
101            @Override
102            public Map<String, OidNormalizer> getNormalizerMapping() throws Exception
103            {
104                return schemaManager.getNormalizerMapping();
105            }
106        } );
107
108        initialize( directoryService );
109    }
110
111
112    private void initialize( DirectoryService directoryService ) throws LdapException
113    {
114        // search all naming contexts for trigger subentenries
115        // generate TriggerSpecification arrays for each subentry
116        // add that subentry to the hash
117        Set<String> suffixes = nexus.listSuffixes();
118
119        AttributeType objectClassAt = directoryService.getSchemaManager().
120            getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
121
122        for ( String suffix : suffixes )
123        {
124            Dn baseDn = directoryService.getDnFactory().create( suffix );
125            ExprNode filter = new EqualityNode<String>( objectClassAt,
126                new Value( objectClassAt, ApacheSchemaConstants.TRIGGER_EXECUTION_SUBENTRY_OC ) );
127            SearchControls ctls = new SearchControls();
128            ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
129
130            CoreSession adminSession = directoryService.getAdminSession();
131
132            Partition partition = nexus.getPartition( baseDn ); 
133            SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, baseDn,
134                filter, ctls );
135            searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS );
136            searchOperationContext.setPartition( partition );
137            searchOperationContext.setTransaction( partition.beginReadTransaction() );
138
139            EntryFilteringCursor results = nexus.search( searchOperationContext );
140
141            try
142            {
143                while ( results.next() )
144                {
145                    Entry resultEntry = results.get();
146                    Dn subentryDn = resultEntry.getDn();
147                    Attribute triggerSpec = resultEntry.get( PRESCRIPTIVE_TRIGGER_ATTR );
148
149                    if ( triggerSpec == null )
150                    {
151                        LOG.warn( "Found triggerExecutionSubentry '{}' without any {}", subentryDn, PRESCRIPTIVE_TRIGGER_ATTR );
152                        continue;
153                    }
154
155                    Dn normSubentryDn = subentryDn;
156                    
157                    if ( !subentryDn.isSchemaAware() )
158                    {
159                        normSubentryDn = new Dn( directoryService.getSchemaManager(), subentryDn );
160                    }
161                    
162                    subentryAdded( normSubentryDn, resultEntry );
163                }
164
165                results.close();
166            }
167            catch ( Exception e )
168            {
169                throw new LdapOperationException( e.getMessage(), e );
170            }
171        }
172    }
173
174
175    private boolean hasPrescriptiveTrigger( Entry entry )
176    {
177        // only do something if the entry contains prescriptiveTrigger
178        Attribute triggerSpec = entry.get( PRESCRIPTIVE_TRIGGER_ATTR );
179
180        return triggerSpec != null;
181    }
182
183
184    public void subentryAdded( Dn normName, Entry entry )
185    {
186        // only do something if the entry contains prescriptiveTrigger
187        Attribute triggerSpec = entry.get( PRESCRIPTIVE_TRIGGER_ATTR );
188
189        if ( triggerSpec == null )
190        {
191            return;
192        }
193
194        List<TriggerSpecification> subentryTriggerSpecs = new ArrayList<>();
195
196        for ( Value value : triggerSpec )
197        {
198            TriggerSpecification item = null;
199
200            try
201            {
202                item = triggerSpecParser.parse( value.getString() );
203                subentryTriggerSpecs.add( item );
204            }
205            catch ( ParseException e )
206            {
207                String msg = I18n.err( I18n.ERR_73, item );
208                LOG.error( msg, e );
209            }
210
211        }
212
213        triggerSpecs.put( normName, subentryTriggerSpecs );
214    }
215
216
217    public void subentryDeleted( Dn normName, Entry entry )
218    {
219        if ( !hasPrescriptiveTrigger( entry ) )
220        {
221            return;
222        }
223
224        triggerSpecs.remove( normName );
225    }
226
227
228    public void subentryModified( ModifyOperationContext opContext, Entry entry )
229    {
230        if ( !hasPrescriptiveTrigger( entry ) )
231        {
232            return;
233        }
234
235        Dn normName = opContext.getDn();
236        List<Modification> mods = opContext.getModItems();
237
238        boolean isTriggerSpecModified = false;
239
240        for ( Modification mod : mods )
241        {
242            isTriggerSpecModified |= mod.getAttribute().contains( PRESCRIPTIVE_TRIGGER_ATTR );
243        }
244
245        if ( isTriggerSpecModified )
246        {
247            subentryDeleted( normName, entry );
248            subentryAdded( normName, entry );
249        }
250    }
251
252
253    public List<TriggerSpecification> getSubentryTriggerSpecs( Dn subentryDn )
254    {
255        List<TriggerSpecification> subentryTriggerSpecs = triggerSpecs.get( subentryDn );
256        
257        if ( subentryTriggerSpecs == null )
258        {
259            return Collections.emptyList();
260        }
261        
262        return Collections.unmodifiableList( subentryTriggerSpecs );
263    }
264
265
266    public void subentryRenamed( Dn oldName, Dn newName )
267    {
268        triggerSpecs.put( newName, triggerSpecs.remove( oldName ) );
269    }
270}