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