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