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}