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 }