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   *     https://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.SchemaErrorHandler;
34  import org.apache.directory.api.ldap.model.schema.SchemaObject;
35  import org.apache.directory.api.ldap.model.schema.SchemaObjectType;
36  import org.apache.directory.api.util.Strings;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  
41  /**
42   * Common schema object registry interface.
43   * 
44   * @param <T> The type of SchemaObject
45   *
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   */
48  public abstract class DefaultSchemaObjectRegistry<T extends SchemaObject> implements SchemaObjectRegistry<T>,
49      Iterable<T>
50  {
51      /** static class logger */
52      private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaObjectRegistry.class );
53  
54      /** a map of SchemaObject looked up by name */
55      protected Map<String, T> byName;
56  
57      /** The SchemaObject type, used by the toString() method  */
58      protected SchemaObjectType schemaObjectType;
59  
60      /** the global OID Registry */
61      protected OidRegistry<T> oidRegistry;
62      
63      /** A flag indicating that the Registry is relaxed or not */
64      private boolean isRelaxed;
65  
66      private SchemaErrorHandler errorHandler;
67  
68      /**
69       * Creates a new DefaultSchemaObjectRegistry instance.
70       * 
71       * @param schemaObjectType The Schema Object type
72       * @param oidRegistry The OID registry to use
73       */
74      protected DefaultSchemaObjectRegistry( SchemaObjectType schemaObjectType, OidRegistry<T> oidRegistry )
75      {
76          byName = new HashMap<>();
77          this.schemaObjectType = schemaObjectType;
78          this.oidRegistry = oidRegistry;
79          this.isRelaxed = Registries.STRICT;
80      }
81      
82      /**
83       * Tells if the Registry is permissive or if it must be checked
84       * against inconsistencies.
85       *
86       * @return True if SchemaObjects can be added even if they break the consistency
87       */
88      public boolean isRelaxed()
89      {
90          return isRelaxed;
91      }
92  
93  
94      /**
95       * Tells if the Registry is strict.
96       *
97       * @return True if SchemaObjects cannot be added if they break the consistency
98       */
99      public boolean isStrict()
100     {
101         return !isRelaxed;
102     }
103 
104 
105     /**
106      * Change the Registry to a relaxed mode, where invalid SchemaObjects
107      * can be registered.
108      */
109     public void setRelaxed()
110     {
111         isRelaxed = Registries.RELAXED;
112         oidRegistry.setRelaxed();
113     }
114 
115 
116     /**
117      * Change the Registry to a strict mode, where invalid SchemaObjects
118      * cannot be registered.
119      */
120     public void setStrict()
121     {
122         isRelaxed = Registries.STRICT;
123         oidRegistry.setStrict();
124     }
125     
126 
127     public SchemaErrorHandler getErrorHandler()
128     {
129         return errorHandler;
130     }
131 
132     public void setErrorHandler( SchemaErrorHandler errorHandler )
133     {
134         this.errorHandler = errorHandler;
135         oidRegistry.setErrorHandler( errorHandler );
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     @Override
142     public boolean contains( String oid )
143     {
144         if ( !byName.containsKey( oid ) )
145         {
146             return byName.containsKey( Strings.toLowerCaseAscii( oid ) );
147         }
148 
149         return true;
150     }
151 
152 
153     /**
154      * {@inheritDoc}
155      */
156     @Override
157     public String getSchemaName( String oid ) throws LdapException
158     {
159         if ( !Oid.isOid( oid ) )
160         {
161             String msg = I18n.err( I18n.ERR_13733_ARG_NOT_NUMERIC_OID );
162             
163             if ( LOG.isWarnEnabled() )
164             {
165                 LOG.warn( msg );
166             }
167             
168             throw new LdapException( msg );
169         }
170 
171         SchemaObject schemaObject = byName.get( oid );
172 
173         if ( schemaObject != null )
174         {
175             return schemaObject.getSchemaName();
176         }
177 
178         String msg = I18n.err( I18n.ERR_13734_OID_NOT_FOUND, oid );
179         
180         if ( LOG.isWarnEnabled() )
181         {
182             LOG.warn( msg );
183         }
184         
185         throw new LdapException( msg );
186     }
187 
188 
189     /**
190      * {@inheritDoc}
191      */
192     @Override
193     public void renameSchema( String originalSchemaName, String newSchemaName )
194     {
195         // Loop on all the SchemaObjects stored and remove those associated
196         // with the give schemaName
197         for ( T schemaObject : this )
198         {
199             if ( originalSchemaName.equalsIgnoreCase( schemaObject.getSchemaName() ) )
200             {
201                 schemaObject.setSchemaName( newSchemaName );
202 
203                 if ( LOG.isDebugEnabled() )
204                 {
205                     LOG.debug( I18n.msg( I18n.MSG_13722_RENAMED_SCHEMA_NAME_TO, schemaObject, newSchemaName ) );
206                 }
207             }
208         }
209     }
210 
211 
212     /**
213      * {@inheritDoc}
214      */
215     @Override
216     public Iterator<T> iterator()
217     {
218         return oidRegistry.iterator();
219     }
220 
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
226     public Iterator<String> oidsIterator()
227     {
228         return byName.keySet().iterator();
229     }
230 
231 
232     /**
233      * {@inheritDoc}
234      */
235     @Override
236     public T lookup( String oid ) throws LdapException
237     {
238         if ( oid == null )
239         {
240             return null;
241         }
242 
243         T schemaObject = byName.get( oid );
244 
245         if ( schemaObject == null )
246         {
247             // let's try with trimming and lowercasing now
248             schemaObject = byName.get( Strings.trim( Strings.toLowerCaseAscii( oid ) ) );
249 
250             if ( schemaObject == null )
251             {
252                 String msg = I18n.err( I18n.ERR_13735_ELEMENT_FOR_OID_DOES_NOT_EXIST, schemaObjectType.name(), oid );
253 
254                 if ( LOG.isDebugEnabled() )
255                 {
256                     LOG.debug( msg );
257                 }
258                 
259                 throw new LdapException( msg );
260             }
261         }
262 
263         if ( LOG.isDebugEnabled() )
264         {
265             LOG.debug( I18n.msg( I18n.MSG_13723_FOUND_WITH_OID, schemaObject, oid ) );
266         }
267 
268         return schemaObject;
269     }
270 
271 
272     /**
273      * {@inheritDoc}
274      */
275     @Override
276     public void register( T schemaObject ) throws LdapException
277     {
278         String oid = schemaObject.getOid();
279 
280         if ( byName.containsKey( oid ) )
281         {
282             String msg = I18n.err( I18n.ERR_13736_ELEMENT_FOR_OID_ALREADY_REGISTERED, schemaObjectType.name(), oid );
283             
284             if ( LOG.isWarnEnabled() )
285             {
286                 LOG.warn( msg );
287             }
288             
289             LdapSchemaException ldapSchemaException = new LdapSchemaException( 
290                 LdapSchemaExceptionCodes.OID_ALREADY_REGISTERED, msg );
291             ldapSchemaException.setSourceObject( schemaObject );
292             throw ldapSchemaException;
293         }
294 
295         byName.put( oid, schemaObject );
296 
297         /*
298          * add the aliases/names to the name map along with their toLowerCase
299          * versions of the name: this is used to make sure name lookups work
300          */
301         for ( String name : schemaObject.getNames() )
302         {
303             String lowerName = Strings.trim( Strings.toLowerCaseAscii( name ) );
304 
305             if ( byName.containsKey( lowerName ) )
306             {
307                 String msg = I18n.err( I18n.ERR_13737_ELEMENT_WITH_NAME_ALREADY_REGISTERED, schemaObjectType.name(), name );
308                 
309                 if ( LOG.isWarnEnabled() )
310                 {
311                     LOG.warn( msg );
312                 }
313                 
314                 LdapSchemaException ldapSchemaException = new LdapSchemaException(
315                     LdapSchemaExceptionCodes.NAME_ALREADY_REGISTERED, msg );
316                 ldapSchemaException.setSourceObject( schemaObject );
317                 throw ldapSchemaException;
318             }
319             else
320             {
321                 byName.put( lowerName, schemaObject );
322             }
323         }
324 
325         // And register the oid -> schemaObject relation
326         oidRegistry.register( schemaObject );
327 
328         if ( LOG.isDebugEnabled() )
329         {
330             LOG.debug( I18n.msg( I18n.MSG_13731_REGISTRED_FOR_OID, schemaObject.getName(), oid ) );
331         }
332     }
333 
334 
335     /**
336      * {@inheritDoc}
337      */
338     @Override
339     public T unregister( String numericOid ) throws LdapException
340     {
341         if ( !Oid.isOid( numericOid ) )
342         {
343             String msg = I18n.err( I18n.ERR_13738_OID_NOT_A_NUMERIC_OID, numericOid );
344             LOG.error( msg );
345             throw new LdapException( msg );
346         }
347 
348         T schemaObject = byName.remove( numericOid );
349 
350         for ( String name : schemaObject.getNames() )
351         {
352             byName.remove( name );
353         }
354 
355         // And remove the SchemaObject from the oidRegistry
356         oidRegistry.unregister( numericOid );
357 
358         if ( LOG.isDebugEnabled() )
359         {
360             LOG.debug( I18n.msg( I18n.MSG_13702_REMOVED_FROM_REGISTRY, schemaObject, numericOid ) );
361         }
362 
363         return schemaObject;
364     }
365 
366 
367     /**
368      * {@inheritDoc}
369      */
370     @Override
371     public T unregister( T schemaObject ) throws LdapException
372     {
373         String oid = schemaObject.getOid();
374 
375         if ( !byName.containsKey( oid ) )
376         {
377             String msg = I18n.err( I18n.ERR_13739_ELEMENT_WITH_OID_NOT_REGISTERED, schemaObjectType.name(), oid );
378             
379             if ( LOG.isWarnEnabled() )
380             {
381                 LOG.warn( msg );
382             }
383             
384             throw new LdapException( msg );
385         }
386 
387         // Remove the oid
388         T removed = byName.remove( oid );
389 
390         /*
391          * Remove the aliases/names from the name map along with their toLowerCase
392          * versions of the name.
393          */
394         for ( String name : schemaObject.getNames() )
395         {
396             byName.remove( Strings.trim( Strings.toLowerCaseAscii( name ) ) );
397         }
398 
399         // And unregister the oid -> schemaObject relation
400         oidRegistry.unregister( oid );
401 
402         return removed;
403     }
404 
405 
406     /**
407      * {@inheritDoc}
408      */
409     @Override
410     public void unregisterSchemaElements( String schemaName ) throws LdapException
411     {
412         if ( schemaName == null )
413         {
414             return;
415         }
416 
417         // Loop on all the SchemaObjects stored and remove those associated
418         // with the give schemaName
419         for ( T schemaObject : this )
420         {
421             if ( schemaName.equalsIgnoreCase( schemaObject.getSchemaName() ) )
422             {
423                 String oid = schemaObject.getOid();
424                 SchemaObject removed = unregister( oid );
425 
426                 if ( LOG.isDebugEnabled() )
427                 {
428                     LOG.debug( I18n.msg( I18n.MSG_13702_REMOVED_FROM_REGISTRY, removed, oid ) );
429                 }
430             }
431         }
432     }
433 
434 
435     /**
436      * {@inheritDoc}
437      */
438     @Override
439 public String getOidByName( String name ) throws LdapException
440     {
441         T schemaObject = byName.get( name );
442 
443         if ( schemaObject == null )
444         {
445             // last resort before giving up check with lower cased version
446             String lowerCased = Strings.toLowerCaseAscii( name );
447 
448             schemaObject = byName.get( lowerCased );
449 
450             // ok this name is not for a schema object in the registry
451             if ( schemaObject == null )
452             {
453                 throw new LdapException( I18n.err( I18n.ERR_13740_CANNOT_FIND_OID_FROM_NAME, name ) );
454             }
455         }
456 
457         // we found the schema object by key on the first lookup attempt
458         return schemaObject.getOid();
459     }
460 
461 
462     /**
463      * Copy a SchemaObject registry
464      * 
465      * @param original The SchemaObject registry to copy
466      * @return The copied ShcemaObject registry
467      */
468     // This will suppress PMD.EmptyCatchBlock warnings in this method
469     @SuppressWarnings("unchecked")
470     public SchemaObjectRegistry<T> copy( SchemaObjectRegistry<T> original )
471     {
472         // Fill the byName and OidRegistry maps, the type has already be copied
473         for ( Map.Entry<String, T> entry : ( ( DefaultSchemaObjectRegistry<T> ) original ).byName.entrySet() )
474         {
475             String key = entry.getKey();
476             // Clone each SchemaObject
477             T value = entry.getValue();
478 
479             if ( value instanceof LoadableSchemaObject )
480             {
481                 // Update the data structure. 
482                 // Comparators, Normalizers and SyntaxCheckers aren't copied, 
483                 // they are immutable
484                 byName.put( key, value );
485 
486                 // Update the OidRegistry
487                 oidRegistry.put( value );
488             }
489             else
490             {
491                 T copiedValue = null;
492 
493                 // Copy the value if it's not already in the oidRegistry
494                 if ( oidRegistry.contains( value.getOid() ) )
495                 {
496                     try
497                     {
498                         copiedValue = oidRegistry.getSchemaObject( value.getOid() );
499                     }
500                     catch ( LdapException ne )
501                     {
502                         // Can't happen
503                     }
504                 }
505                 else
506                 {
507                     copiedValue = ( T ) value.copy();
508                 }
509 
510                 // Update the data structure. 
511                 byName.put( key, copiedValue );
512 
513                 // Update the OidRegistry
514                 oidRegistry.put( copiedValue );
515             }
516         }
517 
518         return this;
519     }
520 
521 
522     /**
523      * {@inheritDoc}
524      */
525     @Override
526     public T get( String oid )
527     {
528         try
529         {
530             return oidRegistry.getSchemaObject( oid );
531         }
532         catch ( LdapException ne )
533         {
534             return null;
535         }
536     }
537 
538 
539     /**
540      * {@inheritDoc}
541      */
542     @Override
543     public SchemaObjectType getType()
544     {
545         return schemaObjectType;
546     }
547 
548 
549     /**
550      * {@inheritDoc}
551      */
552     @Override
553     public int size()
554     {
555         return oidRegistry.size();
556     }
557 
558 
559     /**
560      * @see Object#toString()
561      */
562     @Override
563     public String toString()
564     {
565         StringBuilder sb = new StringBuilder();
566 
567         sb.append( schemaObjectType ).append( ": " );
568         boolean isFirst = true;
569 
570         for ( Map.Entry<String, T> entry : byName.entrySet() )
571         {
572             if ( isFirst )
573             {
574                 isFirst = false;
575             }
576             else
577             {
578                 sb.append( ", " );
579             }
580 
581             String name = entry.getKey();
582             T schemaObject = entry.getValue();
583 
584             sb.append( '<' ).append( name ).append( ", " ).append( schemaObject.getOid() ).append( '>' );
585         }
586 
587         return sb.toString();
588     }
589 
590 
591     /**
592      * {@inheritDoc}
593      */
594     @Override
595     public void clear()
596     {
597         // Clear all the schemaObjects
598         for ( SchemaObject schemaObject : oidRegistry )
599         {
600             // Don't clear LoadableSchemaObject
601             if ( !( schemaObject instanceof LoadableSchemaObject ) )
602             {
603                 schemaObject.clear();
604             }
605         }
606 
607         // Remove the byName elements
608         byName.clear();
609 
610         // Clear the OidRegistry
611         oidRegistry.clear();
612     }
613 }