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     */
020    package org.apache.directory.server.core.normalization;
021    
022    
023    import org.apache.directory.server.core.DirectoryService;
024    import org.apache.directory.server.core.entry.ClonedServerEntry;
025    import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
026    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
027    import org.apache.directory.server.core.interceptor.BaseInterceptor;
028    import org.apache.directory.server.core.interceptor.NextInterceptor;
029    import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
030    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
031    import org.apache.directory.server.core.interceptor.context.BindOperationContext;
032    import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
033    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
034    import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
035    import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
036    import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
037    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
038    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
039    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
040    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
041    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
042    import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
043    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
044    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
045    import org.apache.directory.server.core.partition.DefaultPartitionNexus;
046    import org.apache.directory.shared.ldap.cursor.EmptyCursor;
047    import org.apache.directory.shared.ldap.entry.StringValue;
048    import org.apache.directory.shared.ldap.entry.ServerEntry;
049    import org.apache.directory.shared.ldap.entry.Value;
050    import org.apache.directory.shared.ldap.filter.ExprNode;
051    import org.apache.directory.shared.ldap.name.AVA;
052    import org.apache.directory.shared.ldap.name.DN;
053    import org.apache.directory.shared.ldap.name.NameComponentNormalizer;
054    import org.apache.directory.shared.ldap.name.RDN;
055    import org.apache.directory.shared.ldap.schema.AttributeType;
056    import org.apache.directory.shared.ldap.schema.SchemaManager;
057    import org.apache.directory.shared.ldap.schema.normalizers.ConcreteNameComponentNormalizer;
058    import org.slf4j.Logger;
059    import org.slf4j.LoggerFactory;
060    
061    
062    /**
063     * A name normalization service.  This service makes sure all relative and distinguished
064     * names are normalized before calls are made against the respective interface methods
065     * on {@link DefaultPartitionNexus}.
066     * 
067     * The Filters are also normalized.
068     * 
069     * If the RDN AttributeTypes are not present in the entry for an Add request,
070     * they will be added.
071     *
072     * @org.apache.xbean.XBean
073     *
074     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
075     * @version $Rev: 928945 $
076     */
077    public class NormalizationInterceptor extends BaseInterceptor
078    {
079        /** logger used by this class */
080        private static final Logger LOG = LoggerFactory.getLogger( NormalizationInterceptor.class );
081    
082        /** a filter node value normalizer and undefined node remover */
083        private FilterNormalizingVisitor normVisitor;
084    
085        /** The attributeType registry */
086        private SchemaManager schemaManager;
087    
088        /**
089         * Initialize the registries, normalizers. 
090         */
091        public void init( DirectoryService directoryService ) throws Exception
092        {
093            LOG.debug( "Initialiazing the NormalizationInterceptor" );
094            
095            schemaManager = directoryService.getSchemaManager();
096            NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
097            normVisitor = new FilterNormalizingVisitor( ncn, schemaManager );
098        }
099    
100        /**
101         * The destroy method does nothing
102         */
103        public void destroy()
104        {
105        }
106    
107        // ------------------------------------------------------------------------
108        // Normalize all Name based arguments for ContextPartition interface operations
109        // ------------------------------------------------------------------------
110        /**
111         * {@inheritDoc}
112         */
113        public void add( NextInterceptor nextInterceptor, AddOperationContext opContext ) throws Exception
114        {
115            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
116            opContext.getEntry().getDn().normalize( schemaManager.getNormalizerMapping() );
117            addRdnAttributesToEntry( opContext.getDn(), opContext.getEntry() );
118            nextInterceptor.add( opContext );
119        }
120    
121    
122        /**
123         * {@inheritDoc}
124         */
125        public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws Exception
126        {
127            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
128            nextInterceptor.delete( opContext );
129        }
130    
131    
132        /**
133         * {@inheritDoc}
134         */
135        public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext ) throws Exception
136        {
137            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
138            nextInterceptor.modify( opContext );
139        }
140    
141    
142        /**
143         * {@inheritDoc}
144         */
145        public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext ) throws Exception
146        {
147            // Normalize the new RDN and the DN
148            opContext.getNewRdn().normalize( schemaManager.getNormalizerMapping() );
149            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
150            opContext.getNewDn().normalize( schemaManager.getNormalizerMapping() );
151    
152            // Push to the next interceptor
153            nextInterceptor.rename( opContext );
154        }
155    
156    
157        /**
158         * {@inheritDoc}
159         */
160        public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception
161        {
162            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
163            opContext.getParent().normalize( schemaManager.getNormalizerMapping());
164            nextInterceptor.move( opContext );
165        }
166    
167    
168        /**
169         * {@inheritDoc}
170         */
171        public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext )
172            throws Exception
173        {
174            DN rdn = new DN();
175            rdn.add( opContext.getNewRdn() );
176            rdn.normalize( schemaManager.getNormalizerMapping() );
177            opContext.setNewRdn( rdn.getRdn() );
178    
179            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
180            opContext.getParent().normalize( schemaManager.getNormalizerMapping() );
181            nextInterceptor.moveAndRename( opContext );
182        }
183    
184    
185        /**
186         * {@inheritDoc}
187         */
188        public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
189        {
190            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
191    
192            ExprNode filter = opContext.getFilter();
193            
194            // Normalize the filter
195            ExprNode result = ( ExprNode ) filter.accept( normVisitor );
196    
197            if ( result == null )
198            {
199                LOG.warn( "undefined filter based on undefined attributeType not evaluted at all.  Returning empty enumeration." );
200                return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
201            }
202            else
203            {
204                opContext.setFilter( result );
205                
206                // TODO Normalize the returned Attributes, storing the UP attributes to format the returned values.
207                return nextInterceptor.search( opContext );
208            }
209        }
210    
211    
212        /**
213         * {@inheritDoc}
214         */
215        public boolean hasEntry( NextInterceptor nextInterceptor, EntryOperationContext opContext ) throws Exception
216        {
217            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
218            return nextInterceptor.hasEntry( opContext );
219        }
220    
221    
222        /**
223         * {@inheritDoc}
224         */
225        public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
226        {
227            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
228            return nextInterceptor.list( opContext );
229        }
230    
231        
232        /**
233         * {@inheritDoc}
234         */
235        private String[] normalizeAttrsId( String[] attrIds ) throws Exception
236        {
237            if ( attrIds == null )
238            {
239                return attrIds;
240            }
241            
242            String[] normalizedAttrIds = new String[attrIds.length];
243            int pos = 0;
244            
245            for ( String id:attrIds )
246            {
247                String oid = schemaManager.lookupAttributeTypeRegistry( id ).getOid();
248                normalizedAttrIds[pos++] = oid;
249            }
250            
251            return normalizedAttrIds;
252        }
253    
254        
255        /**
256         * {@inheritDoc}
257         */
258        public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception
259        {
260            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
261            
262            if ( opContext.getAttrsId() != null )
263            {
264                // We have to normalize the requested IDs
265                opContext.setAttrsId( normalizeAttrsId( opContext.getAttrsIdArray() ) );
266            }
267            
268            return nextInterceptor.lookup( opContext );
269        }
270    
271    
272        // ------------------------------------------------------------------------
273        // Normalize all Name based arguments for other interface operations
274        // ------------------------------------------------------------------------
275        /**
276         * {@inheritDoc}
277         */
278        public DN getMatchedName ( NextInterceptor nextInterceptor, GetMatchedNameOperationContext opContext ) throws Exception
279        {
280            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
281            return nextInterceptor.getMatchedName( opContext );
282        }
283    
284    
285        /**
286         * {@inheritDoc}
287         */
288        public DN getSuffix ( NextInterceptor nextInterceptor, GetSuffixOperationContext opContext ) throws Exception
289        {
290            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
291            return nextInterceptor.getSuffix( opContext );
292        }
293    
294    
295        /**
296         * {@inheritDoc}
297         */
298        public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception
299        {
300            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
301            
302            AttributeType at = opContext.getSession().getDirectoryService().getSchemaManager().lookupAttributeTypeRegistry( opContext.getOid() );
303            
304            if ( at.getSyntax().isHumanReadable() && ( opContext.getValue().isBinary() ) )
305            {
306                String value = opContext.getValue().getString();
307                opContext.setValue( new StringValue( value ) );
308            }
309            
310            return next.compare( opContext );
311        }
312        
313        
314        /**
315         * {@inheritDoc}
316         */
317        public void bind( NextInterceptor next, BindOperationContext opContext )  throws Exception
318        {
319            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
320            next.bind( opContext );
321        }
322    
323    
324        /**
325         * {@inheritDoc}
326         */
327        public void addContextPartition( NextInterceptor next, AddContextPartitionOperationContext opContext ) throws Exception
328        {
329            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
330            next.addContextPartition( opContext );
331        }
332    
333    
334        /**
335         * {@inheritDoc}
336         */
337        public void removeContextPartition( NextInterceptor next, RemoveContextPartitionOperationContext opContext ) throws Exception
338        {
339            opContext.getDn().normalize( schemaManager.getNormalizerMapping() );
340            next.removeContextPartition( opContext );
341        }
342    
343    
344        /**
345         * Adds missing RDN's attributes and values to the entry.
346         *
347         * @param dn the DN
348         * @param entry the entry
349         */
350        private void addRdnAttributesToEntry( DN dn, ServerEntry entry ) throws Exception
351        {
352            if ( dn == null || entry == null )
353            {
354                return;
355            }
356    
357            RDN rdn = dn.getRdn();
358    
359            // Loop on all the AVAs
360            for ( AVA ava : rdn )
361            {
362                Value<?> value = ava.getNormValue();
363                Value<?> upValue = ava.getUpValue();
364                String upId = ava.getUpType();
365    
366                // Check that the entry contains this AVA
367                if ( !entry.contains( upId, value ) )
368                {
369                    String message = "The RDN '" + upId + "=" + upValue + "' is not present in the entry";
370                    LOG.warn( message );
371    
372                    // We don't have this attribute : add it.
373                    // Two cases : 
374                    // 1) The attribute does not exist
375                    if ( !entry.containsAttribute( upId ) )
376                    {
377                        entry.add( upId, upValue );
378                    }
379                    // 2) The attribute exists
380                    else
381                    {
382                        AttributeType at = schemaManager.lookupAttributeTypeRegistry( upId );
383    
384                        // 2.1 if the attribute is single valued, replace the value
385                        if ( at.isSingleValued() )
386                        {
387                            entry.removeAttributes( upId );
388                            entry.add( upId, upValue );
389                        }
390                        // 2.2 the attribute is multi-valued : add the missing value
391                        else
392                        {
393                            entry.add( upId, upValue );
394                        }
395                    }
396                }
397            }
398        }
399    
400    }