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.schema.loader; 021 022 023import java.lang.reflect.Constructor; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.security.AccessController; 027import java.security.PrivilegedAction; 028import java.util.ArrayList; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033import org.apache.directory.api.asn1.util.Oid; 034import org.apache.directory.api.i18n.I18n; 035import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants; 036import org.apache.directory.api.ldap.model.constants.SchemaConstants; 037import org.apache.directory.api.ldap.model.entry.Attribute; 038import org.apache.directory.api.ldap.model.entry.BinaryValue; 039import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 040import org.apache.directory.api.ldap.model.entry.Entry; 041import org.apache.directory.api.ldap.model.entry.Value; 042import org.apache.directory.api.ldap.model.exception.LdapException; 043import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 044import org.apache.directory.api.ldap.model.exception.LdapSchemaException; 045import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; 046import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 047import org.apache.directory.api.ldap.model.schema.AttributeType; 048import org.apache.directory.api.ldap.model.schema.LdapComparator; 049import org.apache.directory.api.ldap.model.schema.LdapSyntax; 050import org.apache.directory.api.ldap.model.schema.LoadableSchemaObject; 051import org.apache.directory.api.ldap.model.schema.MatchingRule; 052import org.apache.directory.api.ldap.model.schema.MutableAttributeType; 053import org.apache.directory.api.ldap.model.schema.MutableMatchingRule; 054import org.apache.directory.api.ldap.model.schema.MutableObjectClass; 055import org.apache.directory.api.ldap.model.schema.Normalizer; 056import org.apache.directory.api.ldap.model.schema.ObjectClass; 057import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum; 058import org.apache.directory.api.ldap.model.schema.SchemaManager; 059import org.apache.directory.api.ldap.model.schema.SchemaObject; 060import org.apache.directory.api.ldap.model.schema.SyntaxChecker; 061import org.apache.directory.api.ldap.model.schema.SyntaxChecker.SCBuilder; 062import org.apache.directory.api.ldap.model.schema.UsageEnum; 063import org.apache.directory.api.ldap.model.schema.parsers.LdapComparatorDescription; 064import org.apache.directory.api.ldap.model.schema.parsers.NormalizerDescription; 065import org.apache.directory.api.ldap.model.schema.parsers.SyntaxCheckerDescription; 066import org.apache.directory.api.ldap.model.schema.registries.DefaultSchema; 067import org.apache.directory.api.ldap.model.schema.registries.Registries; 068import org.apache.directory.api.ldap.model.schema.registries.Schema; 069import org.apache.directory.api.util.Base64; 070import org.apache.directory.api.util.StringConstants; 071import org.apache.directory.api.util.Strings; 072import org.slf4j.Logger; 073import org.slf4j.LoggerFactory; 074 075 076/** 077 * Showing how it's done ... 078 * 079 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 080 */ 081public class SchemaEntityFactory implements EntityFactory 082{ 083 /** Slf4j logger */ 084 private static final Logger LOG = LoggerFactory.getLogger( SchemaEntityFactory.class ); 085 086 /** The empty string list. */ 087 private static final List<String> EMPTY_LIST = new ArrayList<>(); 088 089 /** The empty string array. */ 090 private static final String[] EMPTY_ARRAY = new String[] 091 {}; 092 093 /** A special ClassLoader that loads a class from the bytecode attribute */ 094 private final AttributeClassLoader classLoader; 095 096 097 /** 098 * Instantiates a new schema entity factory. 099 */ 100 public SchemaEntityFactory() 101 { 102 this.classLoader = AccessController.doPrivileged( new PrivilegedAction<AttributeClassLoader>() 103 { 104 @Override 105 public AttributeClassLoader run() 106 { 107 return new AttributeClassLoader(); 108 } 109 } ); 110 } 111 112 113 /** 114 * Get an OID from an entry. Handles the bad cases (null OID, 115 * not a valid OID, ...) 116 */ 117 private String getOid( Entry entry, String objectType, boolean strict ) throws LdapInvalidAttributeValueException 118 { 119 // The OID 120 Attribute mOid = entry.get( MetaSchemaConstants.M_OID_AT ); 121 122 if ( mOid == null ) 123 { 124 String msg = I18n.err( I18n.ERR_10005, objectType, MetaSchemaConstants.M_OID_AT ); 125 LOG.warn( msg ); 126 throw new IllegalArgumentException( msg ); 127 } 128 129 String oid = mOid.getString(); 130 131 if ( strict && !Oid.isOid( oid ) ) 132 { 133 String msg = I18n.err( I18n.ERR_10006, oid ); 134 LOG.warn( msg ); 135 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg ); 136 } 137 138 return oid; 139 } 140 141 142 /** 143 * Get an OID from an entry. Handles the bad cases (null OID, 144 * not a valid OID, ...) 145 */ 146 private String getOid( SchemaObject description, String objectType ) throws LdapInvalidAttributeValueException 147 { 148 // The OID 149 String oid = description.getOid(); 150 151 if ( oid == null ) 152 { 153 String msg = I18n.err( I18n.ERR_10005, objectType, MetaSchemaConstants.M_OID_AT ); 154 LOG.warn( msg ); 155 throw new IllegalArgumentException( msg ); 156 } 157 158 if ( !Oid.isOid( oid ) ) 159 { 160 String msg = I18n.err( I18n.ERR_10006, oid ); 161 LOG.warn( msg ); 162 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg ); 163 } 164 165 return oid; 166 } 167 168 169 /** 170 * Check that the Entry is not null 171 */ 172 private void checkEntry( Entry entry, String schemaEntity ) 173 { 174 if ( entry == null ) 175 { 176 String msg = I18n.err( I18n.ERR_10007, schemaEntity ); 177 LOG.warn( msg ); 178 throw new IllegalArgumentException( msg ); 179 } 180 } 181 182 183 /** 184 * Check that the Description is not null 185 */ 186 private void checkDescription( SchemaObject description, String schemaEntity ) 187 { 188 if ( description == null ) 189 { 190 String msg = I18n.err( I18n.ERR_10008, schemaEntity ); 191 LOG.warn( msg ); 192 throw new IllegalArgumentException( msg ); 193 } 194 } 195 196 197 /** 198 * Get the schema from its name. Return the Other reference if there 199 * is no schema name. Throws a NPE if the schema is not loaded. 200 */ 201 private Schema getSchema( String schemaName, Registries registries ) 202 { 203 if ( Strings.isEmpty( schemaName ) ) 204 { 205 schemaName = MetaSchemaConstants.SCHEMA_OTHER; 206 } 207 208 Schema schema = registries.getLoadedSchema( schemaName ); 209 210 if ( schema == null ) 211 { 212 String msg = I18n.err( I18n.ERR_10009, schemaName ); 213 LOG.error( msg ); 214 } 215 216 return schema; 217 } 218 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override 224 public Schema getSchema( Entry entry ) throws LdapException 225 { 226 String name; 227 String owner; 228 String[] dependencies = EMPTY_ARRAY; 229 boolean isDisabled = false; 230 231 if ( entry == null ) 232 { 233 throw new IllegalArgumentException( I18n.err( I18n.ERR_10010 ) ); 234 } 235 236 if ( entry.get( SchemaConstants.CN_AT ) == null ) 237 { 238 throw new IllegalArgumentException( I18n.err( I18n.ERR_10011 ) ); 239 } 240 241 name = entry.get( SchemaConstants.CN_AT ).getString(); 242 243 if ( entry.get( SchemaConstants.CREATORS_NAME_AT ) == null ) 244 { 245 throw new IllegalArgumentException( I18n.err( I18n.ERR_10012, SchemaConstants.CREATORS_NAME_AT ) ); 246 } 247 248 owner = entry.get( SchemaConstants.CREATORS_NAME_AT ).getString(); 249 250 if ( entry.get( MetaSchemaConstants.M_DISABLED_AT ) != null ) 251 { 252 String value = entry.get( MetaSchemaConstants.M_DISABLED_AT ).getString(); 253 value = Strings.upperCase( value ); 254 isDisabled = "TRUE".equalsIgnoreCase( value ); 255 } 256 257 if ( entry.get( MetaSchemaConstants.M_DEPENDENCIES_AT ) != null ) 258 { 259 Set<String> depsSet = new HashSet<>(); 260 Attribute depsAttr = entry.get( MetaSchemaConstants.M_DEPENDENCIES_AT ); 261 262 for ( Value<?> value : depsAttr ) 263 { 264 depsSet.add( value.getString() ); 265 } 266 267 dependencies = depsSet.toArray( EMPTY_ARRAY ); 268 } 269 270 return new DefaultSchema( null, name, owner, dependencies, isDisabled ); 271 } 272 273 274 /** 275 * Class load a syntaxChecker instance 276 */ 277 private SyntaxChecker classLoadSyntaxChecker( SchemaManager schemaManager, String oid, String className, 278 Attribute byteCode ) throws LdapException 279 { 280 // Try to class load the syntaxChecker 281 Class<?> clazz; 282 SyntaxChecker syntaxChecker; 283 String byteCodeStr = StringConstants.EMPTY; 284 285 if ( byteCode == null ) 286 { 287 try 288 { 289 clazz = Class.forName( className ); 290 } 291 catch ( ClassNotFoundException cnfe ) 292 { 293 LOG.error( "Cannot find the syntax checker class constructor for class {}", className ); 294 throw new LdapSchemaException( "Cannot find the syntax checker class " + cnfe.getMessage() ); 295 } 296 } 297 else 298 { 299 classLoader.setAttribute( byteCode ); 300 301 try 302 { 303 clazz = classLoader.loadClass( className ); 304 } 305 catch ( ClassNotFoundException cnfe ) 306 { 307 LOG.error( "Cannot load the syntax checker class constructor for class {}", className ); 308 throw new LdapSchemaException( "Cannot load the syntax checker class " + cnfe.getMessage() ); 309 } 310 311 312 byteCodeStr = new String( Base64.encode( byteCode.getBytes() ) ); 313 } 314 315 // Create the syntaxChecker instance 316 try 317 { 318 Method builder = clazz.getMethod( "builder", null ); 319 syntaxChecker = ( SyntaxChecker ) ( ( SCBuilder ) builder.invoke( null, null ) ).setOid( oid ).build(); 320 } 321 catch ( NoSuchMethodException nsme ) 322 { 323 LOG.error( "Cannot instantiate the syntax checker class constructor for class {}", className ); 324 throw new LdapSchemaException( "Cannot instantiate the syntax checker class " + nsme.getMessage() ); 325 } 326 catch ( InvocationTargetException ite ) 327 { 328 LOG.error( "Cannot instantiate the syntax checker class constructor for class {}", className ); 329 throw new LdapSchemaException( "Cannot instantiate the syntax checker class " + ite.getMessage() ); 330 } 331 catch ( IllegalAccessException iae ) 332 { 333 LOG.error( "Cannot access the syntax checker class constructor for class {}", className ); 334 throw new LdapSchemaException( "Cannot access the syntax checker class constructor " + iae.getMessage() ); 335 } 336 337 // Update the common fields 338 syntaxChecker.setBytecode( byteCodeStr ); 339 syntaxChecker.setFqcn( className ); 340 341 // Inject the SchemaManager for the comparator who needs it 342 syntaxChecker.setSchemaManager( schemaManager ); 343 344 return syntaxChecker; 345 } 346 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override 352 public SyntaxChecker getSyntaxChecker( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 353 String schemaName ) throws LdapException 354 { 355 checkEntry( entry, SchemaConstants.SYNTAX_CHECKER ); 356 357 // The SyntaxChecker OID 358 String oid = getOid( entry, SchemaConstants.SYNTAX_CHECKER, schemaManager.isStrict() ); 359 360 // Get the schema 361 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 362 { 363 // The schema is not loaded. We can't create the requested Normalizer 364 String msg = I18n.err( I18n.ERR_10013, entry.getDn().getName(), schemaName ); 365 LOG.warn( msg ); 366 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 367 } 368 369 Schema schema = getSchema( schemaName, targetRegistries ); 370 371 if ( schema == null ) 372 { 373 // The schema is disabled. We still have to update the backend 374 String msg = I18n.err( I18n.ERR_10014, entry.getDn().getName(), schemaName ); 375 LOG.info( msg ); 376 schema = schemaManager.getLoadedSchema( schemaName ); 377 } 378 379 // The FQCN 380 String className = getFqcn( entry, SchemaConstants.SYNTAX_CHECKER ); 381 382 // The ByteCode 383 Attribute byteCode = entry.get( MetaSchemaConstants.M_BYTECODE_AT ); 384 385 try 386 { 387 // Class load the syntaxChecker 388 SyntaxChecker syntaxChecker = classLoadSyntaxChecker( schemaManager, oid, className, byteCode ); 389 390 // Update the common fields 391 setSchemaObjectProperties( syntaxChecker, entry, schema ); 392 393 // return the resulting syntaxChecker 394 return syntaxChecker; 395 } 396 catch ( Exception e ) 397 { 398 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, e.getMessage(), e ); 399 } 400 } 401 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public SyntaxChecker getSyntaxChecker( SchemaManager schemaManager, 408 SyntaxCheckerDescription syntaxCheckerDescription, Registries targetRegistries, String schemaName ) 409 throws LdapException 410 { 411 checkDescription( syntaxCheckerDescription, SchemaConstants.SYNTAX_CHECKER ); 412 413 // The Comparator OID 414 String oid = getOid( syntaxCheckerDescription, SchemaConstants.SYNTAX_CHECKER ); 415 416 // Get the schema 417 Schema schema = getSchema( schemaName, targetRegistries ); 418 419 if ( schema == null ) 420 { 421 // The schema is not loaded. We can't create the requested SyntaxChecker 422 String msg = I18n.err( I18n.ERR_10013, syntaxCheckerDescription.getName(), schemaName ); 423 LOG.warn( msg ); 424 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 425 } 426 427 // The FQCN 428 String fqcn = getFqcn( syntaxCheckerDescription, SchemaConstants.SYNTAX_CHECKER ); 429 430 // get the byteCode 431 Attribute byteCode = getByteCode( syntaxCheckerDescription, SchemaConstants.SYNTAX_CHECKER ); 432 433 // Class load the SyntaxChecker 434 SyntaxChecker syntaxChecker = classLoadSyntaxChecker( schemaManager, oid, fqcn, byteCode ); 435 436 // Update the common fields 437 setSchemaObjectProperties( syntaxChecker, syntaxCheckerDescription, schema ); 438 439 return syntaxChecker; 440 } 441 442 443 /** 444 * Class load a comparator instances 445 */ 446 private LdapComparator<?> classLoadComparator( SchemaManager schemaManager, String oid, String className, 447 Attribute byteCode ) throws LdapException 448 { 449 // Try to class load the comparator 450 LdapComparator<?> comparator; 451 Class<?> clazz; 452 String byteCodeStr = StringConstants.EMPTY; 453 454 if ( byteCode == null ) 455 { 456 try 457 { 458 clazz = Class.forName( className ); 459 } 460 catch ( ClassNotFoundException cnfe ) 461 { 462 LOG.error( "Cannot find the comparator class constructor for class {}", className ); 463 throw new LdapSchemaException( "Cannot find the comparator class " + cnfe.getMessage() ); 464 } 465 } 466 else 467 { 468 classLoader.setAttribute( byteCode ); 469 470 try 471 { 472 clazz = classLoader.loadClass( className ); 473 } 474 catch ( ClassNotFoundException cnfe ) 475 { 476 LOG.error( "Cannot load the comparator class constructor for class {}", className ); 477 throw new LdapSchemaException( "Cannot load the comparator class " + cnfe.getMessage() ); 478 } 479 480 byteCodeStr = new String( Base64.encode( byteCode.getBytes() ) ); 481 } 482 483 // Create the comparator instance. Either we have a no argument constructor, 484 // or we have one which takes an OID. Lets try the one with an OID argument first 485 try 486 { 487 Constructor<?> constructor = clazz.getConstructor( new Class[] 488 { String.class } ); 489 490 try 491 { 492 comparator = ( LdapComparator<?> ) constructor.newInstance( new Object[] 493 { oid } ); 494 } 495 catch ( InvocationTargetException ite ) 496 { 497 LOG.error( "Cannot invoke the comparator class constructor for class {}", className ); 498 throw new LdapSchemaException( "Cannot invoke the comparator class " + ite.getMessage() ); 499 } 500 catch ( InstantiationException ie ) 501 { 502 LOG.error( "Cannot instanciate the comparator class constructor for class {}", className ); 503 throw new LdapSchemaException( "Cannot instanciate the comparator class " + ie.getMessage() ); 504 } 505 catch ( IllegalAccessException ie ) 506 { 507 LOG.error( "Cannot access the comparator class constructor for class {}", className ); 508 throw new LdapSchemaException( "Cannot access the comparator class " + ie.getMessage() ); 509 } 510 } 511 catch ( NoSuchMethodException nsme ) 512 { 513 // Ok, let's try with the constructor without argument. 514 // In this case, we will have to check that the OID is the same than 515 // the one we got in the Comparator entry 516 try 517 { 518 clazz.getConstructor(); 519 } 520 catch ( NoSuchMethodException nsme2 ) 521 { 522 LOG.error( "Cannot find the comparator class constructor method for class {}", className ); 523 throw new LdapSchemaException( "Cannot find the comparator class constructor method" + nsme2.getMessage() ); 524 } 525 526 try 527 { 528 comparator = ( LdapComparator<?> ) clazz.newInstance(); 529 } 530 catch ( InstantiationException ie ) 531 { 532 LOG.error( "Cannot instantiate the comparator class constructor for class {}", className ); 533 throw new LdapSchemaException( "Cannot instantiate the comparator class " + ie.getMessage() ); 534 } 535 catch ( IllegalAccessException iae ) 536 { 537 LOG.error( "Cannot access the comparator class constructor for class {}", className ); 538 throw new LdapSchemaException( "Cannot access the comparator class constructor " + iae.getMessage() ); 539 } 540 541 if ( !comparator.getOid().equals( oid ) ) 542 { 543 String msg = I18n.err( I18n.ERR_10015, oid, comparator.getOid() ); 544 throw new LdapInvalidAttributeValueException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg, nsme ); 545 } 546 } 547 548 // Update the loadable fields 549 comparator.setBytecode( byteCodeStr ); 550 comparator.setFqcn( className ); 551 552 // Inject the SchemaManager for the comparator who needs it 553 comparator.setSchemaManager( schemaManager ); 554 555 return comparator; 556 } 557 558 559 /** 560 * {@inheritDoc} 561 */ 562 @Override 563 public LdapComparator<?> getLdapComparator( SchemaManager schemaManager, 564 LdapComparatorDescription comparatorDescription, Registries targetRegistries, String schemaName ) 565 throws LdapException 566 { 567 checkDescription( comparatorDescription, SchemaConstants.COMPARATOR ); 568 569 // The Comparator OID 570 String oid = getOid( comparatorDescription, SchemaConstants.COMPARATOR ); 571 572 // Get the schema 573 Schema schema = getSchema( schemaName, targetRegistries ); 574 575 if ( schema == null ) 576 { 577 // The schema is not loaded. We can't create the requested Comparator 578 String msg = I18n.err( I18n.ERR_10016, comparatorDescription.getName(), schemaName ); 579 LOG.warn( msg ); 580 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 581 } 582 583 // The FQCN 584 String fqcn = getFqcn( comparatorDescription, SchemaConstants.COMPARATOR ); 585 586 // get the byteCode 587 Attribute byteCode = getByteCode( comparatorDescription, SchemaConstants.COMPARATOR ); 588 589 // Class load the comparator 590 LdapComparator<?> comparator = classLoadComparator( schemaManager, oid, fqcn, byteCode ); 591 592 // Update the common fields 593 setSchemaObjectProperties( comparator, comparatorDescription, schema ); 594 595 return comparator; 596 } 597 598 599 /** 600 * {@inheritDoc} 601 */ 602 @Override 603 public LdapComparator<?> getLdapComparator( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 604 String schemaName ) throws LdapException 605 { 606 checkEntry( entry, SchemaConstants.COMPARATOR ); 607 608 // The Comparator OID 609 String oid = getOid( entry, SchemaConstants.COMPARATOR, schemaManager.isStrict() ); 610 611 // Get the schema 612 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 613 { 614 // The schema is not loaded. We can't create the requested Comparator 615 String msg = I18n.err( I18n.ERR_10016, entry.getDn().getName(), schemaName ); 616 LOG.warn( msg ); 617 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 618 } 619 620 Schema schema = getSchema( schemaName, targetRegistries ); 621 622 if ( schema == null ) 623 { 624 // The schema is disabled. We still have to update the backend 625 String msg = I18n.err( I18n.ERR_10017, entry.getDn().getName(), schemaName ); 626 LOG.info( msg ); 627 schema = schemaManager.getLoadedSchema( schemaName ); 628 } 629 630 // The FQCN 631 String fqcn = getFqcn( entry, SchemaConstants.COMPARATOR ); 632 633 // The ByteCode 634 Attribute byteCode = entry.get( MetaSchemaConstants.M_BYTECODE_AT ); 635 636 try 637 { 638 // Class load the comparator 639 LdapComparator<?> comparator = classLoadComparator( schemaManager, oid, fqcn, byteCode ); 640 641 // Update the common fields 642 setSchemaObjectProperties( comparator, entry, schema ); 643 644 // return the resulting comparator 645 return comparator; 646 } 647 catch ( Exception e ) 648 { 649 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, e.getMessage(), e ); 650 } 651 } 652 653 654 /** 655 * Class load a normalizer instances 656 */ 657 private Normalizer classLoadNormalizer( SchemaManager schemaManager, String oid, String className, 658 Attribute byteCode ) throws LdapException 659 { 660 // Try to class load the normalizer 661 Class<?> clazz; 662 Normalizer normalizer; 663 String byteCodeStr = StringConstants.EMPTY; 664 665 if ( byteCode == null ) 666 { 667 try 668 { 669 clazz = Class.forName( className ); 670 } 671 catch ( ClassNotFoundException cnfe ) 672 { 673 LOG.error( "Cannot find the normalizer class constructor for class {}", className ); 674 throw new LdapSchemaException( "Cannot find the normalizer class " + cnfe.getMessage() ); 675 } 676 } 677 else 678 { 679 classLoader.setAttribute( byteCode ); 680 681 try 682 { 683 clazz = classLoader.loadClass( className ); 684 } 685 catch ( ClassNotFoundException cnfe ) 686 { 687 LOG.error( "Cannot load the normalizer class constructor for class {}", className ); 688 throw new LdapSchemaException( "Cannot load the normalizer class " + cnfe.getMessage() ); 689 } 690 691 byteCodeStr = new String( Base64.encode( byteCode.getBytes() ) ); 692 } 693 694 // Create the normalizer instance 695 try 696 { 697 normalizer = ( Normalizer ) clazz.newInstance(); 698 } 699 catch ( InstantiationException ie ) 700 { 701 LOG.error( "Cannot instantiate the normalizer class constructor for class {}", className ); 702 throw new LdapSchemaException( "Cannot instantiate the normalizer class " + ie.getMessage() ); 703 } 704 catch ( IllegalAccessException iae ) 705 { 706 LOG.error( "Cannot access the normalizer class constructor for class {}", className ); 707 throw new LdapSchemaException( "Cannot access the normalizer class constructor " + iae.getMessage() ); 708 } 709 710 // Update the common fields 711 normalizer.setBytecode( byteCodeStr ); 712 normalizer.setFqcn( className ); 713 714 // Inject the new OID, as the loaded normalizer might have its own 715 normalizer.setOid( oid ); 716 717 // Inject the SchemaManager for the normalizer who needs it 718 normalizer.setSchemaManager( schemaManager ); 719 720 return normalizer; 721 } 722 723 724 /** 725 * {@inheritDoc} 726 */ 727 @Override 728 public Normalizer getNormalizer( SchemaManager schemaManager, NormalizerDescription normalizerDescription, 729 Registries targetRegistries, String schemaName ) throws LdapException 730 { 731 checkDescription( normalizerDescription, SchemaConstants.NORMALIZER ); 732 733 // The Comparator OID 734 String oid = getOid( normalizerDescription, SchemaConstants.NORMALIZER ); 735 736 // Get the schema 737 Schema schema = getSchema( schemaName, targetRegistries ); 738 739 if ( schema == null ) 740 { 741 // The schema is not loaded. We can't create the requested Normalizer 742 String msg = I18n.err( I18n.ERR_10018, normalizerDescription.getName(), schemaName ); 743 LOG.warn( msg ); 744 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 745 } 746 747 // The FQCN 748 String fqcn = getFqcn( normalizerDescription, SchemaConstants.NORMALIZER ); 749 750 // get the byteCode 751 Attribute byteCode = getByteCode( normalizerDescription, SchemaConstants.NORMALIZER ); 752 753 // Class load the normalizer 754 Normalizer normalizer = classLoadNormalizer( schemaManager, oid, fqcn, byteCode ); 755 756 // Update the common fields 757 setSchemaObjectProperties( normalizer, normalizerDescription, schema ); 758 759 return normalizer; 760 } 761 762 763 /** 764 * {@inheritDoc} 765 */ 766 @Override 767 public Normalizer getNormalizer( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 768 String schemaName ) throws LdapException 769 { 770 checkEntry( entry, SchemaConstants.NORMALIZER ); 771 772 // The Normalizer OID 773 String oid = getOid( entry, SchemaConstants.NORMALIZER, schemaManager.isStrict() ); 774 775 // Get the schema 776 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 777 { 778 // The schema is not loaded. We can't create the requested Normalizer 779 String msg = I18n.err( I18n.ERR_10018, entry.getDn().getName(), schemaName ); 780 LOG.warn( msg ); 781 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 782 } 783 784 Schema schema = getSchema( schemaName, targetRegistries ); 785 786 if ( schema == null ) 787 { 788 // The schema is disabled. We still have to update the backend 789 String msg = I18n.err( I18n.ERR_10019, entry.getDn().getName(), schemaName ); 790 LOG.info( msg ); 791 schema = schemaManager.getLoadedSchema( schemaName ); 792 } 793 794 // The FQCN 795 String className = getFqcn( entry, SchemaConstants.NORMALIZER ); 796 797 // The ByteCode 798 Attribute byteCode = entry.get( MetaSchemaConstants.M_BYTECODE_AT ); 799 800 try 801 { 802 // Class load the Normalizer 803 Normalizer normalizer = classLoadNormalizer( schemaManager, oid, className, byteCode ); 804 805 // Update the common fields 806 setSchemaObjectProperties( normalizer, entry, schema ); 807 808 // return the resulting Normalizer 809 return normalizer; 810 } 811 catch ( Exception e ) 812 { 813 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, e.getMessage(), e ); 814 } 815 } 816 817 818 /** 819 * {@inheritDoc} 820 * @throws LdapInvalidAttributeValueException If the Syntax does not exist 821 * @throws LdapUnwillingToPerformException If the schema is not loaded 822 */ 823 @Override 824 public LdapSyntax getSyntax( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 825 String schemaName ) throws LdapInvalidAttributeValueException, LdapUnwillingToPerformException 826 { 827 checkEntry( entry, SchemaConstants.SYNTAX ); 828 829 // The Syntax OID 830 String oid = getOid( entry, SchemaConstants.SYNTAX, schemaManager.isStrict() ); 831 832 // Get the schema 833 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 834 { 835 // The schema is not loaded. We can't create the requested Syntax 836 String msg = I18n.err( I18n.ERR_10020, entry.getDn().getName(), schemaName ); 837 LOG.warn( msg ); 838 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 839 } 840 841 Schema schema = getSchema( schemaName, targetRegistries ); 842 843 if ( schema == null ) 844 { 845 // The schema is disabled. We still have to update the backend 846 String msg = I18n.err( I18n.ERR_10021, entry.getDn().getName(), schemaName ); 847 LOG.info( msg ); 848 schema = schemaManager.getLoadedSchema( schemaName ); 849 } 850 851 // Create the new LdapSyntax instance 852 LdapSyntax syntax = new LdapSyntax( oid ); 853 854 // Common properties 855 setSchemaObjectProperties( syntax, entry, schema ); 856 857 return syntax; 858 } 859 860 861 /** 862 * {@inheritDoc} 863 * @throws LdapInvalidAttributeValueException If the MatchingRule does not exist 864 * @throws LdapUnwillingToPerformException If the schema is not loaded 865 */ 866 @Override 867 public MatchingRule getMatchingRule( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 868 String schemaName ) throws LdapUnwillingToPerformException, LdapInvalidAttributeValueException 869 { 870 checkEntry( entry, SchemaConstants.MATCHING_RULE ); 871 872 // The MatchingRule OID 873 String oid = getOid( entry, SchemaConstants.MATCHING_RULE, schemaManager.isStrict() ); 874 875 // Get the schema 876 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 877 { 878 // The schema is not loaded. We can't create the requested MatchingRule 879 String msg = I18n.err( I18n.ERR_10022, entry.getDn().getName(), schemaName ); 880 LOG.warn( msg ); 881 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 882 } 883 884 Schema schema = getSchema( schemaName, targetRegistries ); 885 886 if ( schema == null ) 887 { 888 // The schema is disabled. We still have to update the backend 889 String msg = I18n.err( I18n.ERR_10023, entry.getDn().getName(), schemaName ); 890 LOG.info( msg ); 891 schema = schemaManager.getLoadedSchema( schemaName ); 892 } 893 894 MutableMatchingRule matchingRule = new MutableMatchingRule( oid ); 895 896 // The syntax field 897 Attribute mSyntax = entry.get( MetaSchemaConstants.M_SYNTAX_AT ); 898 899 if ( mSyntax != null ) 900 { 901 matchingRule.setSyntaxOid( mSyntax.getString() ); 902 } 903 904 // The normalizer and comparator fields will be updated when we will 905 // apply the registry 906 907 // Common properties 908 setSchemaObjectProperties( matchingRule, entry, schema ); 909 910 return matchingRule; 911 } 912 913 914 /** 915 * Create a list of string from a multivalued attribute's values 916 */ 917 private List<String> getStrings( Attribute attr ) 918 { 919 if ( attr == null ) 920 { 921 return EMPTY_LIST; 922 } 923 924 List<String> strings = new ArrayList<>( attr.size() ); 925 926 for ( Value<?> value : attr ) 927 { 928 strings.add( value.getString() ); 929 } 930 931 return strings; 932 } 933 934 935 /** 936 * {@inheritDoc} 937 */ 938 @Override 939 public ObjectClass getObjectClass( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 940 String schemaName ) throws LdapException 941 { 942 checkEntry( entry, SchemaConstants.OBJECT_CLASS ); 943 944 // The ObjectClass OID 945 String oid = getOid( entry, SchemaConstants.OBJECT_CLASS, schemaManager.isStrict() ); 946 947 // Get the schema 948 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 949 { 950 // The schema is not loaded. We can't create the requested ObjectClass 951 String msg = I18n.err( I18n.ERR_10024, entry.getDn().getName(), schemaName ); 952 LOG.warn( msg ); 953 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 954 } 955 956 Schema schema = getSchema( schemaName, targetRegistries ); 957 958 if ( schema == null ) 959 { 960 // The schema is disabled. We still have to update the backend 961 String msg = I18n.err( I18n.ERR_10025, entry.getDn().getName(), schemaName ); 962 LOG.info( msg ); 963 schema = schemaManager.getLoadedSchema( schemaName ); 964 } 965 966 // Create the ObjectClass instance 967 MutableObjectClass oc = new MutableObjectClass( oid ); 968 969 // The Sup field 970 Attribute mSuperiors = entry.get( MetaSchemaConstants.M_SUP_OBJECT_CLASS_AT ); 971 972 if ( mSuperiors != null ) 973 { 974 oc.setSuperiorOids( getStrings( mSuperiors ) ); 975 } 976 977 // The May field 978 Attribute mMay = entry.get( MetaSchemaConstants.M_MAY_AT ); 979 980 if ( mMay != null ) 981 { 982 oc.setMayAttributeTypeOids( getStrings( mMay ) ); 983 } 984 985 // The Must field 986 Attribute mMust = entry.get( MetaSchemaConstants.M_MUST_AT ); 987 988 if ( mMust != null ) 989 { 990 oc.setMustAttributeTypeOids( getStrings( mMust ) ); 991 } 992 993 // The objectClassType field 994 Attribute mTypeObjectClass = entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ); 995 996 if ( mTypeObjectClass != null ) 997 { 998 String type = mTypeObjectClass.getString(); 999 oc.setType( ObjectClassTypeEnum.getClassType( type ) ); 1000 } 1001 1002 // Common properties 1003 setSchemaObjectProperties( oc, entry, schema ); 1004 1005 return oc; 1006 } 1007 1008 1009 /** 1010 * {@inheritDoc} 1011 * @throws LdapInvalidAttributeValueException If the AttributeType does not exist 1012 * @throws LdapUnwillingToPerformException If the schema is not loaded 1013 */ 1014 @Override 1015 public AttributeType getAttributeType( SchemaManager schemaManager, Entry entry, Registries targetRegistries, 1016 String schemaName ) throws LdapInvalidAttributeValueException, LdapUnwillingToPerformException 1017 { 1018 checkEntry( entry, SchemaConstants.ATTRIBUTE_TYPE ); 1019 1020 // The AttributeType OID 1021 String oid = getOid( entry, SchemaConstants.ATTRIBUTE_TYPE, schemaManager.isStrict() ); 1022 1023 // Get the schema 1024 if ( !schemaManager.isSchemaLoaded( schemaName ) ) 1025 { 1026 // The schema is not loaded, this is an error 1027 String msg = I18n.err( I18n.ERR_10026, entry.getDn().getName(), schemaName ); 1028 LOG.warn( msg ); 1029 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 1030 } 1031 1032 Schema schema = getSchema( schemaName, targetRegistries ); 1033 1034 if ( schema == null ) 1035 { 1036 // The schema is disabled. We still have to update the backend 1037 String msg = I18n.err( I18n.ERR_10027, entry.getDn().getName(), schemaName ); 1038 LOG.info( msg ); 1039 schema = schemaManager.getLoadedSchema( schemaName ); 1040 } 1041 1042 // Create the new AttributeType 1043 MutableAttributeType attributeType = new MutableAttributeType( oid ); 1044 1045 if ( schemaManager.isRelaxed() ) 1046 { 1047 attributeType.setRelaxed( true ); 1048 } 1049 1050 // Syntax 1051 Attribute mSyntax = entry.get( MetaSchemaConstants.M_SYNTAX_AT ); 1052 1053 if ( ( mSyntax != null ) && ( mSyntax.get() != null ) ) 1054 { 1055 attributeType.setSyntaxOid( mSyntax.getString() ); 1056 } 1057 1058 // Syntax Length 1059 Attribute mSyntaxLength = entry.get( MetaSchemaConstants.M_LENGTH_AT ); 1060 1061 if ( mSyntaxLength != null ) 1062 { 1063 attributeType.setSyntaxLength( Integer.parseInt( mSyntaxLength.getString() ) ); 1064 } 1065 1066 // Equality 1067 Attribute mEquality = entry.get( MetaSchemaConstants.M_EQUALITY_AT ); 1068 1069 if ( mEquality != null ) 1070 { 1071 attributeType.setEqualityOid( mEquality.getString() ); 1072 } 1073 1074 // Ordering 1075 Attribute mOrdering = entry.get( MetaSchemaConstants.M_ORDERING_AT ); 1076 1077 if ( mOrdering != null ) 1078 { 1079 attributeType.setOrderingOid( mOrdering.getString() ); 1080 } 1081 1082 // Substr 1083 Attribute mSubstr = entry.get( MetaSchemaConstants.M_SUBSTR_AT ); 1084 1085 if ( mSubstr != null ) 1086 { 1087 attributeType.setSubstringOid( mSubstr.getString() ); 1088 } 1089 1090 Attribute mSupAttributeType = entry.get( MetaSchemaConstants.M_SUP_ATTRIBUTE_TYPE_AT ); 1091 1092 // Sup 1093 if ( mSupAttributeType != null ) 1094 { 1095 attributeType.setSuperiorOid( mSupAttributeType.getString() ); 1096 } 1097 1098 // isCollective 1099 Attribute mCollective = entry.get( MetaSchemaConstants.M_COLLECTIVE_AT ); 1100 1101 if ( mCollective != null ) 1102 { 1103 String val = mCollective.getString(); 1104 attributeType.setCollective( "TRUE".equalsIgnoreCase( val ) ); 1105 } 1106 1107 // isSingleValued 1108 Attribute mSingleValued = entry.get( MetaSchemaConstants.M_SINGLE_VALUE_AT ); 1109 1110 if ( mSingleValued != null ) 1111 { 1112 String val = mSingleValued.getString(); 1113 attributeType.setSingleValued( "TRUE".equalsIgnoreCase( val ) ); 1114 } 1115 1116 // isReadOnly 1117 Attribute mNoUserModification = entry.get( MetaSchemaConstants.M_NO_USER_MODIFICATION_AT ); 1118 1119 if ( mNoUserModification != null ) 1120 { 1121 String val = mNoUserModification.getString(); 1122 attributeType.setUserModifiable( !"TRUE".equalsIgnoreCase( val ) ); 1123 } 1124 1125 // Usage 1126 Attribute mUsage = entry.get( MetaSchemaConstants.M_USAGE_AT ); 1127 1128 if ( mUsage != null ) 1129 { 1130 attributeType.setUsage( UsageEnum.getUsage( mUsage.getString() ) ); 1131 } 1132 1133 // Common properties 1134 setSchemaObjectProperties( attributeType, entry, schema ); 1135 1136 return attributeType; 1137 } 1138 1139 1140 /** 1141 * Process the FQCN attribute 1142 * @throws LdapInvalidAttributeValueException 1143 */ 1144 private String getFqcn( Entry entry, String objectType ) throws LdapInvalidAttributeValueException 1145 { 1146 // The FQCN 1147 Attribute mFqcn = entry.get( MetaSchemaConstants.M_FQCN_AT ); 1148 1149 if ( mFqcn == null ) 1150 { 1151 String msg = I18n.err( I18n.ERR_10028, objectType, MetaSchemaConstants.M_FQCN_AT ); 1152 LOG.warn( msg ); 1153 throw new IllegalArgumentException( msg ); 1154 } 1155 1156 return mFqcn.getString(); 1157 } 1158 1159 1160 /** 1161 * Process the FQCN attribute 1162 */ 1163 private String getFqcn( LoadableSchemaObject description, String objectType ) 1164 { 1165 // The FQCN 1166 String mFqcn = description.getFqcn(); 1167 1168 if ( mFqcn == null ) 1169 { 1170 String msg = I18n.err( I18n.ERR_10028, objectType, MetaSchemaConstants.M_FQCN_AT ); 1171 LOG.warn( msg ); 1172 throw new IllegalArgumentException( msg ); 1173 } 1174 1175 return mFqcn; 1176 } 1177 1178 1179 /** 1180 * Process the ByteCode attribute 1181 */ 1182 private Attribute getByteCode( LoadableSchemaObject description, String objectType ) 1183 { 1184 String byteCodeString = description.getBytecode(); 1185 1186 if ( byteCodeString == null ) 1187 { 1188 String msg = I18n.err( I18n.ERR_10028, objectType, MetaSchemaConstants.M_BYTECODE_AT ); 1189 LOG.warn( msg ); 1190 throw new IllegalArgumentException( msg ); 1191 } 1192 1193 byte[] bytecode = Base64.decode( byteCodeString.toCharArray() ); 1194 1195 return new DefaultAttribute( MetaSchemaConstants.M_BYTECODE_AT, bytecode ); 1196 } 1197 1198 1199 /** 1200 * Return a String value, from teh given Valu, even if it's a binary value 1201 */ 1202 private String getStringValue( Attribute attribute ) 1203 { 1204 Value<?> value = attribute.get(); 1205 1206 if ( value instanceof BinaryValue ) 1207 { 1208 // We have to transform the value to a String 1209 return Strings.utf8ToString( value.getBytes() ); 1210 } 1211 else 1212 { 1213 return value.getString(); 1214 } 1215 } 1216 1217 1218 /** 1219 * Process the common attributes to all SchemaObjects : 1220 * - obsolete 1221 * - description 1222 * - names 1223 * - schemaName 1224 * - specification (if any) 1225 * - extensions 1226 * - isReadOnly 1227 * - isEnabled 1228 * @throws org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException 1229 */ 1230 private void setSchemaObjectProperties( SchemaObject schemaObject, Entry entry, Schema schema ) 1231 throws LdapInvalidAttributeValueException 1232 { 1233 // The isObsolete field 1234 Attribute mObsolete = entry.get( MetaSchemaConstants.M_OBSOLETE_AT ); 1235 1236 if ( mObsolete != null ) 1237 { 1238 String val = mObsolete.getString(); 1239 schemaObject.setObsolete( "TRUE".equalsIgnoreCase( val ) ); 1240 } 1241 1242 // The description field 1243 Attribute mDescription = entry.get( MetaSchemaConstants.M_DESCRIPTION_AT ); 1244 1245 if ( mDescription != null ) 1246 { 1247 schemaObject.setDescription( getStringValue( mDescription ) ); 1248 } 1249 1250 // The names field 1251 Attribute names = entry.get( MetaSchemaConstants.M_NAME_AT ); 1252 1253 if ( names != null ) 1254 { 1255 List<String> values = new ArrayList<>(); 1256 1257 for ( Value<?> name : names ) 1258 { 1259 values.add( name.getString() ); 1260 } 1261 1262 schemaObject.setNames( values ); 1263 } 1264 1265 // The isEnabled field 1266 Attribute mDisabled = entry.get( MetaSchemaConstants.M_DISABLED_AT ); 1267 1268 // If the SchemaObject has an explicit m-disabled attribute, then use it. 1269 // Otherwise, inherit it from the schema 1270 if ( mDisabled != null ) 1271 { 1272 String val = mDisabled.getString(); 1273 schemaObject.setEnabled( !"TRUE".equalsIgnoreCase( val ) ); 1274 } 1275 else 1276 { 1277 schemaObject.setEnabled( schema.isEnabled() ); 1278 } 1279 1280 // The specification field 1281 /* 1282 * TODO : create the M_SPECIFICATION_AT 1283 EntryAttribute mSpecification = entry.get( MetaSchemaConstants.M_SPECIFICATION_AT ); 1284 1285 if ( mSpecification != null ) 1286 { 1287 so.setSpecification( mSpecification.getString() ); 1288 } 1289 */ 1290 1291 // The schemaName field 1292 schemaObject.setSchemaName( schema.getSchemaName() ); 1293 1294 // The extensions fields 1295 // X-SCHEMA 1296 Attribute xSchema = entry.get( MetaSchemaConstants.X_SCHEMA_AT ); 1297 1298 if ( xSchema != null ) 1299 { 1300 String schemaName = xSchema.getString(); 1301 1302 if ( !schema.getSchemaName().equalsIgnoreCase( schemaName ) ) 1303 { 1304 LOG.warn( "Schema (" + schema.getSchemaName() + ") and X-SCHEMA (" 1305 + schemaName + ") are different : " + entry ); 1306 } 1307 1308 schemaObject.addExtension( MetaSchemaConstants.X_SCHEMA_AT, schemaName ); 1309 } 1310 1311 // X-NOT-HUMAN-READABLE 1312 Attribute xNotHumanReadable = entry.get( MetaSchemaConstants.X_NOT_HUMAN_READABLE_AT ); 1313 1314 if ( xNotHumanReadable != null ) 1315 { 1316 String value = xNotHumanReadable.getString(); 1317 1318 schemaObject.addExtension( MetaSchemaConstants.X_NOT_HUMAN_READABLE_AT, value ); 1319 } 1320 1321 // X-READ-ONLY 1322 Attribute xReadOnly = entry.get( MetaSchemaConstants.X_READ_ONLY_AT ); 1323 1324 if ( xReadOnly != null ) 1325 { 1326 String value = xReadOnly.getString(); 1327 1328 schemaObject.addExtension( MetaSchemaConstants.X_READ_ONLY_AT, value ); 1329 } 1330 } 1331 1332 1333 /** 1334 * Process the common attributes to all SchemaObjects : 1335 * - obsolete 1336 * - description 1337 * - names 1338 * - schemaName 1339 * - specification (if any) 1340 * - extensions 1341 * - isReadOnly 1342 * - isEnabled 1343 */ 1344 private void setSchemaObjectProperties( SchemaObject schemaObject, SchemaObject description, Schema schema ) 1345 { 1346 // The isObsolete field 1347 schemaObject.setObsolete( description.isObsolete() ); 1348 1349 // The description field 1350 schemaObject.setDescription( description.getDescription() ); 1351 1352 // The names field 1353 schemaObject.setNames( description.getNames() ); 1354 1355 // The isEnabled field. Has the description does not hold a 1356 // Disable field, we will inherit from the schema enable field 1357 schemaObject.setEnabled( schema.isEnabled() ); 1358 1359 // The isReadOnly field. We don't have this data in the description, 1360 // so set it to false 1361 // TODO : should it be a X-READONLY extension ? 1362 schemaObject.setReadOnly( false ); 1363 1364 // The specification field 1365 schemaObject.setSpecification( description.getSpecification() ); 1366 1367 // The schemaName field 1368 schemaObject.setSchemaName( schema.getSchemaName() ); 1369 1370 // The extensions field 1371 schemaObject.setExtensions( description.getExtensions() ); 1372 } 1373}