View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.api.ldap.model.schema.registries;
21  
22  
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.Map;
26  
27  import org.apache.directory.api.asn1.util.Oid;
28  import org.apache.directory.api.i18n.I18n;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
31  import org.apache.directory.api.ldap.model.exception.LdapSchemaExceptionCodes;
32  import org.apache.directory.api.ldap.model.schema.LoadableSchemaObject;
33  import org.apache.directory.api.ldap.model.schema.SchemaObject;
34  import org.apache.directory.api.ldap.model.schema.SchemaObjectType;
35  import org.apache.directory.api.util.Strings;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  
40  /**
41   * Common schema object registry interface.
42   * 
43   * @param <T> The type of SchemaObject
44   *
45   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
46   */
47  public abstract class DefaultSchemaObjectRegistry<T extends SchemaObject> implements SchemaObjectRegistry<T>,
48      Iterable<T>
49  {
50      /** static class logger */
51      private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaObjectRegistry.class );
52  
53      /** A speedup for debug */
54      private static final boolean DEBUG = LOG.isDebugEnabled();
55  
56      /** a map of SchemaObject looked up by name */
57      protected Map<String, T> byName;
58  
59      /** The SchemaObject type, used by the toString() method  */
60      protected SchemaObjectType schemaObjectType;
61  
62      /** the global OID Registry */
63      protected OidRegistry<T> oidRegistry;
64      
65      /** A flag indicating that the Registry is relaxed or not */
66      private boolean isRelaxed;
67  
68  
69      /**
70       * Creates a new DefaultSchemaObjectRegistry instance.
71       * 
72       * @param schemaObjectType The Schema Object type
73       * @param oidRegistry The OID registry to use
74       */
75      protected DefaultSchemaObjectRegistry( SchemaObjectType schemaObjectType, OidRegistry<T> oidRegistry )
76      {
77          byName = new HashMap<>();
78          this.schemaObjectType = schemaObjectType;
79          this.oidRegistry = oidRegistry;
80          this.isRelaxed = Registries.STRICT;
81      }
82      
83      /**
84       * Tells if the Registry is permissive or if it must be checked
85       * against inconsistencies.
86       *
87       * @return True if SchemaObjects can be added even if they break the consistency
88       */
89      public boolean isRelaxed()
90      {
91          return isRelaxed;
92      }
93  
94  
95      /**
96       * Tells if the Registry is strict.
97       *
98       * @return True if SchemaObjects cannot be added if they break the consistency
99       */
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 }