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.server.core.normalization;
021
022
023import org.apache.directory.api.ldap.model.constants.SchemaConstants;
024import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
025import org.apache.directory.api.ldap.model.entry.Entry;
026import org.apache.directory.api.ldap.model.entry.Modification;
027import org.apache.directory.api.ldap.model.entry.Value;
028import org.apache.directory.api.ldap.model.exception.LdapException;
029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException;
030import org.apache.directory.api.ldap.model.filter.AndNode;
031import org.apache.directory.api.ldap.model.filter.BranchNode;
032import org.apache.directory.api.ldap.model.filter.EqualityNode;
033import org.apache.directory.api.ldap.model.filter.ExprNode;
034import org.apache.directory.api.ldap.model.filter.LeafNode;
035import org.apache.directory.api.ldap.model.filter.NotNode;
036import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
037import org.apache.directory.api.ldap.model.filter.OrNode;
038import org.apache.directory.api.ldap.model.filter.PresenceNode;
039import org.apache.directory.api.ldap.model.filter.UndefinedNode;
040import org.apache.directory.api.ldap.model.name.Ava;
041import org.apache.directory.api.ldap.model.name.Dn;
042import org.apache.directory.api.ldap.model.name.Rdn;
043import org.apache.directory.api.ldap.model.schema.AttributeType;
044import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
045import org.apache.directory.api.ldap.model.schema.normalizers.NameComponentNormalizer;
046import org.apache.directory.server.core.api.DirectoryService;
047import org.apache.directory.server.core.api.InterceptorEnum;
048import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
049import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
050import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
051import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
052import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
053import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
054import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
055import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
056import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
057import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
058import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
059import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
060import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
061import org.apache.directory.server.core.api.normalization.FilterNormalizingVisitor;
062import org.apache.directory.server.i18n.I18n;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066
067/**
068 * A name normalization service.  This service makes sure all relative and distinguished
069 * names are normalized before calls are made against the respective interface methods
070 * on DefaultPartitionNexus.
071 *
072 * The Filters are also normalized.
073 *
074 * If the Rdn AttributeTypes are not present in the entry for an Add request,
075 * they will be added.
076 *
077 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
078 */
079public class NormalizationInterceptor extends BaseInterceptor
080{
081    /** logger used by this class */
082    private static final Logger LOG = LoggerFactory.getLogger( NormalizationInterceptor.class );
083
084    /** a filter node value normalizer and undefined node remover */
085    private FilterNormalizingVisitor normVisitor;
086
087
088    /**
089     * Creates a new instance of a NormalizationInterceptor.
090     */
091    public NormalizationInterceptor()
092    {
093        super( InterceptorEnum.NORMALIZATION_INTERCEPTOR );
094    }
095
096
097    /**
098     * Initialize the registries, normalizers.
099     */
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}