blob: e8c4154aafa0a998f2ef4edb29c083e7c535531e [file] [log] [blame]
/*
* 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;
}
}