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