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