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.collective; 021 022 023import java.util.HashSet; 024import java.util.List; 025import java.util.Set; 026 027import org.apache.directory.api.ldap.model.constants.SchemaConstants; 028import org.apache.directory.api.ldap.model.entry.Attribute; 029import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 030import org.apache.directory.api.ldap.model.entry.Entry; 031import org.apache.directory.api.ldap.model.entry.Modification; 032import org.apache.directory.api.ldap.model.entry.ModificationOperation; 033import org.apache.directory.api.ldap.model.entry.Value; 034import org.apache.directory.api.ldap.model.exception.LdapException; 035import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException; 036import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; 037import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 038import org.apache.directory.api.ldap.model.name.Dn; 039import org.apache.directory.api.ldap.model.schema.AttributeType; 040import org.apache.directory.api.ldap.model.schema.SchemaUtils; 041import org.apache.directory.server.core.api.CoreSession; 042import org.apache.directory.server.core.api.DirectoryService; 043import org.apache.directory.server.core.api.InterceptorEnum; 044import org.apache.directory.server.core.api.entry.ClonedServerEntry; 045import org.apache.directory.server.core.api.filtering.EntryFilter; 046import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 047import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 048import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 049import org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext; 050import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 051import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 052import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 053import org.apache.directory.server.i18n.I18n; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057 058/** 059 * An interceptor based service dealing with collective attribute 060 * management. This service intercepts read operations on entries to 061 * inject collective attribute value pairs into the response based on 062 * the entires inclusion within collectiveAttributeSpecificAreas and 063 * collectiveAttributeInnerAreas. 064 * 065 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 066 */ 067public class CollectiveAttributeInterceptor extends BaseInterceptor 068{ 069 /** The LoggerFactory used by this Interceptor */ 070 private static final Logger LOG = LoggerFactory.getLogger( CollectiveAttributeInterceptor.class ); 071 072 073 /** 074 * Creates a new instance of a CollectiveAttributeInterceptor. 075 */ 076 public CollectiveAttributeInterceptor() 077 { 078 super( InterceptorEnum.COLLECTIVE_ATTRIBUTE_INTERCEPTOR ); 079 } 080 081 /** 082 * the search result filter to use for collective attribute injection 083 */ 084 private class CollectiveAttributeFilter implements EntryFilter 085 { 086 /** 087 * {@inheritDoc} 088 */ 089 @Override 090 public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException 091 { 092 addCollectiveAttributes( operation, entry ); 093 094 return true; 095 } 096 097 098 /** 099 * {@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 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}