| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.collective; |
| |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.entry.ClonedServerEntry; |
| import org.apache.directory.server.core.filtering.EntryFilter; |
| import org.apache.directory.server.core.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.interceptor.NextInterceptor; |
| import org.apache.directory.server.core.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ListOperationContext; |
| import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.interceptor.context.OperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchingOperationContext; |
| import org.apache.directory.server.core.partition.ByPassConstants; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.shared.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.Entry; |
| import org.apache.directory.shared.ldap.model.entry.EntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.Modification; |
| import org.apache.directory.shared.ldap.model.entry.ModificationOperation; |
| import org.apache.directory.shared.ldap.model.entry.Value; |
| import org.apache.directory.shared.ldap.model.exception.LdapException; |
| import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeTypeException; |
| import org.apache.directory.shared.ldap.model.exception.LdapSchemaViolationException; |
| import org.apache.directory.shared.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.shared.ldap.model.name.Dn; |
| import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl; |
| import org.apache.directory.shared.ldap.model.schema.SchemaUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An interceptor based service dealing with collective attribute |
| * management. This service intercepts read operations on entries to |
| * inject collective attribute value pairs into the response based on |
| * the entires inclusion within collectiveAttributeSpecificAreas and |
| * collectiveAttributeInnerAreas. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class CollectiveAttributeInterceptor extends BaseInterceptor |
| { |
| /** The LoggerFactory used by this Interceptor */ |
| private static Logger LOG = LoggerFactory.getLogger( CollectiveAttributeInterceptor.class ); |
| |
| /** |
| * the search result filter to use for collective attribute injection |
| */ |
| private final EntryFilter SEARCH_FILTER = new EntryFilter() |
| { |
| public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception |
| { |
| String[] retAttrs = operation.getSearchControls().getReturningAttributes(); |
| addCollectiveAttributes( operation, result, retAttrs ); |
| |
| return true; |
| } |
| }; |
| //------------------------------------------------------------------------------------- |
| // Initialization |
| //------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| public void init( DirectoryService directoryService ) throws LdapException |
| { |
| super.init( directoryService ); |
| |
| LOG.debug( "CollectiveAttribute interceptor initilaized" ); |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // Interceptor Method Overrides |
| // ------------------------------------------------------------------------ |
| /** |
| * {@inheritDoc} |
| */ |
| public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException |
| { |
| checkAdd( addContext.getDn(), addContext.getEntry() ); |
| |
| next.add( addContext ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext listContext ) |
| throws LdapException |
| { |
| EntryFilteringCursor cursor = nextInterceptor.list( listContext ); |
| |
| cursor.addEntryFilter( SEARCH_FILTER ); |
| |
| return cursor; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Entry lookup( NextInterceptor nextInterceptor, LookupOperationContext lookupContext ) throws LdapException |
| { |
| Entry result = nextInterceptor.lookup( lookupContext ); |
| |
| // Adding the collective attributes if any |
| if ( ( lookupContext.getAttrsId() == null ) || ( lookupContext.getAttrsId().size() == 0 ) ) |
| { |
| addCollectiveAttributes( lookupContext, result, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); |
| } |
| else |
| { |
| addCollectiveAttributes( lookupContext, result, lookupContext.getAttrsIdArray() ); |
| } |
| |
| return result; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException |
| { |
| checkModify( modifyContext ); |
| |
| next.modify( modifyContext ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext searchContext ) |
| throws LdapException |
| { |
| EntryFilteringCursor cursor = nextInterceptor.search( searchContext ); |
| |
| cursor.addEntryFilter( SEARCH_FILTER ); |
| |
| return cursor; |
| } |
| |
| |
| //------------------------------------------------------------------------------------- |
| // Helper methods |
| //------------------------------------------------------------------------------------- |
| /** |
| * Check if we can add an entry. There are two cases : <br> |
| * <ul> |
| * <li>The entry is a normal entry : it should not contain any 'c-XXX' attributeType</li> |
| * <li>The entry is a collectiveAttributeSubentry |
| * </ul> |
| */ |
| private void checkAdd( Dn normName, Entry entry ) throws LdapException |
| { |
| if ( entry.hasObjectClass( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) ) |
| { |
| // This is a collectiveAttribute subentry. It must have at least one collective |
| // attribute |
| for ( EntryAttribute attribute : entry ) |
| { |
| if ( attribute.getAttributeType().isCollective() ) |
| { |
| return; |
| } |
| } |
| |
| LOG.info( "A CollectiveAttribute subentry *should* have at least one collectiveAttribute" ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_257_COLLECTIVE_SUBENTRY_WITHOUT_COLLECTIVE_AT ) ); |
| } |
| |
| if ( containsAnyCollectiveAttributes( entry ) ) |
| { |
| /* |
| * TODO: Replace the Exception and the ResultCodeEnum with the correct ones. |
| */ |
| LOG.info( "Cannot add the entry {} : it contains some CollectiveAttributes and is not a collective subentry", |
| entry ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_241_CANNOT_STORE_COLLECTIVE_ATT_IN_ENTRY ) ); |
| } |
| } |
| |
| |
| /** |
| * Check that we can modify an entry |
| */ |
| private void checkModify( ModifyOperationContext modifyContext ) throws LdapException |
| { |
| List<Modification> mods = modifyContext.getModItems(); |
| Entry originalEntry = modifyContext.getEntry(); |
| Entry targetEntry = ( Entry ) SchemaUtils.getTargetEntry( mods, originalEntry ); |
| |
| // If the modified entry contains the CollectiveAttributeSubentry, then the modification |
| // is accepted, no matter what |
| if ( targetEntry.contains( OBJECT_CLASS_AT, SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) ) |
| { |
| return; |
| } |
| |
| // Check that we don't add any collectve attribute, this is not allowed on normal entries |
| if ( hasCollectiveAttributes( mods ) ) |
| { |
| /* |
| * TODO: Replace the Exception and the ResultCodeEnum with the correct ones. |
| */ |
| LOG.info( "Cannot modify the entry {} : it contains some CollectiveAttributes and is not a collective subentry", |
| targetEntry ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_242 ) ); |
| } |
| } |
| |
| |
| /** |
| * Check that we have a CollectiveAttribute in the modifications. (CollectiveAttributes |
| * are those with a name starting with 'c-'). |
| */ |
| private boolean hasCollectiveAttributes( List<Modification> mods ) throws LdapException |
| { |
| for ( Modification mod : mods ) |
| { |
| // TODO: handle http://issues.apache.org/jira/browse/DIRSERVER-1198 |
| EntryAttribute attr = mod.getAttribute(); |
| MutableAttributeTypeImpl attrType = attr.getAttributeType(); |
| |
| // Defensive programming. Very unlikely to happen here... |
| if ( attrType == null ) |
| { |
| try |
| { |
| attrType = schemaManager.lookupAttributeTypeRegistry( attr.getUpId() ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapInvalidAttributeTypeException(); |
| } |
| } |
| |
| ModificationOperation modOp = mod.getOperation(); |
| |
| // If the AT is collective and we don't try to remove it, then we can return. |
| if ( attrType.isCollective() && ( modOp != ModificationOperation.REMOVE_ATTRIBUTE ) ) |
| { |
| return true; |
| } |
| } |
| |
| // No collective attrbute found |
| return false; |
| } |
| |
| |
| /** |
| * Check if the entry contains any collective AttributeType (those starting with 'c-') |
| */ |
| private boolean containsAnyCollectiveAttributes( Entry entry ) throws LdapException |
| { |
| Set<MutableAttributeTypeImpl> attributeTypes = entry.getAttributeTypes(); |
| |
| for ( MutableAttributeTypeImpl attributeType : attributeTypes ) |
| { |
| if ( attributeType.isCollective() ) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * Adds the set of collective attributes requested in the returning attribute list |
| * and contained in subentries referenced by the entry. Excludes collective |
| * attributes that are specified to be excluded via the 'collectiveExclusions' |
| * attribute in the entry. |
| * |
| * @param opContext the context of the operation collective attributes |
| * are added to |
| * @param entry the entry to have the collective attributes injected |
| * @param retAttrs array or attribute type to be specifically included in the result entry(s) |
| * @throws LdapException if there are problems accessing subentries |
| */ |
| private void addCollectiveAttributes( OperationContext opContext, Entry entry, String[] retAttrs ) throws LdapException |
| { |
| EntryAttribute collectiveAttributeSubentries = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get( |
| COLLECTIVE_ATTRIBUTE_SUBENTRIES_AT ); |
| |
| /* |
| * If there are no collective attribute subentries referenced then we |
| * have no collective attributes to inject to this entry. |
| */ |
| if ( collectiveAttributeSubentries == null ) |
| { |
| return; |
| } |
| |
| /* |
| * Before we proceed we need to lookup the exclusions within the entry |
| * and build a set of exclusions for rapid lookup. We use OID values |
| * in the exclusions set instead of regular names that may have case |
| * variance. |
| */ |
| EntryAttribute collectiveExclusions = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get( |
| COLLECTIVE_EXCLUSIONS_AT ); |
| Set<String> exclusions = new HashSet<String>(); |
| |
| if ( collectiveExclusions != null ) |
| { |
| if ( collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT_OID ) |
| || collectiveExclusions.contains( SchemaConstants.EXCLUDE_ALL_COLLECTIVE_ATTRIBUTES_AT ) ) |
| { |
| /* |
| * This entry does not allow any collective attributes |
| * to be injected into itself. |
| */ |
| return; |
| } |
| |
| exclusions = new HashSet<String>(); |
| |
| for ( Value<?> value : collectiveExclusions ) |
| { |
| MutableAttributeTypeImpl attrType = schemaManager.lookupAttributeTypeRegistry( value.getString() ); |
| exclusions.add( attrType.getOid() ); |
| } |
| } |
| |
| /* |
| * If no attributes are requested specifically |
| * then it means all user attributes are requested. |
| * So populate the array with all user attributes indicator: "*". |
| */ |
| if ( retAttrs == null ) |
| { |
| retAttrs = SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY; |
| } |
| |
| /* |
| * Construct a set of requested attributes for easier tracking. |
| */ |
| Set<String> retIdsSet = new HashSet<String>( retAttrs.length ); |
| |
| for ( String retAttr : retAttrs ) |
| { |
| if ( retAttr.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) |
| || retAttr.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) ) |
| { |
| retIdsSet.add( retAttr ); |
| } |
| else |
| { |
| retIdsSet.add( schemaManager.lookupAttributeTypeRegistry( retAttr ).getOid() ); |
| } |
| } |
| |
| /* |
| * For each collective subentry referenced by the entry we lookup the |
| * attributes of the subentry and copy collective attributes from the |
| * subentry into the entry. |
| */ |
| for ( Value<?> value : collectiveAttributeSubentries ) |
| { |
| String subentryDnStr = value.getString(); |
| Dn subentryDn = opContext.getSession().getDirectoryService().getDnFactory().create( subentryDnStr ); |
| |
| /* |
| * TODO - Instead of hitting disk here can't we leverage the |
| * SubentryService to get us cached sub-entries so we're not |
| * wasting time with a lookup here? It is ridiculous to waste |
| * time looking up this sub-entry. |
| */ |
| |
| Entry subentry = opContext.lookup( subentryDn, ByPassConstants.LOOKUP_COLLECTIVE_BYPASS ); |
| |
| for ( MutableAttributeTypeImpl attributeType : subentry.getAttributeTypes() ) |
| { |
| String attrId = attributeType.getName(); |
| |
| if ( !attributeType.isCollective() ) |
| { |
| continue; |
| } |
| |
| /* |
| * Skip the addition of this collective attribute if it is excluded |
| * in the 'collectiveAttributes' attribute. |
| */ |
| if ( exclusions.contains( attributeType.getOid() ) ) |
| { |
| continue; |
| } |
| |
| Set<MutableAttributeTypeImpl> allSuperTypes = getAllSuperTypes( attributeType ); |
| |
| for ( String retId : retIdsSet ) |
| { |
| if ( retId.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) |
| || retId.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) ) |
| { |
| continue; |
| } |
| |
| MutableAttributeTypeImpl retType = schemaManager.lookupAttributeTypeRegistry( retId ); |
| |
| if ( allSuperTypes.contains( retType ) ) |
| { |
| retIdsSet.add( schemaManager.lookupAttributeTypeRegistry( attrId ).getOid() ); |
| break; |
| } |
| } |
| |
| /* |
| * If not all attributes or this collective attribute requested specifically |
| * then bypass the inclusion process. |
| */ |
| if ( !( retIdsSet.contains( SchemaConstants.ALL_USER_ATTRIBUTES ) || retIdsSet.contains( schemaManager |
| .lookupAttributeTypeRegistry( attrId ).getOid() ) ) ) |
| { |
| continue; |
| } |
| |
| EntryAttribute subentryColAttr = subentry.get( attrId ); |
| EntryAttribute entryColAttr = entry.get( attrId ); |
| |
| /* |
| * If entry does not have attribute for collective attribute then create it. |
| */ |
| if ( entryColAttr == null ) |
| { |
| entryColAttr = new DefaultEntryAttribute( attrId, schemaManager |
| .lookupAttributeTypeRegistry( attrId ) ); |
| entry.put( entryColAttr ); |
| } |
| |
| /* |
| * Add all the collective attribute values in the subentry |
| * to the currently processed collective attribute in the entry. |
| */ |
| for ( Value<?> subentryColVal : subentryColAttr ) |
| { |
| entryColAttr.add( subentryColVal.getString() ); |
| } |
| } |
| } |
| } |
| |
| |
| private Set<MutableAttributeTypeImpl> getAllSuperTypes( MutableAttributeTypeImpl id ) throws LdapException |
| { |
| Set<MutableAttributeTypeImpl> allSuperTypes = new HashSet<MutableAttributeTypeImpl>(); |
| MutableAttributeTypeImpl superType = id; |
| |
| while ( superType != null ) |
| { |
| superType = superType.getSuperior(); |
| |
| if ( superType != null ) |
| { |
| allSuperTypes.add( superType ); |
| } |
| } |
| |
| return allSuperTypes; |
| } |
| } |