blob: 905022b2c40e73c9b0a91af4cb9541b0ea4ff73c [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.shared.ldap.model.entry;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import org.apache.commons.lang.ArrayUtils;
import org.apache.directory.shared.i18n.I18n;
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.name.Dn;
import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl;
import org.apache.directory.shared.ldap.model.schema.MatchingRule;
import org.apache.directory.shared.ldap.model.schema.Normalizer;
import org.apache.directory.shared.ldap.model.schema.normalizers.NoOpNormalizer;
import org.apache.directory.shared.util.Chars;
import org.apache.directory.shared.util.Hex;
import org.apache.directory.shared.util.Position;
import org.apache.directory.shared.util.Strings;
/**
* A set of utility fuctions for working with Attributes.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public final class AttributeUtils
{
/**
* Private constructor.
*/
private AttributeUtils()
{
}
/**
* Correctly removes an attribute from an entry using it's attributeType information.
*
* @param type the attributeType of the attribute to remove
* @param entry the entry to remove the attribute from
* @return the Attribute that is removed
*/
public static Attribute removeAttribute( MutableAttributeTypeImpl type, Attributes entry )
{
Attribute attr = entry.get( type.getOid() );
if ( attr == null )
{
List<String> aliases = type.getNames();
for ( String alias : aliases )
{
attr = entry.get( alias );
if ( attr != null )
{
return entry.remove( attr.getID() );
}
}
}
if ( attr == null )
{
return null;
}
return entry.remove( attr.getID() );
}
/**
* Compare two values and return true if they are equal.
*
* @param value1 The first value
* @param value2 The second value
* @return true if both value are null or if they are equal.
*/
public static boolean equals( Object value1, Object value2 )
{
if ( value1 == value2 )
{
return true;
}
if ( value1 == null )
{
return false;
}
if ( value1 instanceof byte[] )
{
if ( value2 instanceof byte[] )
{
return Arrays.equals( ( byte[] ) value1, ( byte[] ) value2 );
}
else
{
return false;
}
}
else
{
return value1.equals( value2 );
}
}
/**
* Clone the value. An attribute value is supposed to be either a String
* or a byte array. If it's a String, then we just return it ( as String
* is immutable, we don't need to copy it). If it's a bu=yte array, we
* create a new byte array and copy the bytes into it.
*
* @param value The value to clone
* @return The cloned value
*/
public static Object cloneValue( Object value )
{
// First copy the value
Object newValue = null;
if ( value instanceof byte[] )
{
newValue = ( ( byte[] ) value ).clone();
}
else
{
newValue = value;
}
return newValue;
}
/**
* Switch from a BasicAttribute to a AttributeImpl. This is
* necessary to allow cloning to be correctly handled.
*
* @param attribute The attribute to transform
* @return A instance of AttributeImpl
*/
public static Attribute toBasicAttribute( Attribute attribute )
{
if ( attribute instanceof BasicAttribute )
{
// Just return the attribute
return attribute;
}
else
{
// Create a new AttributeImpl from the original attribute
Attribute newAttribute = new BasicAttribute( attribute.getID() );
try
{
NamingEnumeration<?> values = attribute.getAll();
while ( values.hasMoreElements() )
{
newAttribute.add( cloneValue( values.next() ) );
}
return newAttribute;
}
catch ( NamingException ne )
{
return newAttribute;
}
}
}
/**
* Utility method to extract an attribute from Attributes object using
* all combinationos of the name including aliases.
*
* @param attrs the Attributes to get the Attribute object from
* @param type the attribute type specification
* @return an Attribute with matching the attributeType spec or null
*/
public static Attribute getAttribute( Attributes attrs, MutableAttributeTypeImpl type )
{
// check if the attribute's OID is used
Attribute attr = attrs.get( type.getOid() );
if ( attr != null )
{
return attr;
}
// optimization bypass to avoid cost of the loop below
if ( type.getNames().size() == 1 )
{
attr = attrs.get( type.getNames().get( 0 ) );
if ( attr != null )
{
return attr;
}
}
// iterate through aliases
for ( String alias : type.getNames() )
{
attr = attrs.get( alias );
if ( attr != null )
{
return attr;
}
}
return null;
}
/**
* Check if an attribute contains a specific value, using the associated matchingRule for that
*
* @param attr The attribute we are searching in
* @param compared The object we are looking for
* @param type The attribute type
* @return <code>true</code> if the value exists in the attribute
* @throws LdapException If something went wrong while accessing the data
*/
public static boolean containsValue( Attribute attr, Value<?> compared, MutableAttributeTypeImpl type ) throws LdapException
{
// quick bypass test
if ( attr.contains( compared ) )
{
return true;
}
MatchingRule matchingRule = type.getEquality();
Normalizer normalizer = null;
if ( matchingRule != null )
{
normalizer = matchingRule.getNormalizer();
}
else
{
normalizer = new NoOpNormalizer( type.getOid() );
}
if ( type.getSyntax().isHumanReadable() )
{
try
{
String comparedStr = normalizer.normalize( compared.getString() );
for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
{
String value = ( String ) values.nextElement();
if ( comparedStr.equals( normalizer.normalize( value ) ) )
{
return true;
}
}
}
catch ( NamingException e )
{
throw new LdapException( e.getMessage() );
}
}
else
{
byte[] comparedBytes = null;
if ( !compared.isBinary() )
{
if ( compared.getString().length() < 3 )
{
return false;
}
// Transform the String to a byte array
int state = 1;
comparedBytes = new byte[compared.getString().length() / 3];
int pos = 0;
for ( char c : compared.getString().toCharArray() )
{
switch ( state )
{
case 1:
if ( c != '\\' )
{
return false;
}
state++;
break;
case 2:
int high = Hex.getHexValue(c);
if ( high == -1 )
{
return false;
}
comparedBytes[pos] = ( byte ) ( high << 4 );
state++;
break;
case 3:
int low = Hex.getHexValue(c);
if ( low == -1 )
{
return false;
}
comparedBytes[pos] += ( byte ) low;
pos++;
state = 1;
break;
default:
break;
}
}
}
else
{
comparedBytes = compared.getBytes();
}
try
{
for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
{
Object value = values.nextElement();
if ( value instanceof byte[] && ArrayUtils.isEquals( comparedBytes, value ) )
{
return true;
}
}
}
catch ( NamingException ne )
{
throw new LdapException( ne.getMessage() );
}
}
return false;
}
/**
* Check if an attribute contains a value. The test is case insensitive,
* and the value is supposed to be a String. If the value is a byte[],
* then the case sensitivity is useless.
*
* @param attr The attribute to check
* @param value The value to look for
* @return true if the value is present in the attribute
*/
public static boolean containsValueCaseIgnore( Attribute attr, Object value )
{
// quick bypass test
if ( attr.contains( value ) )
{
return true;
}
try
{
if ( value instanceof String )
{
String strVal = ( String ) value;
NamingEnumeration<?> attrVals = attr.getAll();
while ( attrVals.hasMoreElements() )
{
Object attrVal = attrVals.nextElement();
if ( attrVal instanceof String && strVal.equalsIgnoreCase( ( String ) attrVal ) )
{
return true;
}
}
}
else
{
byte[] valueBytes = ( byte[] ) value;
NamingEnumeration<?> attrVals = attr.getAll();
while ( attrVals.hasMoreElements() )
{
Object attrVal = attrVals.nextElement();
if ( attrVal instanceof byte[] && Arrays.equals( ( byte[] ) attrVal, valueBytes ) )
{
return true;
}
}
}
}
catch ( NamingException ne )
{
return false;
}
return false;
}
/**
* Creates a new attribute which contains the values representing the
* difference of two attributes. If both attributes are null then we cannot
* determine the attribute ID and an {@link IllegalArgumentException} is
* raised. Note that the order of arguments makes a difference.
*
* @param attr0
* the first attribute
* @param attr1
* the second attribute
* @return a new attribute with the difference of values from both attribute
* arguments
* @throws NamingException
* if there are problems accessing attribute values
*/
public static Attribute getDifference( Attribute attr0, Attribute attr1 ) throws NamingException
{
String id;
if ( ( attr0 == null ) && ( attr1 == null ) )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_04339 ) );
}
else if ( attr0 == null )
{
return new BasicAttribute( attr1.getID() );
}
else if ( attr1 == null )
{
return ( Attribute ) attr0.clone();
}
else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_04340 ) );
}
else
{
id = attr0.getID();
}
Attribute attr = new BasicAttribute( id );
for ( int ii = 0; ii < attr0.size(); ii++ )
{
attr.add( attr0.get( ii ) );
}
for ( int ii = 0; ii < attr1.size(); ii++ )
{
attr.remove( attr1.get( ii ) );
}
return attr;
}
/**
* Creates a new attribute which contains the values representing the union
* of two attributes. If one attribute is null then the resultant attribute
* returned is a copy of the non-null attribute. If both are null then we
* cannot determine the attribute ID and an {@link IllegalArgumentException}
* is raised.
*
* @param attr0
* the first attribute
* @param attr1
* the second attribute
* @return a new attribute with the union of values from both attribute
* arguments
* @throws NamingException
* if there are problems accessing attribute values
*/
public static Attribute getUnion( Attribute attr0, Attribute attr1 ) throws NamingException
{
String id;
if ( attr0 == null && attr1 == null )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_04341 ) );
}
else if ( attr0 == null )
{
id = attr1.getID();
}
else if ( attr1 == null )
{
id = attr0.getID();
}
else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_04342 ) );
}
else
{
id = attr0.getID();
}
Attribute attr = new BasicAttribute( id );
if ( attr0 != null )
{
for ( int ii = 0; ii < attr0.size(); ii++ )
{
attr.add( attr0.get( ii ) );
}
}
if ( attr1 != null )
{
for ( int ii = 0; ii < attr1.size(); ii++ )
{
attr.add( attr1.get( ii ) );
}
}
return attr;
}
/**
* Check if the attributes is a BasicAttributes, and if so, switch
* the case sensitivity to false to avoid tricky problems in the server.
* (Ldap attributeTypes are *always* case insensitive)
*
* @param attributes The Attributes to check
*/
public static Attributes toCaseInsensitive( Attributes attributes )
{
if ( attributes == null )
{
return attributes;
}
if ( attributes instanceof BasicAttributes )
{
if ( attributes.isCaseIgnored() )
{
// Just do nothing if the Attributes is already case insensitive
return attributes;
}
else
{
// Ok, bad news : we have to create a new BasicAttributes
// which will be case insensitive
Attributes newAttrs = new BasicAttributes( true );
NamingEnumeration<?> attrs = attributes.getAll();
if ( attrs != null )
{
// Iterate through the attributes now
while ( attrs.hasMoreElements() )
{
newAttrs.put( ( Attribute ) attrs.nextElement() );
}
}
return newAttrs;
}
}
else
{
// we can safely return the attributes if it's not a BasicAttributes
return attributes;
}
}
/**
* Return a string representing the attributes with tabs in front of the
* string
*
* @param tabs
* Spaces to be added before the string
* @param attribute
* The attribute to print
* @return A string
*/
public static String toString( String tabs, Attribute attribute )
{
StringBuffer sb = new StringBuffer();
sb.append( tabs ).append( "Attribute\n" );
if ( attribute != null )
{
sb.append( tabs ).append( " Type : '" ).append( attribute.getID() ).append( "'\n" );
for ( int j = 0; j < attribute.size(); j++ )
{
try
{
Object attr = attribute.get( j );
if ( attr != null )
{
if ( attr instanceof String )
{
sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr )
.append( " \n" );
}
else if ( attr instanceof byte[] )
{
String string = Strings.utf8ToString((byte[]) attr);
sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " );
sb.append( string ).append( '/' );
sb.append( Strings.dumpBytes((byte[]) attr) );
sb.append( " \n" );
}
else
{
sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr )
.append( " \n" );
}
}
}
catch ( NamingException ne )
{
sb.append( "Bad attribute : " ).append( ne.getMessage() );
}
}
}
return sb.toString();
}
/**
* Return a string representing the attribute
*
* @param attribute
* The attribute to print
* @return A string
*/
public static String toString( Attribute attribute )
{
return toString( "", attribute );
}
/**
* Return a string representing the attributes with tabs in front of the
* string
*
* @param tabs
* Spaces to be added before the string
* @param attributes
* The attributes to print
* @return A string
*/
public static String toString( String tabs, Attributes attributes )
{
StringBuffer sb = new StringBuffer();
sb.append( tabs ).append( "Attributes\n" );
if ( attributes != null )
{
NamingEnumeration<?> attributesIterator = attributes.getAll();
while ( attributesIterator.hasMoreElements() )
{
Attribute attribute = ( Attribute ) attributesIterator.nextElement();
sb.append( tabs ).append( attribute.toString() );
}
}
return sb.toString();
}
/**
* Parse attribute's options :
*
* options = *( ';' option )
* option = 1*keychar
* keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
*/
private static void parseOptions( String str, Position pos ) throws ParseException
{
while ( Strings.isCharASCII( str, pos.start, ';' ) )
{
pos.start++;
// We have an option
if ( !Chars.isAlphaDigitMinus(str, pos.start) )
{
// We must have at least one keychar
throw new ParseException( I18n.err( I18n.ERR_04343 ), pos.start );
}
pos.start++;
while ( Chars.isAlphaDigitMinus(str, pos.start) )
{
pos.start++;
}
}
}
/**
* Parse a number :
*
* number = '0' | '1'..'9' digits
* digits = '0'..'9'*
*
* @return true if a number has been found
*/
private static boolean parseNumber( String filter, Position pos )
{
char c = Strings.charAt(filter, pos.start);
switch ( c )
{
case '0':
// If we get a starting '0', we should get out
pos.start++;
return true;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
pos.start++;
break;
default:
// Not a number.
return false;
}
while ( Chars.isDigit(filter, pos.start) )
{
pos.start++;
}
return true;
}
/**
*
* Parse an OID.
*
* numericoid = number 1*( '.' number )
* number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
*
* @param str The OID to parse
* @param pos The current position in the string
* @throws ParseException If we don't have a valid OID
*/
public static void parseOID( String str, Position pos ) throws ParseException
{
// We have an OID
parseNumber( str, pos );
// We must have at least one '.' number
if ( !Strings.isCharASCII( str, pos.start, '.' ) )
{
throw new ParseException( I18n.err( I18n.ERR_04344 ), pos.start );
}
pos.start++;
if ( !parseNumber( str, pos ) )
{
throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start );
}
while ( true )
{
// Break if we get something which is not a '.'
if ( !Strings.isCharASCII( str, pos.start, '.' ) )
{
break;
}
pos.start++;
if ( !parseNumber( str, pos ) )
{
throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start );
}
}
}
/**
* Parse an attribute. The grammar is :
* attributedescription = attributetype options
* attributetype = oid
* oid = descr / numericoid
* descr = keystring
* numericoid = number 1*( '.' number )
* options = *( ';' option )
* option = 1*keychar
* keystring = leadkeychar *keychar
* leadkeychar = 'a'-z' | 'A'-'Z'
* keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
* number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
*
* @param str The parsed attribute,
* @param pos The position of the attribute in the current string
* @return The parsed attribute if valid
*/
public static String parseAttribute( String str, Position pos, boolean withOption ) throws ParseException
{
// We must have an OID or an DESCR first
char c = Strings.charAt(str, pos.start);
if ( c == '\0' )
{
throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start );
}
int start = pos.start;
if ( Chars.isAlpha(c) )
{
// A DESCR
pos.start++;
while ( Chars.isAlphaDigitMinus(str, pos.start) )
{
pos.start++;
}
// Parse the options if needed
if ( withOption )
{
parseOptions( str, pos );
}
return str.substring( start, pos.start );
}
else if ( Chars.isDigit(c) )
{
// An OID
pos.start++;
// Parse the OID
parseOID( str, pos );
// Parse the options
if ( withOption )
{
parseOptions( str, pos );
}
return str.substring( start, pos.start );
}
else
{
throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start );
}
}
/**
* Return a string representing the attributes
*
* @param attributes
* The attributes to print
* @return A string
*/
public static String toString( Attributes attributes )
{
return toString( "", attributes );
}
/**
* A method to apply a modification to an existing entry.
*
* @param entry The entry on which we want to apply a modification
* @param modification the Modification to be applied
* @throws org.apache.directory.shared.ldap.model.exception.LdapException if some operation fails.
*/
public static void applyModification( Entry entry, Modification modification ) throws LdapException
{
EntryAttribute modAttr = modification.getAttribute();
String modificationId = modAttr.getId();
switch ( modification.getOperation() )
{
case ADD_ATTRIBUTE:
EntryAttribute modifiedAttr = entry.get( modificationId );
if ( modifiedAttr == null )
{
// The attribute should be added.
entry.put( modAttr );
}
else
{
// The attribute exists : the values can be different,
// so we will just add the new values to the existing ones.
for ( Value<?> value : modAttr )
{
// If the value already exist, nothing is done.
// Note that the attribute *must* have been
// normalized before.
modifiedAttr.add( value );
}
}
break;
case REMOVE_ATTRIBUTE:
if ( modAttr.get() == null )
{
// We have no value in the ModificationItem attribute :
// we have to remove the whole attribute from the initial
// entry
entry.removeAttributes( modificationId );
}
else
{
// We just have to remove the values from the original
// entry, if they exist.
modifiedAttr = entry.get( modificationId );
if ( modifiedAttr == null )
{
break;
}
for ( Value<?> value : modAttr )
{
// If the value does not exist, nothing is done.
// Note that the attribute *must* have been
// normalized before.
modifiedAttr.remove( value );
}
if ( modifiedAttr.size() == 0 )
{
// If this was the last value, remove the attribute
entry.removeAttributes( modifiedAttr.getId() );
}
}
break;
case REPLACE_ATTRIBUTE:
if ( modAttr.get() == null )
{
// If the modification does not have any value, we have
// to delete the attribute from the entry.
entry.removeAttributes( modificationId );
}
else
{
// otherwise, just substitute the existing attribute.
entry.put( modAttr );
}
break;
default:
break;
}
}
/**
* Convert a BasicAttributes or a AttributesImpl to a ServerEntry
*
* @param attributes the BasicAttributes or AttributesImpl instance to convert
* @param dn The Dn which is needed by the ServerEntry
* @return An instance of a ServerEntry object
*
* @throws LdapException If we get an invalid attribute
*/
public static Entry toClientEntry( Attributes attributes, Dn dn ) throws LdapException
{
if ( attributes instanceof BasicAttributes )
{
try
{
Entry entry = new DefaultEntry( dn );
for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); )
{
Attribute attr = attrs.nextElement();
EntryAttribute entryAttribute = toClientAttribute( attr );
if ( entryAttribute != null )
{
entry.put( entryAttribute );
}
}
return entry;
}
catch ( LdapException ne )
{
throw new LdapInvalidAttributeTypeException( ne.getMessage() );
}
}
else
{
return null;
}
}
/**
* Converts an {@link Entry} to an {@link Attributes}.
*
* @param entry
* the {@link Entry} to convert
* @return
* the equivalent {@link Attributes}
*/
public static Attributes toAttributes( Entry entry )
{
if ( entry != null )
{
Attributes attributes = new BasicAttributes( true );
// Looping on attributes
for ( Iterator<EntryAttribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); )
{
EntryAttribute entryAttribute = ( EntryAttribute ) attributeIterator.next();
attributes.put( toAttribute( entryAttribute ) );
}
return attributes;
}
return null;
}
/**
* Converts an {@link EntryAttribute} to an {@link Attribute}.
*
* @param entryAttribute
* the {@link EntryAttribute} to convert
* @return
* the equivalent {@link Attribute}
*/
public static Attribute toAttribute( EntryAttribute entryAttribute )
{
if ( entryAttribute != null )
{
Attribute attribute = new BasicAttribute( entryAttribute.getId() );
// Looping on values
for ( Iterator<Value<?>> valueIterator = entryAttribute.iterator(); valueIterator.hasNext(); )
{
Value<?> value = valueIterator.next();
attribute.add( value.get() );
}
return attribute;
}
return null;
}
/**
* Convert a BasicAttribute or a AttributeImpl to a EntryAttribute
*
* @param attribute the BasicAttributes or AttributesImpl instance to convert
* @return An instance of a ClientEntry object
*/
public static EntryAttribute toClientAttribute( Attribute attribute )
{
if ( attribute == null )
{
return null;
}
try
{
EntryAttribute clientAttribute = new DefaultEntryAttribute( attribute.getID() );
for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); )
{
Object value = values.nextElement();
if ( value instanceof String )
{
clientAttribute.add( ( String ) value );
}
else if ( value instanceof byte[] )
{
clientAttribute.add( ( byte[] ) value );
}
else
{
clientAttribute.add( ( String ) null );
}
}
return clientAttribute;
}
catch ( NamingException ne )
{
return null;
}
}
}