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