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.referral;
021
022 import javax.naming.Context;
023 import javax.naming.NamingException;
024
025 import org.apache.directory.server.core.DirectoryService;
026 import org.apache.directory.server.core.ReferralManager;
027 import org.apache.directory.server.core.ReferralManagerImpl;
028 import org.apache.directory.server.core.interceptor.BaseInterceptor;
029 import org.apache.directory.server.core.interceptor.NextInterceptor;
030 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
031 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
032 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
033 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
034 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
035 import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
036 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
037 import org.apache.directory.server.core.partition.PartitionNexus;
038 import org.apache.directory.server.i18n.I18n;
039 import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
040 import org.apache.directory.shared.ldap.constants.SchemaConstants;
041 import org.apache.directory.shared.ldap.entry.StringValue;
042 import org.apache.directory.shared.ldap.entry.EntryAttribute;
043 import org.apache.directory.shared.ldap.entry.ServerEntry;
044 import org.apache.directory.shared.ldap.entry.Value;
045 import org.apache.directory.shared.ldap.filter.SearchScope;
046 import org.apache.directory.shared.ldap.name.DN;
047 import org.apache.directory.shared.ldap.schema.SchemaManager;
048 import org.apache.directory.shared.ldap.util.LdapURL;
049 import org.apache.directory.shared.ldap.util.StringTools;
050 import org.slf4j.Logger;
051 import org.slf4j.LoggerFactory;
052
053
054 /**
055 * An service which is responsible referral handling behavoirs. It manages
056 * referral handling behavoir when the {@link Context#REFERRAL} is implicitly
057 * or explicitly set to "ignore", when set to "throw" and when set to "follow".
058 *
059 * @org.apache.xbean.XBean
060 *
061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062 * @version $Rev$
063 */
064 public class ReferralInterceptor extends BaseInterceptor
065 {
066 private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class );
067
068 private PartitionNexus nexus;
069
070 /** The global schemaManager */
071 private SchemaManager schemaManager;
072
073 /** The referralManager */
074 private ReferralManager referralManager;
075
076 /** A normalized form for the SubschemaSubentry DN */
077 private String subschemaSubentryDnNorm;
078
079
080 static private void checkRefAttributeValue( Value<?> value ) throws NamingException, LdapURLEncodingException
081 {
082 StringValue ref = ( StringValue ) value;
083
084 String refVal = ref.getString();
085
086 LdapURL ldapUrl = new LdapURL( refVal );
087
088 // We have a LDAP URL, we have to check that :
089 // - we don't have scope specifier
090 // - we don't have filters
091 // - we don't have attribute description list
092 // - we don't have extensions
093 // - the DN is not empty
094
095 if ( ldapUrl.getScope() != SearchScope.OBJECT )
096 {
097 // This is the default value if we don't have any scope
098 // Let's assume that it's incorrect if we get something
099 // else in the LdapURL
100 String message = I18n.err( I18n.ERR_36 );
101 LOG.error( message );
102 throw new NamingException( message );
103 }
104
105 if ( !StringTools.isEmpty( ldapUrl.getFilter() ) )
106 {
107 String message = I18n.err( I18n.ERR_37 );
108 LOG.error( message );
109 throw new NamingException( message );
110 }
111
112 if ( ( ldapUrl.getAttributes() != null ) && ( ldapUrl.getAttributes().size() != 0 ) )
113 {
114 String message = I18n.err( I18n.ERR_38 );
115 LOG.error( message );
116 throw new NamingException( message );
117 }
118
119 if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) )
120 {
121 String message = I18n.err( I18n.ERR_39 );
122 LOG.error( message );
123 throw new NamingException( message );
124 }
125
126 if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) )
127 {
128 String message = I18n.err( I18n.ERR_40 );
129 LOG.error( message );
130 throw new NamingException( message );
131 }
132
133 DN dn = ldapUrl.getDn();
134
135 if ( ( dn == null ) || dn.isEmpty() )
136 {
137 String message = I18n.err( I18n.ERR_41 );
138 LOG.error( message );
139 throw new NamingException( message );
140 }
141 }
142
143
144 static private boolean isReferral( ServerEntry entry ) throws NamingException
145 {
146 // Check that the entry is not null, otherwise return FALSE.
147 // This is typically to cover the case where the entry has not
148 // been added into the context because it does not exists.
149 if ( entry == null )
150 {
151 return false;
152 }
153
154 EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
155
156 if ( oc == null )
157 {
158 LOG.warn( "could not find objectClass attribute in entry: " + entry );
159 return false;
160 }
161
162 if ( !oc.contains( SchemaConstants.REFERRAL_OC ) )
163 {
164 return false;
165 }
166 else
167 {
168 // We have a referral ObjectClass, let's check that the ref is
169 // valid, accordingly to the RFC
170
171 // Get the 'ref' attributeType
172 EntryAttribute refAttr = entry.get( SchemaConstants.REF_AT );
173
174 if ( refAttr == null )
175 {
176 // very unlikely, as we have already checked the entry in SchemaInterceptor
177 String message = I18n.err( I18n.ERR_42 );
178 LOG.error( message );
179 throw new NamingException( message );
180 }
181
182 for ( Value<?> value : refAttr )
183 {
184 try
185 {
186 checkRefAttributeValue( value );
187 }
188 catch ( LdapURLEncodingException luee )
189 {
190 // Either the URL is invalid, or it's not a LDAP URL.
191 // we will just ignore this LdapURL.
192 }
193 }
194
195 return true;
196 }
197 }
198
199
200 public void init( DirectoryService directoryService ) throws Exception
201 {
202 nexus = directoryService.getPartitionNexus();
203 schemaManager = directoryService.getSchemaManager();
204
205 // Initialize the referralManager
206 referralManager = new ReferralManagerImpl( directoryService );
207 directoryService.setReferralManager( referralManager );
208
209 Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
210 DN subschemaSubentryDn = new DN( subschemaSubentry.getString() );
211 subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() );
212 subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
213 }
214
215
216 /**
217 * Add an entry into the server. We have 3 cases :
218 * (1) The entry does not have any parent referral and is not a referral itself
219 * (2) The entry does not have any parent referral and is a referral itself
220 * (3) The entry has a parent referral
221 *
222 * Case (1) is easy : we inject the entry into the server and we are done.
223 * Case (2) is the same as case (1), but we have to update the referral manager.
224 * Case (3) is handled by the LdapProcotol handler, as we have to return a
225 * LdapResult containing a list of this entry's parent's referrals URL, if the
226 * ManageDSAIT control is not present, or the parent's entry if the control
227 * is present.
228 *
229 * Of course, if the entry already exists, nothing will be done, as we will get an
230 * entryAlreadyExists error.
231 *
232 */
233 public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
234 {
235 ServerEntry entry = opContext.getEntry();
236
237 // Check if the entry is a referral itself
238 boolean isReferral = isReferral( entry );
239
240 // We add the entry into the server
241 next.add( opContext );
242
243 // If the addition is successful, we update the referralManager
244 if ( isReferral )
245 {
246 // We have to add it to the referralManager
247 referralManager.lockWrite();
248
249 referralManager.addReferral( entry );
250
251 referralManager.unlock();
252 }
253
254 }
255
256
257 /**
258 * Delete an entry in the server. We have 4 cases :
259 * (1) the entry is not a referral and does not have a parent referral
260 * (2) the entry is not a referral but has a parent referral
261 * (3) the entry is a referral
262 *
263 * Case (1) is handled by removing the entry from the server
264 * In case (2), we return an exception build using the parent referral
265 * For case(3), we remove the entry from the server and remove the referral
266 * from the referral manager.
267 *
268 * If the entry does not exist in the server, we will get a NoSuchObject error
269 */
270 public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
271 {
272 ServerEntry entry = opContext.getEntry();
273
274 // First delete the entry into the server
275 next.delete( opContext );
276
277 // Check if the entry exists and is a referral itself
278 // If so, we have to update the referralManager
279 if ( ( entry != null ) && isReferral( entry ) )
280 {
281 // We have to remove it from the referralManager
282 referralManager.lockWrite();
283
284 referralManager.removeReferral( entry );
285
286 referralManager.unlock();
287 }
288 }
289
290
291 /**
292 *
293 **/
294 public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
295 {
296 DN oldName = opContext.getDn();
297
298 DN newName = ( DN ) opContext.getParent().clone();
299 newName.add( oldName.get( oldName.size() - 1 ) );
300
301 // Check if the entry is a referral itself
302 boolean isReferral = isReferral( opContext.getEntry() );
303
304 next.move( opContext );
305
306
307 if ( isReferral )
308 {
309 // Update the referralManager
310 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName );
311
312 ServerEntry newEntry = nexus.lookup( lookupContext );
313
314 referralManager.lockWrite();
315
316 referralManager.addReferral( newEntry );
317 referralManager.removeReferral( opContext.getEntry() );
318
319 referralManager.unlock();
320 }
321 }
322
323
324 /**
325 *
326 **/
327 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws Exception
328 {
329 DN newName = ( DN ) opContext.getParent().clone();
330 newName.add( opContext.getNewRdn() );
331
332 // Check if the entry is a referral itself
333 boolean isReferral = isReferral( opContext.getEntry() );
334
335 next.moveAndRename( opContext );
336
337 if ( isReferral )
338 {
339 // Update the referralManager
340 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), newName );
341
342 ServerEntry newEntry = nexus.lookup( lookupContext );
343
344 referralManager.lockWrite();
345
346 referralManager.addReferral( newEntry );
347 referralManager.removeReferral( opContext.getEntry() );
348
349 referralManager.unlock();
350 }
351 }
352
353
354 public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
355 {
356 // Check if the entry is a referral itself
357 boolean isReferral = isReferral( opContext.getEntry() );
358
359 next.rename( opContext );
360
361 if ( isReferral )
362 {
363 // Update the referralManager
364 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), opContext.getNewDn() );
365
366 ServerEntry newEntry = nexus.lookup( lookupContext );
367
368 referralManager.lockWrite();
369
370 referralManager.addReferral( newEntry );
371 referralManager.removeReferral( opContext.getEntry().getOriginalEntry() );
372
373 referralManager.unlock();
374 }
375 }
376
377
378 /**
379 * Modify an entry in the server.
380 */
381 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
382 {
383 DN name = opContext.getDn();
384
385 // handle a normal modify without following referrals
386 next.modify( opContext );
387
388 // Check if we are trying to modify the schema or the rootDSE,
389 // if so, we don't modify the referralManager
390 if ( ( name == DN.EMPTY_DN ) || ( subschemaSubentryDnNorm.equals( name.getNormName() ) ) )
391 {
392 // Do nothing
393 return;
394 }
395
396 // Update the referralManager. We have to read the entry again
397 // as it has been modified, before updating the ReferralManager
398 // TODO: this can be spare, as we build the entry later.
399 // But we will have to store the modified entry into the opContext
400 LookupOperationContext lookupContext = new LookupOperationContext( opContext.getSession(), name );
401
402 ServerEntry newEntry = nexus.lookup( lookupContext );
403
404 // Check that we have the entry, just in case
405 // TODO : entries should be locked until the operation is done on it.
406 if ( newEntry != null )
407 {
408 referralManager.lockWrite();
409
410 if ( referralManager.isReferral( newEntry.getDn() ) )
411 {
412 referralManager.removeReferral( opContext.getEntry() );
413 referralManager.addReferral( newEntry );
414 }
415
416 referralManager.unlock();
417 }
418 }
419
420
421 /**
422 * When adding a new context partition, we have to update the referralManager
423 * by injecting all the new referrals into it. This is done using the init()
424 * method of the referralManager.
425 *
426 public void addContextPartition( NextInterceptor next, AddContextPartitionOperationContext opContext )
427 throws Exception
428 {
429 // First, inject the partition
430 next.addContextPartition( opContext );
431
432 Partition partition = opContext.getPartition();
433 DN suffix = partition.getSuffixDn();
434
435 // add referrals immediately after adding the new partition
436 referralManager.init( directoryService, new String[]{ suffix.getNormName() } );
437 }
438
439
440 /**
441 * Remove a partion's referrals from the server. We have to first
442 * clear the referrals manager from all of this partition's referrals,
443 * then we can delete the partition.
444 *
445 public void removeContextPartition( NextInterceptor next, RemoveContextPartitionOperationContext opContext )
446 throws Exception
447 {
448 // get the partition suffix
449 DN suffix = opContext.getDn();
450
451 // remove referrals immediately before removing the partition
452 referralManager.remove( directoryService, suffix );
453
454 // And remove the partition from the server
455 next.removeContextPartition( opContext );
456 }*/
457 }