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.exception; 021 022 023 import java.util.List; 024 025 import org.apache.commons.collections.map.LRUMap; 026 import org.apache.directory.server.core.DirectoryService; 027 import org.apache.directory.server.core.entry.ClonedServerEntry; 028 import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor; 029 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 030 import org.apache.directory.server.core.interceptor.BaseInterceptor; 031 import org.apache.directory.server.core.interceptor.NextInterceptor; 032 import org.apache.directory.server.core.interceptor.context.AddOperationContext; 033 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; 034 import org.apache.directory.server.core.interceptor.context.EntryOperationContext; 035 import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext; 036 import org.apache.directory.server.core.interceptor.context.ListOperationContext; 037 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 038 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 039 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 040 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 041 import org.apache.directory.server.core.interceptor.context.OperationContext; 042 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 043 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 044 import org.apache.directory.server.core.partition.ByPassConstants; 045 import org.apache.directory.server.core.partition.Partition; 046 import org.apache.directory.server.core.partition.PartitionNexus; 047 import org.apache.directory.server.i18n.I18n; 048 import org.apache.directory.shared.ldap.constants.SchemaConstants; 049 import org.apache.directory.shared.ldap.cursor.EmptyCursor; 050 import org.apache.directory.shared.ldap.entry.EntryAttribute; 051 import org.apache.directory.shared.ldap.entry.Modification; 052 import org.apache.directory.shared.ldap.entry.ModificationOperation; 053 import org.apache.directory.shared.ldap.entry.ServerEntry; 054 import org.apache.directory.shared.ldap.entry.Value; 055 import org.apache.directory.shared.ldap.exception.LdapAliasException; 056 import org.apache.directory.shared.ldap.exception.LdapAttributeInUseException; 057 import org.apache.directory.shared.ldap.exception.LdapContextNotEmptyException; 058 import org.apache.directory.shared.ldap.exception.LdapEntryAlreadyExistsException; 059 import org.apache.directory.shared.ldap.exception.LdapNoSuchObjectException; 060 import org.apache.directory.shared.ldap.exception.LdapUnwillingToPerformException; 061 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 062 import org.apache.directory.shared.ldap.name.DN; 063 064 065 /** 066 * An {@link org.apache.directory.server.core.interceptor.Interceptor} that detects any operations that breaks integrity 067 * of {@link Partition} and terminates the current invocation chain by 068 * throwing a {@link Exception}. Those operations include when an entry 069 * already exists at a DN and is added once again to the same DN. 070 * 071 * @org.apache.xbean.XBean 072 * 073 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 074 * @version $Rev: 927839 $ 075 */ 076 public class ExceptionInterceptor extends BaseInterceptor 077 { 078 private PartitionNexus nexus; 079 private DirectoryService directoryService; 080 private DN subschemSubentryDn; 081 082 083 /** 084 * A cache to store entries which are not aliases. 085 * It's a speedup, we will be able to avoid backend lookups. 086 * 087 * Note that the backend also use a cache mechanism, but for performance gain, it's good 088 * to manage a cache here. The main problem is that when a user modify the parent, we will 089 * have to update it at three different places : 090 * - in the backend, 091 * - in the partition cache, 092 * - in this cache. 093 * 094 * The update of the backend and partition cache is already correctly handled, so we will 095 * just have to offer an access to refresh the local cache. This should be done in 096 * delete, modify and move operations. 097 * 098 * We need to be sure that frequently used DNs are always in cache, and not discarded. 099 * We will use a LRU cache for this purpose. 100 */ 101 private final LRUMap notAliasCache = new LRUMap( DEFAULT_CACHE_SIZE ); 102 103 /** Declare a default for this cache. 100 entries seems to be enough */ 104 private static final int DEFAULT_CACHE_SIZE = 100; 105 106 107 /** 108 * Creates an interceptor that is also the exception handling service. 109 */ 110 public ExceptionInterceptor() 111 { 112 } 113 114 115 public void init( DirectoryService directoryService ) throws Exception 116 { 117 this.directoryService = directoryService; 118 nexus = directoryService.getPartitionNexus(); 119 Value<?> attr = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); 120 subschemSubentryDn = new DN( attr.getString() ); 121 subschemSubentryDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() ); 122 } 123 124 125 public void destroy() 126 { 127 } 128 129 /** 130 * In the pre-invocation state this interceptor method checks to see if the entry to be added already exists. If it 131 * does an exception is raised. 132 */ 133 public void add( NextInterceptor nextInterceptor, AddOperationContext opContext ) 134 throws Exception 135 { 136 DN name = opContext.getDn(); 137 138 if ( subschemSubentryDn.getNormName().equals( name.getNormName() ) ) 139 { 140 throw new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_249 ) ); 141 } 142 143 // check if the entry already exists 144 if ( nextInterceptor.hasEntry( new EntryOperationContext( opContext.getSession(), name ) ) ) 145 { 146 LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_250, name.getName() ) ); 147 //ne.setResolvedName( new DN( name.getName() ) ); 148 throw ne; 149 } 150 151 DN suffix = nexus.getSuffix( new GetSuffixOperationContext( this.directoryService.getAdminSession(), 152 name ) ); 153 154 // we're adding the suffix entry so just ignore stuff to mess with the parent 155 if ( suffix.equals( name ) ) 156 { 157 nextInterceptor.add( opContext ); 158 return; 159 } 160 161 DN parentDn = ( DN ) name.clone(); 162 parentDn.remove( name.size() - 1 ); 163 164 // check if we're trying to add to a parent that is an alias 165 boolean notAnAlias; 166 167 synchronized( notAliasCache ) 168 { 169 notAnAlias = notAliasCache.containsKey( parentDn.getNormName() ); 170 } 171 172 if ( ! notAnAlias ) 173 { 174 // We don't know if the parent is an alias or not, so we will launch a 175 // lookup, and update the cache if it's not an alias 176 ClonedServerEntry attrs; 177 178 try 179 { 180 attrs = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS ); 181 } 182 catch ( Exception e ) 183 { 184 LdapNoSuchObjectException e2 = new LdapNoSuchObjectException( I18n.err( I18n.ERR_251, 185 parentDn.getName() ) ); 186 //e2.setResolvedName( new DN( nexus.getMatchedName( 187 // new GetMatchedNameOperationContext( opContext.getSession(), parentDn ) ).getName() ) ); 188 throw e2; 189 } 190 191 EntryAttribute objectClass = attrs.getOriginalEntry().get( SchemaConstants.OBJECT_CLASS_AT ); 192 193 if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) 194 { 195 String msg = I18n.err( I18n.ERR_252, name.getName() ); 196 LdapAliasException e = new LdapAliasException( msg ); 197 //e.setResolvedName( new DN( parentDn.getName() ) ); 198 throw e; 199 } 200 else 201 { 202 synchronized ( notAliasCache ) 203 { 204 notAliasCache.put( parentDn.getNormName(), parentDn ); 205 } 206 } 207 } 208 209 nextInterceptor.add( opContext ); 210 } 211 212 213 /** 214 * Checks to make sure the entry being deleted exists, and has no children, otherwise throws the appropriate 215 * LdapException. 216 */ 217 public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws Exception 218 { 219 DN name = opContext.getDn(); 220 221 if ( name.getNormName().equalsIgnoreCase( subschemSubentryDn.getNormName() ) ) 222 { 223 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 224 I18n.err( I18n.ERR_253, subschemSubentryDn ) ); 225 } 226 227 // check if entry to delete exists 228 String msg = "Attempt to delete non-existant entry: "; 229 assertHasEntry( nextInterceptor, opContext, msg, name ); 230 231 // check if entry to delete has children (only leaves can be deleted) 232 boolean hasChildren = false; 233 EntryFilteringCursor list = nextInterceptor.list( new ListOperationContext( opContext.getSession(), name ) ); 234 235 if ( list.next() ) 236 { 237 hasChildren = true; 238 } 239 240 list.close(); 241 242 if ( hasChildren ) 243 { 244 LdapContextNotEmptyException e = new LdapContextNotEmptyException(); 245 //e.setResolvedName( new DN( name.getName() ) ); 246 throw e; 247 } 248 249 synchronized( notAliasCache ) 250 { 251 if ( notAliasCache.containsKey( name.getNormName() ) ) 252 { 253 notAliasCache.remove( name.getNormName() ); 254 } 255 } 256 257 nextInterceptor.delete( opContext ); 258 } 259 260 261 /** 262 * Checks to see the base being searched exists, otherwise throws the appropriate LdapException. 263 */ 264 public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception 265 { 266 if ( opContext.getDn().getNormName().equals( subschemSubentryDn.getNormName() ) ) 267 { 268 // there is nothing under the schema subentry 269 return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext ); 270 } 271 272 // check if entry to search exists 273 String msg = "Attempt to search under non-existant entry: "; 274 assertHasEntry( nextInterceptor, opContext, msg, opContext.getDn() ); 275 276 return nextInterceptor.list( opContext ); 277 } 278 279 280 /** 281 * Checks to see the base being searched exists, otherwise throws the appropriate LdapException. 282 */ 283 public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception 284 { 285 if ( opContext.getDn().getNormName().equals( subschemSubentryDn.getNormName() ) ) 286 { 287 return nexus.getRootDSE( null ); 288 } 289 290 // check if entry to lookup exists 291 String msg = "Attempt to lookup non-existant entry: "; 292 assertHasEntry( nextInterceptor, opContext, msg, opContext.getDn() ); 293 294 return nextInterceptor.lookup( opContext ); 295 } 296 297 298 /** 299 * Checks to see the entry being modified exists, otherwise throws the appropriate LdapException. 300 */ 301 public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext ) 302 throws Exception 303 { 304 // check if entry to modify exists 305 String msg = "Attempt to modify non-existant entry: "; 306 307 // handle operations against the schema subentry in the schema service 308 // and never try to look it up in the nexus below 309 if ( opContext.getDn().getNormName().equalsIgnoreCase( subschemSubentryDn.getNormName() ) ) 310 { 311 nextInterceptor.modify( opContext ); 312 return; 313 } 314 315 assertHasEntry( nextInterceptor, opContext, msg, opContext.getDn() ); 316 317 ServerEntry entry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS ); 318 List<Modification> items = opContext.getModItems(); 319 320 for ( Modification item : items ) 321 { 322 if ( item.getOperation() == ModificationOperation.ADD_ATTRIBUTE ) 323 { 324 EntryAttribute modAttr = item.getAttribute(); 325 EntryAttribute entryAttr = entry.get( modAttr.getId() ); 326 327 if ( entryAttr != null ) 328 { 329 for ( Value<?> value:modAttr ) 330 { 331 if ( entryAttr.contains( value ) ) 332 { 333 throw new LdapAttributeInUseException( I18n.err( I18n.ERR_254, value, 334 modAttr.getId() ) ); 335 } 336 } 337 } 338 } 339 } 340 341 // Let's assume that the new modified entry may be an alias, 342 // but we don't want to check that now... 343 // We will simply remove the DN from the NotAlias cache. 344 // It would be smarter to check the modified attributes, but 345 // it would also be more complex. 346 synchronized( notAliasCache ) 347 { 348 if ( notAliasCache.containsKey( opContext.getDn().getNormName() ) ) 349 { 350 notAliasCache.remove( opContext.getDn().getNormName() ); 351 } 352 } 353 354 nextInterceptor.modify( opContext ); 355 } 356 357 /** 358 * Checks to see the entry being renamed exists, otherwise throws the appropriate LdapException. 359 */ 360 public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext ) 361 throws Exception 362 { 363 DN dn = opContext.getDn(); 364 365 if ( dn.equals( subschemSubentryDn ) ) 366 { 367 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_255, subschemSubentryDn, 368 subschemSubentryDn ) ); 369 } 370 371 // Check to see if the renamed entry exists 372 if ( opContext.getEntry() == null ) 373 { 374 // This is a nonsense : we can't rename an entry which does not exist 375 // on the server 376 LdapNoSuchObjectException ldnfe; 377 ldnfe = new LdapNoSuchObjectException( I18n.err( I18n.ERR_256, dn.getName() ) ); 378 //ldnfe.setResolvedName( new DN( dn.getName() ) ); 379 throw ldnfe; 380 } 381 382 // check to see if target entry exists 383 DN newDn = opContext.getNewDn(); 384 385 if ( nextInterceptor.hasEntry( new EntryOperationContext( opContext.getSession(), newDn ) ) ) 386 { 387 LdapEntryAlreadyExistsException e; 388 e = new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_257, newDn.getName() ) ); 389 //e.setResolvedName( new DN( newDn.getName() ) ); 390 throw e; 391 } 392 393 // Remove the previous entry from the notAnAlias cache 394 synchronized( notAliasCache ) 395 { 396 if ( notAliasCache.containsKey( dn.getNormName() ) ) 397 { 398 notAliasCache.remove( dn.getNormName() ); 399 } 400 } 401 402 nextInterceptor.rename( opContext ); 403 } 404 405 406 /** 407 * Checks to see the entry being moved exists, and so does its parent, otherwise throws the appropriate 408 * LdapException. 409 */ 410 public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception 411 { 412 DN oriChildName = opContext.getDn(); 413 DN newParentName = opContext.getParent(); 414 415 if ( oriChildName.getNormName().equalsIgnoreCase( subschemSubentryDn.getNormName() ) ) 416 { 417 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_258, subschemSubentryDn, 418 subschemSubentryDn ) ); 419 } 420 421 // check if child to move exists 422 String msg = "Attempt to move to non-existant parent: "; 423 assertHasEntry( nextInterceptor, opContext, msg, oriChildName ); 424 425 // check if parent to move to exists 426 msg = "Attempt to move to non-existant parent: "; 427 assertHasEntry( nextInterceptor, opContext, msg, newParentName ); 428 429 // check to see if target entry exists 430 String rdn = oriChildName.get( oriChildName.size() - 1 ); 431 DN target = ( DN ) newParentName.clone(); 432 target.add( rdn ); 433 434 if ( nextInterceptor.hasEntry( new EntryOperationContext( opContext.getSession(), target ) ) ) 435 { 436 // we must calculate the resolved name using the user provided Rdn value 437 String upRdn = new DN( oriChildName.getName() ).get( oriChildName.size() - 1 ); 438 DN upTarget = ( DN ) newParentName.clone(); 439 upTarget.add( upRdn ); 440 441 LdapEntryAlreadyExistsException e; 442 e = new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_257, upTarget.getName() ) ); 443 //e.setResolvedName( new DN( upTarget.getName() ) ); 444 throw e; 445 } 446 447 // Remove the original entry from the NotAlias cache, if needed 448 synchronized( notAliasCache ) 449 { 450 if ( notAliasCache.containsKey( oriChildName.getNormName() ) ) 451 { 452 notAliasCache.remove( oriChildName.getNormName() ); 453 } 454 } 455 456 nextInterceptor.move( opContext ); 457 } 458 459 460 /** 461 * Checks to see the entry being moved exists, and so does its parent, otherwise throws the appropriate 462 * LdapException. 463 */ 464 public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) throws Exception 465 { 466 DN oriChildName = opContext.getDn(); 467 DN parent = opContext.getParent(); 468 469 if ( oriChildName.getNormName().equalsIgnoreCase( subschemSubentryDn.getNormName() ) ) 470 { 471 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_258, subschemSubentryDn, 472 subschemSubentryDn ) ); 473 } 474 475 // check if child to move exists 476 String msg = "Attempt to move to non-existant parent: "; 477 assertHasEntry( nextInterceptor, opContext, msg, oriChildName ); 478 479 // check if parent to move to exists 480 msg = "Attempt to move to non-existant parent: "; 481 assertHasEntry( nextInterceptor, opContext, msg, parent ); 482 483 // check to see if target entry exists 484 DN target = ( DN ) parent.clone(); 485 target.add( opContext.getNewRdn() ); 486 487 if ( nextInterceptor.hasEntry( new EntryOperationContext( opContext.getSession(), target ) ) ) 488 { 489 // we must calculate the resolved name using the user provided Rdn value 490 DN upTarget = ( DN ) parent.clone(); 491 upTarget.add( opContext.getNewRdn() ); 492 493 LdapEntryAlreadyExistsException e; 494 e = new LdapEntryAlreadyExistsException( I18n.err( I18n.ERR_257, upTarget.getName() ) ); 495 //e.setResolvedName( new DN( upTarget.getName() ) ); 496 throw e; 497 } 498 499 // Remove the original entry from the NotAlias cache, if needed 500 synchronized( notAliasCache ) 501 { 502 if ( notAliasCache.containsKey( oriChildName.getNormName() ) ) 503 { 504 notAliasCache.remove( oriChildName.getNormName() ); 505 } 506 } 507 508 nextInterceptor.moveAndRename( opContext ); 509 } 510 511 512 /** 513 * Checks to see the entry being searched exists, otherwise throws the appropriate LdapException. 514 */ 515 public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception 516 { 517 DN base = opContext.getDn(); 518 519 try 520 { 521 EntryFilteringCursor cursor = nextInterceptor.search( opContext ); 522 523 if ( ! cursor.next() ) 524 { 525 if ( !base.isEmpty() && !( subschemSubentryDn.getNormName() ).equalsIgnoreCase( base.getNormName() ) ) 526 { 527 // We just check that the entry exists only if we didn't found any entry 528 assertHasEntry( nextInterceptor, opContext, "Attempt to search under non-existant entry:" , base ); 529 } 530 } 531 532 return cursor; 533 } 534 catch ( Exception ne ) 535 { 536 String msg = I18n.err( I18n.ERR_259 ); 537 assertHasEntry( nextInterceptor, opContext, msg, base ); 538 throw ne; 539 } 540 } 541 542 543 /** 544 * Asserts that an entry is present and as a side effect if it is not, creates a LdapNoSuchObjectException, which is 545 * used to set the before exception on the invocation - eventually the exception is thrown. 546 * 547 * @param msg the message to prefix to the distinguished name for explanation 548 * @param dn the distinguished name of the entry that is asserted 549 * @throws Exception if the entry does not exist 550 * @param nextInterceptor the next interceptor in the chain 551 */ 552 private void assertHasEntry( NextInterceptor nextInterceptor, OperationContext opContext, 553 String msg, DN dn ) throws Exception 554 { 555 if ( subschemSubentryDn.getNormName().equals( dn.getNormName() ) ) 556 { 557 return; 558 } 559 560 if ( ! opContext.hasEntry( dn, ByPassConstants.HAS_ENTRY_BYPASS ) ) 561 { 562 LdapNoSuchObjectException e; 563 564 if ( msg != null ) 565 { 566 e = new LdapNoSuchObjectException( msg + dn.getName() ); 567 } 568 else 569 { 570 e = new LdapNoSuchObjectException( dn.getName() ); 571 } 572 573 //e.setResolvedName( 574 // new DN( 575 // opContext.getSession().getDirectoryService().getOperationManager().getMatchedName( 576 // new GetMatchedNameOperationContext( opContext.getSession(), dn ) ).getName() ) ); 577 throw e; 578 } 579 } 580 }