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.collective;
021    
022    
023    import java.util.HashSet;
024    import java.util.Set;
025    
026    import javax.naming.NamingException;
027    
028    import org.apache.directory.server.core.DirectoryService;
029    import org.apache.directory.server.core.entry.ClonedServerEntry;
030    import org.apache.directory.server.core.filtering.EntryFilter;
031    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
032    import org.apache.directory.server.core.interceptor.BaseInterceptor;
033    import org.apache.directory.server.core.interceptor.NextInterceptor;
034    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
035    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
036    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
037    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
038    import org.apache.directory.server.core.interceptor.context.OperationContext;
039    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
040    import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
041    import org.apache.directory.server.core.partition.ByPassConstants;
042    import org.apache.directory.server.core.partition.PartitionNexus;
043    import org.apache.directory.shared.ldap.constants.SchemaConstants;
044    import org.apache.directory.shared.ldap.entry.DefaultServerAttribute;
045    import org.apache.directory.shared.ldap.entry.EntryAttribute;
046    import org.apache.directory.shared.ldap.entry.ServerEntry;
047    import org.apache.directory.shared.ldap.entry.Value;
048    import org.apache.directory.shared.ldap.name.DN;
049    import org.apache.directory.shared.ldap.schema.AttributeType;
050    import org.apache.directory.shared.ldap.schema.SchemaManager;
051    
052    
053    /**
054     * An interceptor based service dealing with collective attribute
055     * management.  This service intercepts read operations on entries to
056     * inject collective attribute value pairs into the response based on
057     * the entires inclusion within collectiveAttributeSpecificAreas and
058     * collectiveAttributeInnerAreas.
059     *
060     * @org.apache.xbean.XBean
061     *
062     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
063     * @version $Rev: 927380 $
064     */
065    public class CollectiveAttributeInterceptor extends BaseInterceptor
066    {
067        /** The SchemaManager */
068        private SchemaManager schemaManager;
069        
070        private PartitionNexus nexus;
071        
072        private CollectiveAttributesSchemaChecker collectiveAttributesSchemaChecker;
073    
074    
075        /**
076         * the search result filter to use for collective attribute injection
077         */
078        private final EntryFilter SEARCH_FILTER = new EntryFilter()
079        {
080            public boolean accept( SearchingOperationContext operation, ClonedServerEntry result )
081                throws Exception
082            {
083                DN name = result.getDn();
084                
085                if ( name.isNormalized() == false )
086                {
087                    name = DN.normalize( name, schemaManager.getNormalizerMapping() );
088                }
089                
090                String[] retAttrs = operation.getSearchControls().getReturningAttributes();
091                addCollectiveAttributes( operation, result, retAttrs );
092                return true;
093            }
094        };
095    
096        public void init( DirectoryService directoryService ) throws Exception
097        {
098            super.init( directoryService );
099            schemaManager = directoryService.getSchemaManager();
100            nexus = directoryService.getPartitionNexus();
101            collectiveAttributesSchemaChecker = new CollectiveAttributesSchemaChecker( nexus, schemaManager );
102        }
103    
104    
105        /**
106         * Adds the set of collective attributes requested in the returning attribute list
107         * and contained in subentries referenced by the entry. Excludes collective
108         * attributes that are specified to be excluded via the 'collectiveExclusions'
109         * attribute in the entry.
110         *
111         * @param opContext the context of the operation collective attributes 
112         * are added to
113         * @param entry the entry to have the collective attributes injected
114         * @param retAttrs array or attribute type to be specifically included in the result entry(s)
115         * @throws NamingException if there are problems accessing subentries
116         */
117        private void addCollectiveAttributes( OperationContext opContext, ClonedServerEntry entry, 
118            String[] retAttrs ) throws Exception
119        {
120            EntryAttribute collectiveAttributeSubentries = 
121                entry.getOriginalEntry().get( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT );
122            
123            /*
124             * If there are no collective attribute subentries referenced then we 
125             * have no collective attributes to inject to this entry.
126             */
127            if ( collectiveAttributeSubentries == null )
128            {
129                return;
130            }
131        
132            /*
133             * Before we proceed we need to lookup the exclusions within the entry 
134             * and build a set of exclusions for rapid lookup.  We use OID values 
135             * in the exclusions set instead of regular names that may have case 
136             * variance.
137             */
138            EntryAttribute collectiveExclusions = 
139                entry.getOriginalEntry().get( SchemaConstants.COLLECTIVE_EXCLUSIONS_AT );
140            Set<String> exclusions = new HashSet<String>();
141            
142            if ( collectiveExclusions != null )
143            {
144                if ( collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT_OID )
145                     || 
146                     collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT  ) )
147                {
148                    /*
149                     * This entry does not allow any collective attributes
150                     * to be injected into itself.
151                     */
152                    return;
153                }
154    
155                exclusions = new HashSet<String>();
156                
157                for ( Value<?> value:collectiveExclusions )
158                {
159                    AttributeType attrType = schemaManager.lookupAttributeTypeRegistry( value.getString() );
160                    exclusions.add( attrType.getOid() );
161                }
162            }
163            
164            /*
165             * If no attributes are requested specifically
166             * then it means all user attributes are requested.
167             * So populate the array with all user attributes indicator: "*".
168             */
169            if ( retAttrs == null )
170            {
171                retAttrs = SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY;
172            }
173            
174            /*
175             * Construct a set of requested attributes for easier tracking.
176             */ 
177            Set<String> retIdsSet = new HashSet<String>( retAttrs.length );
178            
179            for ( String retAttr:retAttrs )
180            {
181                if ( retAttr.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) ||
182                    retAttr.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) )
183                {
184                    retIdsSet.add( retAttr );
185                }
186                else
187                {
188                    retIdsSet.add( schemaManager.lookupAttributeTypeRegistry( retAttr ).getOid() );
189                }
190            }
191    
192            /*
193             * For each collective subentry referenced by the entry we lookup the
194             * attributes of the subentry and copy collective attributes from the
195             * subentry into the entry.
196             */
197            for ( Value<?> value:collectiveAttributeSubentries )
198            {
199                String subentryDnStr = value.getString();
200                DN subentryDn = new DN( subentryDnStr );
201                
202                /*
203                 * TODO - Instead of hitting disk here can't we leverage the 
204                 * SubentryService to get us cached sub-entries so we're not
205                 * wasting time with a lookup here? It is ridiculous to waste
206                 * time looking up this sub-entry. 
207                 */
208                
209                ServerEntry subentry = opContext.lookup( subentryDn, ByPassConstants.LOOKUP_COLLECTIVE_BYPASS );
210                
211                for ( AttributeType attributeType:subentry.getAttributeTypes() )
212                {
213                    String attrId = attributeType.getName();
214                    
215                    if ( !attributeType.isCollective() )
216                    {
217                        continue;
218                    }
219                    
220                    /*
221                     * Skip the addition of this collective attribute if it is excluded
222                     * in the 'collectiveAttributes' attribute.
223                     */
224                    if ( exclusions.contains( attributeType.getOid() ) )
225                    {
226                        continue;
227                    }
228                    
229                    Set<AttributeType> allSuperTypes = getAllSuperTypes( attributeType );
230    
231                    for ( String retId : retIdsSet )
232                    {
233                        if ( retId.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) || retId.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) )
234                        {
235                            continue;
236                        }
237    
238                        AttributeType retType = schemaManager.lookupAttributeTypeRegistry( retId );
239    
240                        if ( allSuperTypes.contains( retType ) )
241                        {
242                            retIdsSet.add( schemaManager.lookupAttributeTypeRegistry( attrId ).getOid() );
243                            break;
244                        }
245                    }
246    
247                    /*
248                     * If not all attributes or this collective attribute requested specifically
249                     * then bypass the inclusion process.
250                     */
251                    if ( !( retIdsSet.contains( SchemaConstants.ALL_USER_ATTRIBUTES ) || 
252                        retIdsSet.contains( schemaManager.lookupAttributeTypeRegistry( attrId ).getOid() ) ) )
253                    {
254                        continue;
255                    }
256                    
257                    EntryAttribute subentryColAttr = subentry.get( attrId );
258                    EntryAttribute entryColAttr = entry.get( attrId );
259    
260                    /*
261                     * If entry does not have attribute for collective attribute then create it.
262                     */
263                    if ( entryColAttr == null )
264                    {
265                        entryColAttr = new DefaultServerAttribute( attrId, schemaManager.lookupAttributeTypeRegistry( attrId ) );
266                        entry.put( entryColAttr );
267                    }
268    
269                    /*
270                     *  Add all the collective attribute values in the subentry
271                     *  to the currently processed collective attribute in the entry.
272                     */
273                    for ( Value<?> subentryColVal:subentryColAttr )
274                    {
275                        entryColAttr.add( subentryColVal.getString() );
276                    }
277                }
278            }
279        }
280        
281        
282        private Set<AttributeType> getAllSuperTypes( AttributeType id ) throws Exception
283        {
284            Set<AttributeType> allSuperTypes = new HashSet<AttributeType>();
285            AttributeType superType = id;
286            
287            while ( superType != null )
288            {
289                superType = superType.getSuperior();
290                
291                if ( superType != null )
292                {
293                    allSuperTypes.add( superType );
294                }
295            }
296            
297            return allSuperTypes;
298        }
299    
300    
301        // ------------------------------------------------------------------------
302        // Interceptor Method Overrides
303        // ------------------------------------------------------------------------
304    
305        
306        public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) 
307            throws Exception
308        {
309            ClonedServerEntry result = nextInterceptor.lookup( opContext );
310            
311            if ( result == null )
312            {
313                return null;
314            }
315            
316            if ( ( opContext.getAttrsId() == null ) || ( opContext.getAttrsId().size() == 0 ) ) 
317            {
318                addCollectiveAttributes( opContext, result, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
319            }
320            else
321            {
322                addCollectiveAttributes( opContext, result, opContext.getAttrsIdArray() );
323            }
324    
325            return result;
326        }
327    
328    
329        public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
330        {
331            EntryFilteringCursor cursor = nextInterceptor.list( opContext );
332            cursor.addEntryFilter( SEARCH_FILTER );
333            return cursor;
334        }
335    
336    
337        public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
338        {
339            EntryFilteringCursor cursor = nextInterceptor.search( opContext );
340            cursor.addEntryFilter( SEARCH_FILTER );
341            return cursor;
342        }
343    
344        
345        // ------------------------------------------------------------------------
346        // Partial Schema Checking
347        // ------------------------------------------------------------------------
348        
349        
350        public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
351        {
352            collectiveAttributesSchemaChecker.checkAdd( opContext.getDn(), opContext.getEntry() );
353            
354            next.add( opContext );
355        }
356    
357    
358        public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
359        {
360            collectiveAttributesSchemaChecker.checkModify( opContext,opContext.getDn(), opContext.getModItems() );
361    
362            next.modify( opContext );
363        }
364    }