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 * https://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.api.ldap.model.schema.registries; 021 022 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026 027import org.apache.directory.api.asn1.util.Oid; 028import org.apache.directory.api.i18n.I18n; 029import org.apache.directory.api.ldap.model.exception.LdapException; 030import org.apache.directory.api.ldap.model.exception.LdapSchemaException; 031import org.apache.directory.api.ldap.model.exception.LdapSchemaExceptionCodes; 032import org.apache.directory.api.ldap.model.schema.LoadableSchemaObject; 033import org.apache.directory.api.ldap.model.schema.SchemaErrorHandler; 034import org.apache.directory.api.ldap.model.schema.SchemaObject; 035import org.apache.directory.api.ldap.model.schema.SchemaObjectType; 036import org.apache.directory.api.util.Strings; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040 041/** 042 * Common schema object registry interface. 043 * 044 * @param <T> The type of SchemaObject 045 * 046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 047 */ 048public abstract class DefaultSchemaObjectRegistry<T extends SchemaObject> implements SchemaObjectRegistry<T>, 049 Iterable<T> 050{ 051 /** static class logger */ 052 private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaObjectRegistry.class ); 053 054 /** a map of SchemaObject looked up by name */ 055 protected Map<String, T> byName; 056 057 /** The SchemaObject type, used by the toString() method */ 058 protected SchemaObjectType schemaObjectType; 059 060 /** the global OID Registry */ 061 protected OidRegistry<T> oidRegistry; 062 063 /** A flag indicating that the Registry is relaxed or not */ 064 private boolean isRelaxed; 065 066 private SchemaErrorHandler errorHandler; 067 068 /** 069 * Creates a new DefaultSchemaObjectRegistry instance. 070 * 071 * @param schemaObjectType The Schema Object type 072 * @param oidRegistry The OID registry to use 073 */ 074 protected DefaultSchemaObjectRegistry( SchemaObjectType schemaObjectType, OidRegistry<T> oidRegistry ) 075 { 076 byName = new HashMap<>(); 077 this.schemaObjectType = schemaObjectType; 078 this.oidRegistry = oidRegistry; 079 this.isRelaxed = Registries.STRICT; 080 } 081 082 /** 083 * Tells if the Registry is permissive or if it must be checked 084 * against inconsistencies. 085 * 086 * @return True if SchemaObjects can be added even if they break the consistency 087 */ 088 public boolean isRelaxed() 089 { 090 return isRelaxed; 091 } 092 093 094 /** 095 * Tells if the Registry is strict. 096 * 097 * @return True if SchemaObjects cannot be added if they break the consistency 098 */ 099 public boolean isStrict() 100 { 101 return !isRelaxed; 102 } 103 104 105 /** 106 * Change the Registry to a relaxed mode, where invalid SchemaObjects 107 * can be registered. 108 */ 109 public void setRelaxed() 110 { 111 isRelaxed = Registries.RELAXED; 112 oidRegistry.setRelaxed(); 113 } 114 115 116 /** 117 * Change the Registry to a strict mode, where invalid SchemaObjects 118 * cannot be registered. 119 */ 120 public void setStrict() 121 { 122 isRelaxed = Registries.STRICT; 123 oidRegistry.setStrict(); 124 } 125 126 127 public SchemaErrorHandler getErrorHandler() 128 { 129 return errorHandler; 130 } 131 132 public void setErrorHandler( SchemaErrorHandler errorHandler ) 133 { 134 this.errorHandler = errorHandler; 135 oidRegistry.setErrorHandler( errorHandler ); 136 } 137 138 /** 139 * {@inheritDoc} 140 */ 141 @Override 142 public boolean contains( String oid ) 143 { 144 if ( !byName.containsKey( oid ) ) 145 { 146 return byName.containsKey( Strings.toLowerCaseAscii( oid ) ); 147 } 148 149 return true; 150 } 151 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public String getSchemaName( String oid ) throws LdapException 158 { 159 if ( !Oid.isOid( oid ) ) 160 { 161 String msg = I18n.err( I18n.ERR_13733_ARG_NOT_NUMERIC_OID ); 162 163 if ( LOG.isWarnEnabled() ) 164 { 165 LOG.warn( msg ); 166 } 167 168 throw new LdapException( msg ); 169 } 170 171 SchemaObject schemaObject = byName.get( oid ); 172 173 if ( schemaObject != null ) 174 { 175 return schemaObject.getSchemaName(); 176 } 177 178 String msg = I18n.err( I18n.ERR_13734_OID_NOT_FOUND, oid ); 179 180 if ( LOG.isWarnEnabled() ) 181 { 182 LOG.warn( msg ); 183 } 184 185 throw new LdapException( msg ); 186 } 187 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override 193 public void renameSchema( String originalSchemaName, String newSchemaName ) 194 { 195 // Loop on all the SchemaObjects stored and remove those associated 196 // with the give schemaName 197 for ( T schemaObject : this ) 198 { 199 if ( originalSchemaName.equalsIgnoreCase( schemaObject.getSchemaName() ) ) 200 { 201 schemaObject.setSchemaName( newSchemaName ); 202 203 if ( LOG.isDebugEnabled() ) 204 { 205 LOG.debug( I18n.msg( I18n.MSG_13722_RENAMED_SCHEMA_NAME_TO, schemaObject, newSchemaName ) ); 206 } 207 } 208 } 209 } 210 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public Iterator<T> iterator() 217 { 218 return oidRegistry.iterator(); 219 } 220 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public Iterator<String> oidsIterator() 227 { 228 return byName.keySet().iterator(); 229 } 230 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public T lookup( String oid ) throws LdapException 237 { 238 if ( oid == null ) 239 { 240 return null; 241 } 242 243 T schemaObject = byName.get( oid ); 244 245 if ( schemaObject == null ) 246 { 247 // let's try with trimming and lowercasing now 248 schemaObject = byName.get( Strings.trim( Strings.toLowerCaseAscii( oid ) ) ); 249 250 if ( schemaObject == null ) 251 { 252 String msg = I18n.err( I18n.ERR_13735_ELEMENT_FOR_OID_DOES_NOT_EXIST, schemaObjectType.name(), oid ); 253 254 if ( LOG.isDebugEnabled() ) 255 { 256 LOG.debug( msg ); 257 } 258 259 throw new LdapException( msg ); 260 } 261 } 262 263 if ( LOG.isDebugEnabled() ) 264 { 265 LOG.debug( I18n.msg( I18n.MSG_13723_FOUND_WITH_OID, schemaObject, oid ) ); 266 } 267 268 return schemaObject; 269 } 270 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 public void register( T schemaObject ) throws LdapException 277 { 278 String oid = schemaObject.getOid(); 279 280 if ( byName.containsKey( oid ) ) 281 { 282 String msg = I18n.err( I18n.ERR_13736_ELEMENT_FOR_OID_ALREADY_REGISTERED, schemaObjectType.name(), oid ); 283 284 if ( LOG.isWarnEnabled() ) 285 { 286 LOG.warn( msg ); 287 } 288 289 LdapSchemaException ldapSchemaException = new LdapSchemaException( 290 LdapSchemaExceptionCodes.OID_ALREADY_REGISTERED, msg ); 291 ldapSchemaException.setSourceObject( schemaObject ); 292 throw ldapSchemaException; 293 } 294 295 byName.put( oid, schemaObject ); 296 297 /* 298 * add the aliases/names to the name map along with their toLowerCase 299 * versions of the name: this is used to make sure name lookups work 300 */ 301 for ( String name : schemaObject.getNames() ) 302 { 303 String lowerName = Strings.trim( Strings.toLowerCaseAscii( name ) ); 304 305 if ( byName.containsKey( lowerName ) ) 306 { 307 String msg = I18n.err( I18n.ERR_13737_ELEMENT_WITH_NAME_ALREADY_REGISTERED, schemaObjectType.name(), name ); 308 309 if ( LOG.isWarnEnabled() ) 310 { 311 LOG.warn( msg ); 312 } 313 314 LdapSchemaException ldapSchemaException = new LdapSchemaException( 315 LdapSchemaExceptionCodes.NAME_ALREADY_REGISTERED, msg ); 316 ldapSchemaException.setSourceObject( schemaObject ); 317 throw ldapSchemaException; 318 } 319 else 320 { 321 byName.put( lowerName, schemaObject ); 322 } 323 } 324 325 // And register the oid -> schemaObject relation 326 oidRegistry.register( schemaObject ); 327 328 if ( LOG.isDebugEnabled() ) 329 { 330 LOG.debug( I18n.msg( I18n.MSG_13731_REGISTRED_FOR_OID, schemaObject.getName(), oid ) ); 331 } 332 } 333 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override 339 public T unregister( String numericOid ) throws LdapException 340 { 341 if ( !Oid.isOid( numericOid ) ) 342 { 343 String msg = I18n.err( I18n.ERR_13738_OID_NOT_A_NUMERIC_OID, numericOid ); 344 LOG.error( msg ); 345 throw new LdapException( msg ); 346 } 347 348 T schemaObject = byName.remove( numericOid ); 349 350 for ( String name : schemaObject.getNames() ) 351 { 352 byName.remove( name ); 353 } 354 355 // And remove the SchemaObject from the oidRegistry 356 oidRegistry.unregister( numericOid ); 357 358 if ( LOG.isDebugEnabled() ) 359 { 360 LOG.debug( I18n.msg( I18n.MSG_13702_REMOVED_FROM_REGISTRY, schemaObject, numericOid ) ); 361 } 362 363 return schemaObject; 364 } 365 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override 371 public T unregister( T schemaObject ) throws LdapException 372 { 373 String oid = schemaObject.getOid(); 374 375 if ( !byName.containsKey( oid ) ) 376 { 377 String msg = I18n.err( I18n.ERR_13739_ELEMENT_WITH_OID_NOT_REGISTERED, schemaObjectType.name(), oid ); 378 379 if ( LOG.isWarnEnabled() ) 380 { 381 LOG.warn( msg ); 382 } 383 384 throw new LdapException( msg ); 385 } 386 387 // Remove the oid 388 T removed = byName.remove( oid ); 389 390 /* 391 * Remove the aliases/names from the name map along with their toLowerCase 392 * versions of the name. 393 */ 394 for ( String name : schemaObject.getNames() ) 395 { 396 byName.remove( Strings.trim( Strings.toLowerCaseAscii( name ) ) ); 397 } 398 399 // And unregister the oid -> schemaObject relation 400 oidRegistry.unregister( oid ); 401 402 return removed; 403 } 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override 410 public void unregisterSchemaElements( String schemaName ) throws LdapException 411 { 412 if ( schemaName == null ) 413 { 414 return; 415 } 416 417 // Loop on all the SchemaObjects stored and remove those associated 418 // with the give schemaName 419 for ( T schemaObject : this ) 420 { 421 if ( schemaName.equalsIgnoreCase( schemaObject.getSchemaName() ) ) 422 { 423 String oid = schemaObject.getOid(); 424 SchemaObject removed = unregister( oid ); 425 426 if ( LOG.isDebugEnabled() ) 427 { 428 LOG.debug( I18n.msg( I18n.MSG_13702_REMOVED_FROM_REGISTRY, removed, oid ) ); 429 } 430 } 431 } 432 } 433 434 435 /** 436 * {@inheritDoc} 437 */ 438 @Override 439public String getOidByName( String name ) throws LdapException 440 { 441 T schemaObject = byName.get( name ); 442 443 if ( schemaObject == null ) 444 { 445 // last resort before giving up check with lower cased version 446 String lowerCased = Strings.toLowerCaseAscii( name ); 447 448 schemaObject = byName.get( lowerCased ); 449 450 // ok this name is not for a schema object in the registry 451 if ( schemaObject == null ) 452 { 453 throw new LdapException( I18n.err( I18n.ERR_13740_CANNOT_FIND_OID_FROM_NAME, name ) ); 454 } 455 } 456 457 // we found the schema object by key on the first lookup attempt 458 return schemaObject.getOid(); 459 } 460 461 462 /** 463 * Copy a SchemaObject registry 464 * 465 * @param original The SchemaObject registry to copy 466 * @return The copied ShcemaObject registry 467 */ 468 // This will suppress PMD.EmptyCatchBlock warnings in this method 469 @SuppressWarnings("unchecked") 470 public SchemaObjectRegistry<T> copy( SchemaObjectRegistry<T> original ) 471 { 472 // Fill the byName and OidRegistry maps, the type has already be copied 473 for ( Map.Entry<String, T> entry : ( ( DefaultSchemaObjectRegistry<T> ) original ).byName.entrySet() ) 474 { 475 String key = entry.getKey(); 476 // Clone each SchemaObject 477 T value = entry.getValue(); 478 479 if ( value instanceof LoadableSchemaObject ) 480 { 481 // Update the data structure. 482 // Comparators, Normalizers and SyntaxCheckers aren't copied, 483 // they are immutable 484 byName.put( key, value ); 485 486 // Update the OidRegistry 487 oidRegistry.put( value ); 488 } 489 else 490 { 491 T copiedValue = null; 492 493 // Copy the value if it's not already in the oidRegistry 494 if ( oidRegistry.contains( value.getOid() ) ) 495 { 496 try 497 { 498 copiedValue = oidRegistry.getSchemaObject( value.getOid() ); 499 } 500 catch ( LdapException ne ) 501 { 502 // Can't happen 503 } 504 } 505 else 506 { 507 copiedValue = ( T ) value.copy(); 508 } 509 510 // Update the data structure. 511 byName.put( key, copiedValue ); 512 513 // Update the OidRegistry 514 oidRegistry.put( copiedValue ); 515 } 516 } 517 518 return this; 519 } 520 521 522 /** 523 * {@inheritDoc} 524 */ 525 @Override 526 public T get( String oid ) 527 { 528 try 529 { 530 return oidRegistry.getSchemaObject( oid ); 531 } 532 catch ( LdapException ne ) 533 { 534 return null; 535 } 536 } 537 538 539 /** 540 * {@inheritDoc} 541 */ 542 @Override 543 public SchemaObjectType getType() 544 { 545 return schemaObjectType; 546 } 547 548 549 /** 550 * {@inheritDoc} 551 */ 552 @Override 553 public int size() 554 { 555 return oidRegistry.size(); 556 } 557 558 559 /** 560 * @see Object#toString() 561 */ 562 @Override 563 public String toString() 564 { 565 StringBuilder sb = new StringBuilder(); 566 567 sb.append( schemaObjectType ).append( ": " ); 568 boolean isFirst = true; 569 570 for ( Map.Entry<String, T> entry : byName.entrySet() ) 571 { 572 if ( isFirst ) 573 { 574 isFirst = false; 575 } 576 else 577 { 578 sb.append( ", " ); 579 } 580 581 String name = entry.getKey(); 582 T schemaObject = entry.getValue(); 583 584 sb.append( '<' ).append( name ).append( ", " ).append( schemaObject.getOid() ).append( '>' ); 585 } 586 587 return sb.toString(); 588 } 589 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override 595 public void clear() 596 { 597 // Clear all the schemaObjects 598 for ( SchemaObject schemaObject : oidRegistry ) 599 { 600 // Don't clear LoadableSchemaObject 601 if ( !( schemaObject instanceof LoadableSchemaObject ) ) 602 { 603 schemaObject.clear(); 604 } 605 } 606 607 // Remove the byName elements 608 byName.clear(); 609 610 // Clear the OidRegistry 611 oidRegistry.clear(); 612 } 613}