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.collective;
21  
22  
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
28  import org.apache.directory.api.ldap.model.entry.Attribute;
29  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
30  import org.apache.directory.api.ldap.model.entry.Entry;
31  import org.apache.directory.api.ldap.model.entry.Modification;
32  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
33  import org.apache.directory.api.ldap.model.entry.Value;
34  import org.apache.directory.api.ldap.model.exception.LdapException;
35  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException;
36  import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
37  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
38  import org.apache.directory.api.ldap.model.name.Dn;
39  import org.apache.directory.api.ldap.model.schema.AttributeType;
40  import org.apache.directory.api.ldap.model.schema.SchemaUtils;
41  import org.apache.directory.server.core.api.CoreSession;
42  import org.apache.directory.server.core.api.DirectoryService;
43  import org.apache.directory.server.core.api.InterceptorEnum;
44  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
45  import org.apache.directory.server.core.api.filtering.EntryFilter;
46  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
47  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
48  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
51  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
52  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
53  import org.apache.directory.server.i18n.I18n;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  
58  /**
59   * An interceptor based service dealing with collective attribute
60   * management.  This service intercepts read operations on entries to
61   * inject collective attribute value pairs into the response based on
62   * the entires inclusion within collectiveAttributeSpecificAreas and
63   * collectiveAttributeInnerAreas.
64   *
65   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
66   */
67  public class CollectiveAttributeInterceptor extends BaseInterceptor
68  {
69      /** The LoggerFactory used by this Interceptor */
70      private static final Logger LOG = LoggerFactory.getLogger( CollectiveAttributeInterceptor.class );
71  
72  
73      /**
74       * Creates a new instance of a CollectiveAttributeInterceptor.
75       */
76      public CollectiveAttributeInterceptor()
77      {
78          super( InterceptorEnum.COLLECTIVE_ATTRIBUTE_INTERCEPTOR );
79      }
80  
81      /**
82       * the search result filter to use for collective attribute injection
83       */
84      private class CollectiveAttributeFilter implements EntryFilter
85      {
86          /**
87           * {@inheritDoc}
88           */
89          @Override
90          public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException
91          {
92              addCollectiveAttributes( operation, entry );
93  
94              return true;
95          }
96  
97  
98          /**
99           * {@inheritDoc}
100          */
101         @Override
102         public String toString( String tabs )
103         {
104             return tabs + "CollectiveAttributeFilter";
105         }
106     }
107 
108     /** The CollectiveAttribute search filter */
109     private final EntryFilter searchFilter = new CollectiveAttributeFilter();
110 
111 
112     //-------------------------------------------------------------------------------------
113     // Initialization
114     //-------------------------------------------------------------------------------------
115     /**
116      * {@inheritDoc}
117      */
118     @Override
119     public void init( DirectoryService directoryService ) throws LdapException
120     {
121         super.init( directoryService );
122 
123         LOG.debug( "CollectiveAttribute interceptor initialized" );
124     }
125 
126 
127     // ------------------------------------------------------------------------
128     // Interceptor Method Overrides
129     // ------------------------------------------------------------------------
130     /**
131      * {@inheritDoc}
132      */
133     @Override
134     public void add( AddOperationContext addContext ) throws LdapException
135     {
136         checkAdd( addContext.getDn(), addContext.getEntry() );
137 
138         next( addContext );
139     }
140 
141 
142     /**
143      * {@inheritDoc}
144      */
145     @Override
146     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
147     {
148         Entry result = next( lookupContext );
149 
150         // do not add collective attributes
151         if ( lookupContext.isSyncreplLookup() )
152         {
153             return result;
154         }
155 
156         // Adding the collective attributes if any
157         addCollectiveAttributes( lookupContext, result );
158 
159         return result;
160     }
161 
162 
163     /**
164      * {@inheritDoc}
165      */
166     @Override
167     public void modify( ModifyOperationContext modifyContext ) throws LdapException
168     {
169         checkModify( modifyContext );
170 
171         next( modifyContext );
172     }
173 
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
179     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
180     {
181         EntryFilteringCursor cursor = next( searchContext );
182 
183         // only add collective attributes for non-syncrepl search
184         if ( !searchContext.isSyncreplSearch() )
185         {
186             cursor.addEntryFilter( searchFilter );
187         }
188 
189         return cursor;
190     }
191 
192 
193     //-------------------------------------------------------------------------------------
194     // Helper methods
195     //-------------------------------------------------------------------------------------
196     /**
197      * Check if we can add an entry. There are two cases : <br>
198      * <ul>
199      * <li>The entry is a normal entry : it should not contain any 'c-XXX' attributeType</li>
200      * <li>The entry is a collectiveAttributeSubentry
201      * </ul>
202      */
203     private void checkAdd( Dn normName, Entry entry ) throws LdapException
204     {
205         if ( entry.hasObjectClass( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) )
206         {
207             // This is a collectiveAttribute subentry. It must have at least one collective
208             // attribute
209             for ( Attribute attribute : entry )
210             {
211                 if ( attribute.getAttributeType().isCollective() )
212                 {
213                     return;
214                 }
215             }
216 
217             LOG.info( "A CollectiveAttribute subentry *should* have at least one collectiveAttribute" );
218             throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION,
219                 I18n.err( I18n.ERR_257_COLLECTIVE_SUBENTRY_WITHOUT_COLLECTIVE_AT ) );
220         }
221 
222         if ( containsAnyCollectiveAttributes( entry ) )
223         {
224             /*
225              * TODO: Replace the Exception and the ResultCodeEnum with the correct ones.
226              */
227             LOG.info(
228                 "Cannot add the entry {} : it contains some CollectiveAttributes and is not a collective subentry",
229                 entry );
230             throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION,
231                 I18n.err( I18n.ERR_241_CANNOT_STORE_COLLECTIVE_ATT_IN_ENTRY ) );
232         }
233     }
234 
235 
236     /**
237      * Check that we can modify an entry
238      */
239     private void checkModify( ModifyOperationContext modifyContext ) throws LdapException
240     {
241         List<Modification> mods = modifyContext.getModItems();
242         Entry originalEntry = modifyContext.getEntry();
243         Entry targetEntry = SchemaUtils.getTargetEntry( mods, originalEntry );
244 
245         // If the modified entry contains the CollectiveAttributeSubentry, then the modification
246         // is accepted, no matter what
247         if ( targetEntry.contains( directoryService.getAtProvider().getObjectClass(),
248             SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) )
249         {
250             return;
251         }
252 
253         // Check that we don't add any collectve attribute, this is not allowed on normal entries
254         if ( hasCollectiveAttributes( mods ) )
255         {
256             /*
257              * TODO: Replace the Exception and the ResultCodeEnum with the correct ones.
258              */
259             LOG.info(
260                 "Cannot modify the entry {} : it contains some CollectiveAttributes and is not a collective subentry",
261                 targetEntry );
262             throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_242 ) );
263         }
264     }
265 
266 
267     /**
268      * Check that we have a CollectiveAttribute in the modifications. (CollectiveAttributes
269      * are those with a name starting with 'c-').
270      */
271     private boolean hasCollectiveAttributes( List<Modification> mods ) throws LdapException
272     {
273         for ( Modification mod : mods )
274         {
275             // TODO: handle http://issues.apache.org/jira/browse/DIRSERVER-1198
276             Attribute attr = mod.getAttribute();
277             AttributeType attrType = attr.getAttributeType();
278 
279             // Defensive programming. Very unlikely to happen here...
280             if ( attrType == null )
281             {
282                 try
283                 {
284                     attrType = schemaManager.lookupAttributeTypeRegistry( attr.getUpId() );
285                 }
286                 catch ( LdapException le )
287                 {
288                     throw new LdapInvalidAttributeTypeException();
289                 }
290             }
291 
292             ModificationOperation modOp = mod.getOperation();
293 
294             // If the AT is collective and we don't try to remove it, then we can return.
295             if ( attrType.isCollective() && ( modOp != ModificationOperation.REMOVE_ATTRIBUTE ) )
296             {
297                 return true;
298             }
299         }
300 
301         // No collective attrbute found
302         return false;
303     }
304 
305 
306     /**
307      * Check if the entry contains any collective AttributeType (those starting with 'c-')
308      */
309     private boolean containsAnyCollectiveAttributes( Entry entry )
310     {
311         for ( Attribute attribute : entry.getAttributes() )
312         {
313             AttributeType attributeType = attribute.getAttributeType();
314 
315             if ( attributeType.isCollective() )
316             {
317                 return true;
318             }
319         }
320 
321         return false;
322     }
323 
324 
325     /**
326      * Adds the set of collective attributes requested in the returning attribute list
327      * and contained in subentries referenced by the entry. Excludes collective
328      * attributes that are specified to be excluded via the 'collectiveExclusions'
329      * attribute in the entry.
330      *
331      * @param opContext the context of the operation collective attributes
332      * are added to
333      * @param entry the entry to have the collective attributes injected
334      * @throws LdapException if there are problems accessing subentries
335      */
336     private void addCollectiveAttributes( FilteringOperationContext opContext, Entry entry )
337         throws LdapException
338     {
339         CoreSession session = opContext.getSession();
340 
341         Attribute collectiveAttributeSubentries = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get(
342             directoryService.getAtProvider().getCollectiveAttributeSubentries() );
343 
344         /*
345          * If there are no collective attribute subentries referenced then we
346          * have no collective attributes to inject to this entry.
347          */
348         if ( collectiveAttributeSubentries == null )
349         {
350             return;
351         }
352 
353         if ( LOG.isDebugEnabled() )
354         {
355             LOG.debug( "Filtering entry {}", entry.getDn() );
356         }
357 
358         /*
359          * Before we proceed we need to lookup the exclusions within the entry
360          * and build a set of exclusions for rapid lookup.  We use OID values
361          * in the exclusions set instead of regular names that may have case
362          * variance.
363          */
364         Attribute collectiveExclusions = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get(
365             directoryService.getAtProvider().getCollectiveExclusions() );
366         Set<AttributeType> exclusions = new HashSet<>();
367 
368         if ( collectiveExclusions != null )
369         {
370             LOG.debug( "The entry has some exclusions : {}", collectiveExclusions );
371 
372             if ( collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT_OID )
373                 || collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT ) )
374             {
375                 /*
376                  * This entry does not allow any collective attributes
377                  * to be injected into itself.
378                  */
379                 LOG.debug( "The entry excludes all the collectiveAttributes" );
380 
381                 return;
382             }
383 
384             for ( Value value : collectiveExclusions )
385             {
386                 AttributeType attrType = schemaManager.lookupAttributeTypeRegistry( value.getString() );
387                 exclusions.add( attrType );
388                 LOG.debug( "Adding {} in the list of excluded collectiveAttributes", attrType.getName() );
389             }
390         }
391 
392         /*
393          * For each collective subentry referenced by the entry we lookup the
394          * attributes of the subentry and copy collective attributes from the
395          * subentry into the entry.
396          */
397         for ( Value value : collectiveAttributeSubentries )
398         {
399             String subentryDnStr = value.getString();
400             Dn subentryDn = dnFactory.create( subentryDnStr );
401 
402             LOG.debug( "Applying subentries {}", subentryDn.getName() );
403 
404             /*
405              * TODO - Instead of hitting disk here can't we leverage the
406              * SubentryService to get us cached sub-entries so we're not
407              * wasting time with a lookup here? It is ridiculous to waste
408              * time looking up this sub-entry.
409              */
410 
411             LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, subentryDn,
412                 SchemaConstants.ALL_ATTRIBUTES_ARRAY );
413             lookupContext.setPartition( opContext.getPartition() );
414             lookupContext.setTransaction( opContext.getTransaction() );
415 
416             Entry subentry = directoryService.getPartitionNexus().lookup( lookupContext );
417 
418             //LOG.debug( "Fetched the subentry : {}", subentry.getDn().getName() );
419 
420             for ( Attribute attribute : subentry.getAttributes() )
421             {
422                 AttributeType attributeType = attribute.getAttributeType();
423 
424                 // Skip the attributes which are not collective
425                 if ( !attributeType.isCollective() )
426                 {
427                     //LOG.debug( "The {} subentry attribute is not collective", attributeType.getName() );
428                     continue;
429                 }
430 
431                 /*
432                  * Skip the addition of this collective attribute if it is excluded
433                  * in the 'collectiveAttributes' attribute.
434                  */
435                 if ( exclusions.contains( attributeType ) )
436                 {
437                     LOG.debug( "The {} subentry attribute has been removed, it's in the exclusion list",
438                         attributeType.getName() );
439                     continue;
440                 }
441 
442                 /*
443                  * If not all attributes or this collective attribute requested specifically
444                  * then bypass the inclusion process.
445                  */
446                 if ( !opContext.isAllUserAttributes() && !opContext.contains( schemaManager, attributeType ) )
447                 {
448                     LOG.debug( "The {} subentry attribute is not in the list of attributes to return",
449                         attributeType.getName() );
450                     continue;
451                 }
452 
453                 Attribute subentryColAttr = subentry.get( attributeType );
454                 Attribute entryColAttr = entry.get( attributeType );
455 
456                 /*
457                  * If entry does not have attribute for collective attribute then create it.
458                  */
459                 if ( entryColAttr == null )
460                 {
461                     entryColAttr = new DefaultAttribute( attributeType );
462                     entry.put( entryColAttr );
463                 }
464 
465                 /*
466                  *  Add all the collective attribute values in the subentry
467                  *  to the currently processed collective attribute in the entry.
468                  */
469                 for ( Value subentryColVal : subentryColAttr )
470                 {
471                     LOG.debug( "Adding the {} collective attribute into the entry", subentryColAttr );
472                     entryColAttr.add( subentryColVal.getString() );
473                 }
474             }
475         }
476     }
477 }