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