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.server.core.normalization;
21  
22  
23  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
24  import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
25  import org.apache.directory.api.ldap.model.entry.Entry;
26  import org.apache.directory.api.ldap.model.entry.Modification;
27  import org.apache.directory.api.ldap.model.entry.Value;
28  import org.apache.directory.api.ldap.model.exception.LdapException;
29  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException;
30  import org.apache.directory.api.ldap.model.filter.AndNode;
31  import org.apache.directory.api.ldap.model.filter.BranchNode;
32  import org.apache.directory.api.ldap.model.filter.EqualityNode;
33  import org.apache.directory.api.ldap.model.filter.ExprNode;
34  import org.apache.directory.api.ldap.model.filter.LeafNode;
35  import org.apache.directory.api.ldap.model.filter.NotNode;
36  import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
37  import org.apache.directory.api.ldap.model.filter.OrNode;
38  import org.apache.directory.api.ldap.model.filter.PresenceNode;
39  import org.apache.directory.api.ldap.model.filter.UndefinedNode;
40  import org.apache.directory.api.ldap.model.name.Ava;
41  import org.apache.directory.api.ldap.model.name.Dn;
42  import org.apache.directory.api.ldap.model.name.Rdn;
43  import org.apache.directory.api.ldap.model.schema.AttributeType;
44  import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
45  import org.apache.directory.api.ldap.model.schema.normalizers.NameComponentNormalizer;
46  import org.apache.directory.server.core.api.DirectoryService;
47  import org.apache.directory.server.core.api.InterceptorEnum;
48  import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
49  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
50  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
51  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
52  import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
53  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
54  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
55  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
56  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
57  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
58  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
59  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
60  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
61  import org.apache.directory.server.core.api.normalization.FilterNormalizingVisitor;
62  import org.apache.directory.server.i18n.I18n;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  
67  /**
68   * A name normalization service.  This service makes sure all relative and distinguished
69   * names are normalized before calls are made against the respective interface methods
70   * on DefaultPartitionNexus.
71   *
72   * The Filters are also normalized.
73   *
74   * If the Rdn AttributeTypes are not present in the entry for an Add request,
75   * they will be added.
76   *
77   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
78   */
79  public class NormalizationInterceptor extends BaseInterceptor
80  {
81      /** logger used by this class */
82      private static final Logger LOG = LoggerFactory.getLogger( NormalizationInterceptor.class );
83  
84      /** a filter node value normalizer and undefined node remover */
85      private FilterNormalizingVisitor normVisitor;
86  
87  
88      /**
89       * Creates a new instance of a NormalizationInterceptor.
90       */
91      public NormalizationInterceptor()
92      {
93          super( InterceptorEnum.NORMALIZATION_INTERCEPTOR );
94      }
95  
96  
97      /**
98       * Initialize the registries, normalizers.
99       */
100     @Override
101     public void init( DirectoryService directoryService ) throws LdapException
102     {
103         LOG.debug( "Initialiazing the NormalizationInterceptor" );
104 
105         super.init( directoryService );
106 
107         NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
108         normVisitor = new FilterNormalizingVisitor( ncn, schemaManager );
109     }
110 
111 
112     /**
113      * The destroy method does nothing
114      */
115     @Override
116     public void destroy()
117     {
118     }
119 
120 
121     // ------------------------------------------------------------------------
122     // Normalize all Name based arguments for ContextPartition interface operations
123     // ------------------------------------------------------------------------
124     /**
125      * {@inheritDoc}
126      */
127     @Override
128     public void add( AddOperationContext addContext ) throws LdapException
129     {
130         Dn addDn = addContext.getDn();
131         
132         if ( !addDn.isSchemaAware() )
133         {
134             addContext.setDn( new Dn( schemaManager, addDn ) );
135         }
136         
137         Dn entryDn = addContext.getEntry().getDn();
138         
139         if ( !entryDn.isSchemaAware() )
140         {
141             addContext.getEntry().setDn( new Dn( schemaManager, entryDn ) );
142         }
143         
144         addRdnAttributesToEntry( addContext.getDn(), addContext.getEntry() );
145         
146         next( addContext );
147     }
148 
149 
150     /**
151      * {@inheritDoc}
152      */
153     @Override
154     public boolean compare( CompareOperationContext compareContext ) throws LdapException
155     {
156         Dn dn = compareContext.getDn();
157         
158         if ( !dn.isSchemaAware() )
159         {
160             compareContext.setDn( new Dn( schemaManager, dn ) );
161         }
162 
163         // Get the attributeType from the OID
164         try
165         {
166             AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( compareContext.getOid() );
167 
168             // Translate the value from binary to String if the AT is HR
169             if ( attributeType.getSyntax().isHumanReadable() && ( !compareContext.getValue().isHumanReadable() ) )
170             {
171                 compareContext.setValue( compareContext.getValue() );
172             }
173 
174             compareContext.setAttributeType( attributeType );
175         }
176         catch ( LdapException le )
177         {
178             throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_266, compareContext.getOid() ) );
179         }
180 
181         return next( compareContext );
182     }
183 
184 
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     public void delete( DeleteOperationContext deleteContext ) throws LdapException
190     {
191         Dn dn = deleteContext.getDn();
192         
193         if ( !dn.isSchemaAware() )
194         {
195             deleteContext.setDn( new Dn( schemaManager, dn ) );
196         }
197 
198         next( deleteContext );
199     }
200 
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException
207     {
208         Dn dn = hasEntryContext.getDn();
209         
210         if ( !dn.isSchemaAware() )
211         {
212             hasEntryContext.setDn( new Dn( schemaManager, dn ) );
213         }
214 
215         return next( hasEntryContext );
216     }
217 
218 
219     /**
220      * {@inheritDoc}
221      */
222     @Override
223     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
224     {
225         Dn dn = lookupContext.getDn();
226         
227         if ( !dn.isSchemaAware() )
228         {
229             lookupContext.setDn( new Dn( schemaManager, dn ) );
230         }
231 
232         return next( lookupContext );
233     }
234 
235 
236     /**
237      * {@inheritDoc}
238      */
239     @Override
240     public void modify( ModifyOperationContext modifyContext ) throws LdapException
241     {
242         Dn dn = modifyContext.getDn();
243         
244         if ( !dn.isSchemaAware() )
245         {
246             modifyContext.setDn( new Dn( schemaManager, dn ) );
247         }
248 
249         if ( modifyContext.getModItems() != null )
250         {
251             for ( Modification modification : modifyContext.getModItems() )
252             {
253                 AttributeType attributeType = schemaManager.getAttributeType( modification.getAttribute().getId() );
254                 modification.apply( attributeType );
255             }
256         }
257 
258         next( modifyContext );
259     }
260 
261 
262     /**
263      * {@inheritDoc}
264      */
265     @Override
266     public void move( MoveOperationContext moveContext ) throws LdapException
267     {
268         Dn moveDn = moveContext.getDn();
269         
270         if ( !moveDn.isSchemaAware() )
271         {
272             moveContext.setDn( new Dn( schemaManager, moveDn ) );
273         }
274 
275         Dn oldSuperiorDn = moveContext.getOldSuperior();
276         
277         if ( !oldSuperiorDn.isSchemaAware() )
278         {
279             moveContext.setOldSuperior( new Dn( schemaManager, oldSuperiorDn ) );
280         }
281 
282         Dn newSuperiorDn = moveContext.getNewSuperior();
283         
284         if ( !newSuperiorDn.isSchemaAware() )
285         {
286             moveContext.setNewSuperior( new Dn( schemaManager, newSuperiorDn ) );
287         }
288         
289         Dn newDn = moveContext.getNewDn();
290         
291         if ( !newDn.isSchemaAware() )
292         {
293             moveContext.setNewDn( new Dn( schemaManager, newDn ) );
294         }
295 
296         Rdn rdn = moveContext.getRdn();
297         
298         if ( !rdn.isSchemaAware() )
299         {
300             moveContext.setRdn( new Rdn( schemaManager, rdn ) );
301         }
302 
303         next( moveContext );
304     }
305 
306 
307     /**
308      * {@inheritDoc}
309      */
310     @Override
311     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
312     {
313         Rdn newRdn = moveAndRenameContext.getNewRdn();
314         
315         if ( !newRdn.isSchemaAware() )
316         {
317             moveAndRenameContext.setNewRdn( new Rdn( schemaManager, newRdn ) );
318         }
319         
320         Dn dn = moveAndRenameContext.getDn();
321         
322         if ( !dn.isSchemaAware() )
323         {
324             moveAndRenameContext.setDn( new Dn( schemaManager, dn ) );
325         }
326         
327         Dn newDn = moveAndRenameContext.getNewDn();
328         
329         if ( !newDn.isSchemaAware() )
330         {
331             moveAndRenameContext.setNewDn( new Dn( schemaManager, newDn ) );
332         }
333         
334         Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
335         
336         if ( !newSuperiorDn.isSchemaAware() )
337         {
338             moveAndRenameContext.setNewSuperiorDn( new Dn( schemaManager, newSuperiorDn ) );
339         }
340 
341         next( moveAndRenameContext );
342     }
343 
344 
345     /**
346      * {@inheritDoc}
347      */
348     @Override
349     public void rename( RenameOperationContext renameContext ) throws LdapException
350     {
351         // Normalize the new Rdn and the Dn if needed
352         Dn dn = renameContext.getDn();
353         
354         if ( !dn.isSchemaAware() )
355         {
356             renameContext.setDn( new Dn( schemaManager, dn ) );
357         }
358         
359         Rdn newRdn = renameContext.getNewRdn();
360         
361         if ( !newRdn.isSchemaAware() )
362         {
363             renameContext.setNewRdn( new Rdn( schemaManager, newRdn ) );
364         }
365         
366         Dn newDn = renameContext.getNewDn();
367         
368         if ( !newDn.isSchemaAware() )
369         {
370             renameContext.setNewDn( new Dn( schemaManager, newDn ) );
371         }
372 
373         // Push to the next interceptor
374         next( renameContext );
375     }
376 
377 
378     /**
379      * {@inheritDoc}
380      */
381     @Override
382     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
383     {
384         Dn dn = searchContext.getDn();
385         
386         if ( !dn.isSchemaAware() )
387         {
388             searchContext.setDn( new Dn( schemaManager, dn ) );
389         }
390 
391         ExprNode filter = searchContext.getFilter();
392 
393         if ( filter == null )
394         {
395             LOG.warn( "undefined filter based on undefined attributeType not evaluted at all.  Returning empty enumeration." );
396             return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, schemaManager );
397         }
398 
399         // Normalize the filter
400         filter = ( ExprNode ) filter.accept( normVisitor );
401 
402         if ( filter == null )
403         {
404             LOG.warn( "undefined filter based on undefined attributeType not evaluted at all.  Returning empty enumeration." );
405             return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, schemaManager );
406         }
407 
408         // We now have to remove the (ObjectClass=*) filter if it's present, and to add the scope filter
409         ExprNode modifiedFilter = removeObjectClass( filter );
410 
411         searchContext.setFilter( modifiedFilter );
412 
413         // TODO Normalize the returned Attributes, storing the UP attributes to format the returned values.
414         return next( searchContext );
415     }
416 
417 
418     /**
419      * Remove the (ObjectClass=*) node from an AndNode, if we have one.
420      */
421     private ExprNode handleAndNode( ExprNode node )
422     {
423         int nbNodes = 0;
424         AndNode newAndNode = new AndNode();
425 
426         for ( ExprNode child : ( ( BranchNode ) node ).getChildren() )
427         {
428             ExprNode modifiedNode = removeObjectClass( child );
429 
430             if ( !( modifiedNode instanceof ObjectClassNode ) )
431             {
432                 newAndNode.addNode( modifiedNode );
433                 nbNodes++;
434             }
435 
436             if ( modifiedNode instanceof UndefinedNode )
437             {
438                 // We can just return an Undefined node as nothing will get selected
439                 return UndefinedNode.UNDEFINED_NODE;
440             }
441         }
442 
443         switch ( nbNodes )
444         {
445             case 0:
446                 // Unlikely... But (&(ObjectClass=*)) or (|(ObjectClass=*)) are still an option
447                 return ObjectClassNode.OBJECT_CLASS_NODE;
448 
449             case 1:
450                 // We can safely remove the AND/OR node and replace it with its first child
451                 return newAndNode.getFirstChild();
452 
453             default:
454                 return newAndNode;
455         }
456     }
457 
458 
459     /**
460      * Remove the (ObjectClass=*) node from a NotNode, if we have one.
461      */
462     private ExprNode handleNotNode( ExprNode node )
463     {
464         for ( ExprNode child : ( ( BranchNode ) node ).getChildren() )
465         {
466             ExprNode modifiedNode = removeObjectClass( child );
467 
468             if ( modifiedNode instanceof ObjectClassNode )
469             {
470                 // We don't want any entry which has an ObjectClass, return an undefined node
471                 return UndefinedNode.UNDEFINED_NODE;
472             }
473 
474             if ( modifiedNode instanceof UndefinedNode )
475             {
476                 // Here, we will select everything
477                 return ObjectClassNode.OBJECT_CLASS_NODE;
478             }
479         }
480 
481         return node;
482     }
483 
484 
485     /**
486      * Remove the (ObjectClass=*) node from an OrNode, if we have one.
487      */
488     private ExprNode handleOrNode( ExprNode node )
489     {
490         for ( ExprNode child : ( ( BranchNode ) node ).getChildren() )
491         {
492             ExprNode modifiedNode = removeObjectClass( child );
493 
494             if ( modifiedNode instanceof ObjectClassNode )
495             {
496                 // We can return immediately with an ObjectClass node
497                 return ObjectClassNode.OBJECT_CLASS_NODE;
498             }
499         }
500 
501         return node;
502     }
503 
504 
505     /**
506      * Remove the (ObjectClass=*) node from the filter, if we have one.
507      */
508     private ExprNode removeObjectClass( ExprNode node )
509     {
510         if ( node instanceof LeafNode )
511         {
512             LeafNode leafNode = ( LeafNode ) node;
513 
514             if ( leafNode.getAttributeType() == directoryService.getAtProvider().getObjectClass() )
515             {
516                 if ( leafNode instanceof PresenceNode )
517                 {
518                     // We can safely remove the node and return an undefined node
519                     return ObjectClassNode.OBJECT_CLASS_NODE;
520                 }
521                 else if ( leafNode instanceof EqualityNode )
522                 {
523                     Value value = ( ( EqualityNode<String> ) leafNode ).getValue();
524 
525                     if ( value.equals( SchemaConstants.TOP_OC ) )
526                     {
527                         // Here too we can safely remove the node and return an undefined node
528                         return ObjectClassNode.OBJECT_CLASS_NODE;
529                     }
530                 }
531             }
532         }
533 
534         // --------------------------------------------------------------------
535         //                 H A N D L E   B R A N C H   N O D E S
536         // --------------------------------------------------------------------
537 
538         if ( node instanceof AndNode )
539         {
540             return handleAndNode( node );
541         }
542         else if ( node instanceof OrNode )
543         {
544             return handleOrNode( node );
545         }
546         else if ( node instanceof NotNode )
547         {
548             return handleNotNode( node );
549         }
550         else
551         {
552             // Failover : we return the initial node as is
553             return node;
554         }
555     }
556 
557 
558     // ------------------------------------------------------------------------
559     // Normalize all Name based arguments for other interface operations
560     // ------------------------------------------------------------------------
561     /**
562      * Adds missing Rdn's attributes and values to the entry.
563      *
564      * @param dn the Dn
565      * @param entry the entry
566      */
567     private void addRdnAttributesToEntry( Dn dn, Entry entry ) throws LdapException
568     {
569         if ( dn == null || entry == null )
570         {
571             return;
572         }
573 
574         Rdn rdn = dn.getRdn();
575 
576         // Loop on all the AVAs
577         for ( Ava ava : rdn )
578         {
579             Value value = ava.getValue();
580             String upValue = ava.getValue().getString();
581             String upId = ava.getType();
582 
583             // Check that the entry contains this Ava
584             if ( !entry.contains( upId, value ) )
585             {
586                 String message = "The Rdn '" + upId + "=" + upValue + "' is not present in the entry";
587                 LOG.warn( message );
588 
589                 // We don't have this attribute : add it.
590                 // Two cases :
591                 // 1) The attribute does not exist
592                 if ( !entry.containsAttribute( upId ) )
593                 {
594                     entry.add( upId, upValue );
595                 }
596                 // 2) The attribute exists
597                 else
598                 {
599                     AttributeType at = schemaManager.lookupAttributeTypeRegistry( upId );
600 
601                     // 2.1 if the attribute is single valued, replace the value
602                     if ( at.isSingleValued() )
603                     {
604                         entry.removeAttributes( upId );
605                         entry.add( upId, upValue );
606                     }
607                     // 2.2 the attribute is multi-valued : add the missing value
608                     else
609                     {
610                         entry.add( upId, upValue );
611                     }
612                 }
613             }
614         }
615     }
616 }