001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.api.ldap.model.schema.registries.helper;
021
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025
026import org.apache.directory.api.i18n.I18n;
027import org.apache.directory.api.ldap.model.exception.LdapException;
028import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
029import org.apache.directory.api.ldap.model.exception.LdapSchemaExceptionCodes;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.api.ldap.model.schema.LdapSyntax;
032import org.apache.directory.api.ldap.model.schema.MatchingRule;
033import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
034import org.apache.directory.api.ldap.model.schema.UsageEnum;
035import org.apache.directory.api.ldap.model.schema.registries.AttributeTypeRegistry;
036import org.apache.directory.api.ldap.model.schema.registries.Registries;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * An helper class used to store all the methods associated with an AttributeType
042 * in relation with the Registries and SchemaManager.
043 * 
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046public final class AttributeTypeHelper
047{
048    /** A logger for this class */
049    private static final Logger LOG = LoggerFactory.getLogger( AttributeTypeHelper.class );
050
051    private AttributeTypeHelper()
052    {
053    }
054
055    /**
056     * Inject the AttributeType into the Registries, updating the references to
057     * other SchemaObject
058     *
059     * If one of the referenced SchemaObject does not exist (SUP, EQUALITY, ORDERING, SUBSTR, SYNTAX),
060     * an exception is thrown.
061     * 
062     * @param attributeType The AttributeType to add to the Registries
063     * @param errors The errors we got while adding the AttributeType to the Registries
064     * @param registries The Registries
065     * @throws LdapException If the AttributeType is not valid
066     */
067    public static void addToRegistries( MutableAttributeType attributeType, List<Throwable> errors, Registries registries ) throws LdapException
068    {
069        if ( registries != null )
070        {
071            try
072            {
073                attributeType.unlock();
074                AttributeTypeRegistry attributeTypeRegistry = registries.getAttributeTypeRegistry();
075    
076                // The superior
077                if ( !buildSuperior( attributeType, errors, registries ) )
078                {
079                    // We have had errors, let's stop here as we need a correct superior to continue
080                    return;
081                }
082    
083                // The Syntax
084                buildSyntax( attributeType, errors, registries );
085    
086                // The EQUALITY matching rule
087                buildEquality( attributeType, errors, registries );
088    
089                // The ORDERING matching rule
090                buildOrdering( attributeType, errors, registries );
091    
092                // The SUBSTR matching rule
093                buildSubstring( attributeType, errors, registries );
094    
095                // Check the USAGE
096                checkUsage( attributeType, errors );
097    
098                // Check the COLLECTIVE element
099                checkCollective( attributeType, errors );
100    
101                // Inject the attributeType into the oid/normalizer map
102                attributeTypeRegistry.addMappingFor( attributeType );
103    
104                // Register this AttributeType into the Descendant map
105                attributeTypeRegistry.registerDescendants( attributeType, attributeType.getSuperior() );
106    
107                /**
108                 * Add the AT references (using and usedBy) :
109                 * AT -> MR (for EQUALITY, ORDERING and SUBSTR)
110                 * AT -> S
111                 * AT -> AT
112                 */
113                if ( attributeType.getEquality() != null )
114                {
115                    registries.addReference( attributeType, attributeType.getEquality() );
116                }
117    
118                if ( attributeType.getOrdering() != null )
119                {
120                    registries.addReference( attributeType, attributeType.getOrdering() );
121                }
122    
123                if ( attributeType.getSubstring() != null )
124                {
125                    registries.addReference( attributeType, attributeType.getSubstring() );
126                }
127    
128                if ( attributeType.getSyntax() != null )
129                {
130                    registries.addReference( attributeType, attributeType.getSyntax() );
131                }
132    
133                if ( attributeType.getSuperior() != null )
134                {
135                    registries.addReference( attributeType, attributeType.getSuperior() );
136                }
137            }
138            finally
139            {
140                attributeType.lock();
141            }
142        }
143    }
144
145
146    /**
147     * Build the Superior AttributeType reference for an AttributeType
148     */
149    private static boolean buildSuperior( MutableAttributeType attributeType, List<Throwable> errors, Registries registries )
150    {
151        MutableAttributeType currentSuperior;
152        AttributeTypeRegistry attributeTypeRegistry = registries.getAttributeTypeRegistry();
153        
154        String superiorOid = attributeType.getSuperiorOid();
155
156        if ( superiorOid != null )
157        {
158            // This AT has a superior
159            try
160            {
161                currentSuperior = ( MutableAttributeType ) attributeTypeRegistry.lookup( superiorOid );
162            }
163            catch ( Exception e )
164            {
165                // Not allowed.
166                String msg = I18n.err( I18n.ERR_04303, superiorOid, attributeType.getName() );
167
168                LdapSchemaException ldapSchemaException = new LdapSchemaException(
169                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SUPERIOR, msg, e );
170                ldapSchemaException.setSourceObject( attributeType );
171                ldapSchemaException.setRelatedId( superiorOid );
172                errors.add( ldapSchemaException );
173                LOG.info( msg );
174
175                // Get out now
176                return false;
177            }
178
179            if ( currentSuperior != null )
180            {
181                // a special case : if the superior is collective, this is an error
182                if ( currentSuperior.isCollective() )
183                {
184                    String msg = I18n.err( I18n.ERR_04482_CANNOT_SUBTYPE_COLLECTIVE,
185                        currentSuperior, attributeType.getName() );
186
187                    LdapSchemaException ldapSchemaException = new LdapSchemaException(
188                        LdapSchemaExceptionCodes.AT_CANNOT_SUBTYPE_COLLECTIVE_AT, msg );
189                    ldapSchemaException.setSourceObject( attributeType );
190                    errors.add( ldapSchemaException );
191                    LOG.info( msg );
192                    
193                    return false;
194                }
195
196                attributeType.setSuperior( currentSuperior );
197
198                // Recursively update the superior if not already done. We don't recurse
199                // if the superior's superior is not null, as it means it has already been
200                // handled.
201                if ( currentSuperior.getSuperior() == null )
202                {
203                    registries.buildReference( errors, currentSuperior );
204                }
205
206                // Update the descendant MAP
207                try
208                {
209                    attributeTypeRegistry.registerDescendants( attributeType, currentSuperior );
210                }
211                catch ( LdapException ne )
212                {
213                    errors.add( ne );
214                    LOG.info( ne.getMessage() );
215                    
216                    return false;
217                }
218
219                // Check for cycles now
220                Set<String> superiors = new HashSet<>();
221                superiors.add( attributeType.getOid() );
222                AttributeType tmp = currentSuperior;
223                boolean isOk = true;
224
225                while ( tmp != null )
226                {
227                    if ( superiors.contains( tmp.getOid() ) )
228                    {
229                        // There is a cycle : bad bad bad !
230                        // Not allowed.
231                        String msg = I18n.err( I18n.ERR_04304, attributeType.getName() );
232
233                        LdapSchemaException ldapSchemaException = new LdapSchemaException(
234                            LdapSchemaExceptionCodes.AT_CYCLE_TYPE_HIERARCHY, msg );
235                        ldapSchemaException.setSourceObject( attributeType );
236                        errors.add( ldapSchemaException );
237                        LOG.info( msg );
238                        isOk = false;
239
240                        break;
241                    }
242                    else
243                    {
244                        superiors.add( tmp.getOid() );
245                        tmp = tmp.getSuperior();
246                    }
247                }
248
249                superiors.clear();
250
251                return isOk;
252            }
253            else
254            {
255                // Not allowed.
256                String msg = I18n.err( I18n.ERR_04305, superiorOid, attributeType.getName() );
257
258                LdapSchemaException ldapSchemaException = new LdapSchemaException(
259                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SUPERIOR, msg );
260                ldapSchemaException.setSourceObject( attributeType );
261                ldapSchemaException.setRelatedId( superiorOid );
262                errors.add( ldapSchemaException );
263                LOG.info( msg );
264
265                // Get out now
266                return false;
267            }
268        }
269        else
270        {
271            // No superior, just return
272            return true;
273        }
274    }
275
276
277    /**
278     * Build the SYNTAX reference for an AttributeType
279     */
280    private static void buildSyntax( MutableAttributeType attributeType, List<Throwable> errors, Registries registries )
281    {
282        String syntaxOid = attributeType.getSyntaxOid();
283        
284        if ( syntaxOid != null )
285        {
286            LdapSyntax currentSyntax = null;
287
288            try
289            {
290                currentSyntax = registries.getLdapSyntaxRegistry().lookup( syntaxOid );
291            }
292            catch ( LdapException ne )
293            {
294                // Not allowed.
295                String msg = I18n.err( I18n.ERR_04306, syntaxOid, attributeType.getName() );
296
297                LdapSchemaException ldapSchemaException = new LdapSchemaException(
298                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SYNTAX, msg, ne );
299                ldapSchemaException.setSourceObject( attributeType );
300                ldapSchemaException.setRelatedId( syntaxOid );
301                errors.add( ldapSchemaException );
302                LOG.info( msg );
303                
304                return;
305            }
306
307            if ( currentSyntax != null )
308            {
309                // Update the Syntax reference
310                attributeType.setSyntax( currentSyntax );
311            }
312            else
313            {
314                // Not allowed.
315                String msg = I18n.err( I18n.ERR_04306, syntaxOid, attributeType.getName() );
316
317                LdapSchemaException ldapSchemaException = new LdapSchemaException(
318                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SYNTAX, msg );
319                ldapSchemaException.setSourceObject( attributeType );
320                ldapSchemaException.setRelatedId( syntaxOid );
321                errors.add( ldapSchemaException );
322                LOG.info( msg );
323                
324                return;
325            }
326        }
327        else
328        {
329            // We inherit from the superior's syntax, if any
330            if ( attributeType.getSuperior() != null )
331            {
332                if ( attributeType.getSuperior().getSyntax() != null )
333                {
334                    attributeType.setSyntax( attributeType.getSuperior().getSyntax() );
335                }
336                else
337                {
338                    String msg = I18n.err( I18n.ERR_04306, syntaxOid, attributeType.getName() );
339
340                    LdapSchemaException ldapSchemaException = new LdapSchemaException(
341                        LdapSchemaExceptionCodes.AT_NONEXISTENT_SYNTAX, msg );
342                    ldapSchemaException.setSourceObject( attributeType );
343                    ldapSchemaException.setRelatedId( syntaxOid );
344                    errors.add( ldapSchemaException );
345                    LOG.info( msg );
346                    
347                    return;
348                }
349            }
350            else
351            {
352                // Not allowed.
353                String msg = I18n.err( I18n.ERR_04307, attributeType.getName() );
354
355                LdapSchemaException ldapSchemaException = new LdapSchemaException(
356                    LdapSchemaExceptionCodes.AT_SYNTAX_OR_SUPERIOR_REQUIRED, msg );
357                ldapSchemaException.setSourceObject( attributeType );
358                errors.add( ldapSchemaException );
359                LOG.info( msg );
360                
361                return;
362            }
363        }
364    }
365    
366    
367    /**
368     * Build the EQUALITY MR reference for an AttributeType
369     */
370    private static void buildEquality( MutableAttributeType attributeType, List<Throwable> errors, Registries registries )
371    {
372        String equalityOid = attributeType.getEqualityOid();
373        
374        // The equality MR. It can be null
375        if ( equalityOid != null )
376        {
377            MatchingRule currentEquality = null;
378
379            try
380            {
381                currentEquality = registries.getMatchingRuleRegistry().lookup( equalityOid );
382            }
383            catch ( LdapException ne )
384            {
385                // Not allowed.
386                String msg = I18n.err( I18n.ERR_04308, equalityOid, attributeType.getName() );
387
388                LdapSchemaException ldapSchemaException = new LdapSchemaException(
389                    LdapSchemaExceptionCodes.AT_NONEXISTENT_EQUALITY_MATCHING_RULE, msg, ne );
390                ldapSchemaException.setSourceObject( attributeType );
391                ldapSchemaException.setRelatedId( equalityOid );
392                errors.add( ldapSchemaException );
393                LOG.info( msg );
394                
395                return;
396            }
397
398            if ( currentEquality != null )
399            {
400                attributeType.setEquality( currentEquality );
401                
402                // Restore the old equality OID to preserve the user's provided value
403                attributeType.setEqualityOid( equalityOid );
404            }
405            else
406            {
407                // Not allowed.
408                String msg = I18n.err( I18n.ERR_04309, equalityOid, attributeType.getName() );
409
410                LdapSchemaException ldapSchemaException = new LdapSchemaException(
411                    LdapSchemaExceptionCodes.AT_NONEXISTENT_EQUALITY_MATCHING_RULE, msg );
412                ldapSchemaException.setSourceObject( attributeType );
413                ldapSchemaException.setRelatedId( equalityOid );
414                errors.add( ldapSchemaException );
415                LOG.info( msg );
416            }
417        }
418        else
419        {
420            AttributeType superior = attributeType.getSuperior();
421            
422            // If the AT has a superior, take its Equality MR if any
423            if ( ( superior != null ) && ( superior.getEquality() != null ) )
424            {
425                attributeType.setEquality( superior.getEquality() );
426            }
427        }
428    }
429
430
431    /**
432     * Build the SUBSTR MR reference for an AttributeType
433     */
434    private static void buildSubstring( MutableAttributeType attributeType, List<Throwable> errors, Registries registries )
435    {
436        String substringOid = attributeType.getSubstringOid();
437        
438        // The Substring MR. It can be null
439        if ( substringOid != null )
440        {
441            MatchingRule currentSubstring = null;
442
443            try
444            {
445                currentSubstring = registries.getMatchingRuleRegistry().lookup( substringOid );
446            }
447            catch ( LdapException ne )
448            {
449                // Not allowed.
450                String msg = I18n.err( I18n.ERR_04312, substringOid, attributeType.getName() );
451
452                LdapSchemaException ldapSchemaException = new LdapSchemaException(
453                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SUBSTRING_MATCHING_RULE, msg, ne );
454                ldapSchemaException.setSourceObject( attributeType );
455                ldapSchemaException.setRelatedId( substringOid );
456                errors.add( ldapSchemaException );
457                LOG.info( msg );
458                
459                return;
460            }
461
462            if ( currentSubstring != null )
463            {
464                attributeType.setSubstring( currentSubstring );
465            }
466            else
467            {
468                // Not allowed.
469                String msg = I18n.err( I18n.ERR_04313, substringOid, attributeType.getName() );
470
471                LdapSchemaException ldapSchemaException = new LdapSchemaException(
472                    LdapSchemaExceptionCodes.AT_NONEXISTENT_SUBSTRING_MATCHING_RULE, msg );
473                ldapSchemaException.setSourceObject( attributeType );
474                ldapSchemaException.setRelatedId( substringOid );
475                errors.add( ldapSchemaException );
476                LOG.info( msg );
477                
478                return;
479            }
480        }
481        else
482        {
483            AttributeType superior = attributeType.getSuperior();
484            
485            // If the AT has a superior, take its Substring MR if any
486            if ( ( superior != null ) && ( superior.getSubstring() != null ) )
487            {
488                attributeType.setSubstring( superior.getSubstring() );
489            }
490        }
491    }
492    
493    
494
495
496
497
498    /**
499     * Build the ORDERING MR reference for an AttributeType
500     */
501    private static void buildOrdering( MutableAttributeType attributeType, List<Throwable> errors, Registries registries )
502    {
503        String orderingOid = attributeType.getOrderingOid();
504        
505        if ( orderingOid != null )
506        {
507            MatchingRule currentOrdering = null;
508
509            try
510            {
511                currentOrdering = registries.getMatchingRuleRegistry().lookup( orderingOid );
512            }
513            catch ( LdapException ne )
514            {
515                // Not allowed.
516                String msg = I18n.err( I18n.ERR_04310, orderingOid, attributeType.getName() );
517
518                LdapSchemaException ldapSchemaException = new LdapSchemaException(
519                    LdapSchemaExceptionCodes.AT_NONEXISTENT_ORDERING_MATCHING_RULE, msg, ne );
520                ldapSchemaException.setSourceObject( attributeType );
521                ldapSchemaException.setRelatedId( orderingOid );
522                errors.add( ldapSchemaException );
523                LOG.info( msg );
524                
525                return;
526            }
527
528            if ( currentOrdering != null )
529            {
530                attributeType.setOrdering( currentOrdering );
531            }
532            else
533            {
534                // Not allowed.
535                String msg = I18n.err( I18n.ERR_04311, orderingOid, attributeType.getName() );
536
537                LdapSchemaException ldapSchemaException = new LdapSchemaException(
538                    LdapSchemaExceptionCodes.AT_NONEXISTENT_ORDERING_MATCHING_RULE, msg );
539                ldapSchemaException.setSourceObject( attributeType );
540                ldapSchemaException.setRelatedId( orderingOid );
541                errors.add( ldapSchemaException );
542                LOG.info( msg );
543            }
544        }
545        else
546        {
547            AttributeType superior = attributeType.getSuperior();
548            
549            // If the AT has a superior, take its Ordering MR if any
550            if ( ( superior != null ) && ( superior.getOrdering() != null ) )
551            {
552                attributeType.setOrdering( superior.getOrdering() );
553            }
554        }
555    }
556
557    
558    /**
559     * Check the constraints for the Usage field.
560     */
561    private static void checkUsage( AttributeType attributeType, List<Throwable> errors )
562    {
563        AttributeType superior = attributeType.getSuperior();
564        
565        // Check that the AT usage is the same that its superior
566        if ( ( superior != null ) && ( attributeType.getUsage() != superior.getUsage() ) )
567        {
568            // This is an error
569            String msg = I18n.err( I18n.ERR_04314, attributeType.getName() );
570
571            LdapSchemaException ldapSchemaException = new LdapSchemaException(
572                LdapSchemaExceptionCodes.AT_MUST_HAVE_SAME_USAGE_THAN_SUPERIOR, msg );
573            ldapSchemaException.setSourceObject( attributeType );
574            errors.add( ldapSchemaException );
575            LOG.info( msg );
576            
577            return;
578        }
579
580        // Now, check that the AttributeType's USAGE does not conflict
581        if ( !attributeType.isUserModifiable() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
582        {
583            // Cannot have a not user modifiable AT which is not an operational AT
584            String msg = I18n.err( I18n.ERR_04315, attributeType.getName() );
585
586            LdapSchemaException ldapSchemaException = new LdapSchemaException(
587                LdapSchemaExceptionCodes.AT_USER_APPLICATIONS_USAGE_MUST_BE_USER_MODIFIABLE, msg );
588            ldapSchemaException.setSourceObject( attributeType );
589            errors.add( ldapSchemaException );
590            LOG.info( msg );
591        }
592    }
593
594
595    /**
596     * Check the constraints for the Collective field.
597     */
598    private static void checkCollective( MutableAttributeType attributeType, List<Throwable> errors )
599    {
600        AttributeType superior = attributeType.getSuperior();
601
602        if ( ( superior != null ) && superior.isCollective() )
603        {
604            // An AttributeType will be collective if its superior is collective
605            attributeType.setCollective( true );
606        }
607
608        if ( attributeType.isCollective() && ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) )
609        {
610            // An AttributeType which is collective must be a USER attributeType
611            String msg = I18n.err( I18n.ERR_04316, attributeType.getName() );
612
613            LdapSchemaException ldapSchemaException = new LdapSchemaException(
614                LdapSchemaExceptionCodes.AT_COLLECTIVE_MUST_HAVE_USER_APPLICATIONS_USAGE, msg );
615            ldapSchemaException.setSourceObject( attributeType );
616            errors.add( ldapSchemaException );
617            LOG.info( msg );
618        }
619
620        if ( attributeType.isCollective() && attributeType.isSingleValued() )
621        {
622            // A collective attribute must be multi-valued
623            String msg = I18n.err( I18n.ERR_04483_COLLECTIVE_NOT_MULTI_VALUED, attributeType.getName() );
624
625            LdapSchemaException ldapSchemaException = new LdapSchemaException(
626                LdapSchemaExceptionCodes.AT_COLLECTIVE_CANNOT_BE_SINGLE_VALUED, msg );
627            ldapSchemaException.setSourceObject( attributeType );
628            errors.add( ldapSchemaException );
629            LOG.info( msg );
630        }
631    }
632    
633    
634    /**
635     * Remove the AttributeType from the registries, updating the references to
636     * other SchemaObject.
637     *
638     * If one of the referenced SchemaObject does not exist (SUP, EQUALITY, ORDERING, SUBSTR, SYNTAX),
639     * an exception is thrown.
640     * 
641     * @param attributeType The AttributeType to remove from the Registries
642     * @param errors The errors we got while removing the AttributeType from the Registries
643     * @param registries The Registries
644     * @throws LdapException If the AttributeType is not valid
645     */
646    public static void removeFromRegistries( AttributeType attributeType, List<Throwable> errors, Registries registries ) throws LdapException
647    {
648        if ( registries != null )
649        {
650            AttributeTypeRegistry attributeTypeRegistry = registries.getAttributeTypeRegistry();
651
652            // Remove the attributeType from the oid/normalizer map
653            attributeTypeRegistry.removeMappingFor( attributeType );
654
655            // Unregister this AttributeType into the Descendant map
656            attributeTypeRegistry.unregisterDescendants( attributeType, attributeType.getSuperior() );
657
658            /**
659             * Remove the AT references (using and usedBy) :
660             * AT -> MR (for EQUALITY, ORDERING and SUBSTR)
661             * AT -> S
662             * AT -> AT
663             */
664            if ( attributeType.getEquality() != null )
665            {
666                registries.delReference( attributeType, attributeType.getEquality() );
667            }
668
669            if ( attributeType.getOrdering() != null )
670            {
671                registries.delReference( attributeType, attributeType.getOrdering() );
672            }
673
674            if ( attributeType.getSubstring() != null )
675            {
676                registries.delReference( attributeType, attributeType.getSubstring() );
677            }
678
679            if ( attributeType.getSyntax() != null )
680            {
681                registries.delReference( attributeType, attributeType.getSyntax() );
682            }
683
684            if ( attributeType.getSuperior() != null )
685            {
686                registries.delReference( attributeType, attributeType.getSuperior() );
687            }
688        }
689    }
690}