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