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