blob: 63c10499e60cc11a891cca6225beba0e06baf1da [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.name;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import org.apache.directory.shared.i18n.I18n;
import org.apache.directory.shared.ldap.entry.BinaryValue;
import org.apache.directory.shared.ldap.entry.StringValue;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Attribute Type And Value, which is the basis of all RDN. It contains a
* type, and a value. The type must not be case sensitive. Superfluous leading
* and trailing spaces MUST have been trimmed before. The value MUST be in UTF8
* format, according to RFC 2253. If the type is in OID form, then the value
* must be a hexadecimal string prefixed by a '#' character. Otherwise, the
* string must respect the RC 2253 grammar. No further normalization will be
* done, because we don't have any knowledge of the Schema definition in the
* parser.
*
* We will also keep a User Provided form of the atav (Attribute Type And Value),
* called upName.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class AVA implements Cloneable, Comparable, Externalizable
{
/**
* Declares the Serial Version Uid.
*
* @see <a
* href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
* Declare Serial Version Uid</a>
*/
private static final long serialVersionUID = 1L;
/** The LoggerFactory used by this class */
private static Logger LOG = LoggerFactory.getLogger( AVA.class );
/** The normalized Name type */
private String normType;
/** The user provided Name type */
private String upType;
/** The name value. It can be a String or a byte array */
private Value<?> normValue;
/** The name user provided value. It can be a String or a byte array */
private Value<?> upValue;
/** The user provided AVA */
private String upName;
/** The starting position of this atav in the given string from which
* we have extracted the upName */
private int start;
/** The length of this atav upName */
private int length;
/** Two values used for comparizon */
private static final boolean CASE_SENSITIVE = true;
private static final boolean CASE_INSENSITIVE = false;
/**
* Construct an empty AVA
*/
public AVA()
{
normType = null;
upType = null;
normValue = null;
upValue = null;
upName = "";
start = -1;
length = 0;
}
/**
* Construct an AVA. The type and value are normalized :
* <li> the type is trimmed and lowercased </li>
* <li> the value is trimmed </li>
* <p>
* Note that the upValue should <b>not</b> be null or empty, or resolved
* to an empty string after having trimmed it.
*
* @param upType The Usrr Provided type
* @param normType The normalized type
* @param upValue The User Provided value
* @param normValue The normalized value
*/
public AVA( String upType, String normType, String upValue, String normValue ) throws LdapInvalidDnException
{
this( upType, normType, new StringValue( upValue ), new StringValue( normValue ) );
}
/**
* Construct an AVA. The type and value are normalized :
* <li> the type is trimmed and lowercased </li>
* <li> the value is trimmed </li>
* <p>
* Note that the upValue should <b>not</b> be null or empty, or resolved
* to an empty string after having trimmed it.
*
* @param upType The Usrr Provided type
* @param normType The normalized type
* @param upValue The User Provided value
* @param normValue The normalized value
*/
public AVA( String upType, String normType, byte[] upValue, byte[] normValue ) throws LdapInvalidDnException
{
this( upType, normType, new BinaryValue( upValue ), new BinaryValue( normValue ) );
}
/**
* Construct an AVA. The type and value are normalized :
* <li> the type is trimmed and lowercased </li>
* <li> the value is trimmed </li>
* <p>
* Note that the upValue should <b>not</b> be null or empty, or resolved
* to an empty string after having trimmed it.
*
* @param upType The Usrr Provided type
* @param normType The normalized type
* @param upValue The User Provided value
* @param normValue The normalized value
*/
public AVA( String upType, String normType, Value<?> upValue, Value<?> normValue ) throws LdapInvalidDnException
{
String upTypeTrimmed = StringTools.trim( upType );
String normTypeTrimmed = StringTools.trim( normType );
if ( StringTools.isEmpty( upTypeTrimmed ) )
{
if ( StringTools.isEmpty( normTypeTrimmed ) )
{
String message = I18n.err( I18n.ERR_04188 );
LOG.error( message );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
}
else
{
// In this case, we will use the normType instead
this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
this.upType = normType;
}
}
else if ( StringTools.isEmpty( normTypeTrimmed ) )
{
// In this case, we will use the upType instead
this.normType = StringTools.lowerCaseAscii( upTypeTrimmed );
this.upType = upType;
}
else
{
this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
this.upType = upType;
}
this.normValue = normValue;
this.upValue = upValue;
upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
start = 0;
length = upName.length();
}
/**
* Construct an AVA. The type and value are normalized :
* <li> the type is trimmed and lowercased </li>
* <li> the value is trimmed </li>
* <p>
* Note that the upValue should <b>not</b> be null or empty, or resolved
* to an empty string after having trimmed it.
*
* @param upType The User Provided type
* @param normType The normalized type
* @param upValue The User Provided value
* @param normValue The normalized value
* @param upName The User Provided name (may be escaped)
*/
public AVA( String upType, String normType, Value<?> upValue, Value<?> normValue, String upName )
throws LdapInvalidDnException
{
String upTypeTrimmed = StringTools.trim( upType );
String normTypeTrimmed = StringTools.trim( normType );
if ( StringTools.isEmpty( upTypeTrimmed ) )
{
if ( StringTools.isEmpty( normTypeTrimmed ) )
{
String message = I18n.err( I18n.ERR_04188 );
LOG.error( message );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
}
else
{
// In this case, we will use the normType instead
this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
this.upType = normType;
}
}
else if ( StringTools.isEmpty( normTypeTrimmed ) )
{
// In this case, we will use the upType instead
this.normType = StringTools.lowerCaseAscii( upTypeTrimmed );
this.upType = upType;
}
else
{
this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
this.upType = upType;
}
this.normValue = normValue;
this.upValue = upValue;
this.upName = upName;
start = 0;
length = upName.length();
}
/**
* Get the normalized type of a AVA
*
* @return The normalized type
*/
public String getNormType()
{
return normType;
}
/**
* Get the user provided type of a AVA
*
* @return The user provided type
*/
public String getUpType()
{
return upType;
}
/**
* Store a new type
*
* @param upType The AVA User Provided type
* @param type The AVA type
*
* @throws LdapInvalidDnException if the type or upType are empty or null.
* If the upName is invalid.
*/
public void setType( String upType, String type ) throws LdapInvalidDnException
{
if ( StringTools.isEmpty( type ) || StringTools.isEmpty( type.trim() ) )
{
String message = I18n.err( I18n.ERR_04188 );
LOG.error( message );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
}
if ( StringTools.isEmpty( upType ) || StringTools.isEmpty( upType.trim() ) )
{
String message = I18n.err( I18n.ERR_04189 );
LOG.error( message );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
}
int equalPosition = upName.indexOf( '=' );
if ( equalPosition <= 1 )
{
String message = I18n.err( I18n.ERR_04190 );
LOG.error( message );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
}
normType = type.trim().toLowerCase();
this.upType = upType;
upName = upType + upName.substring( equalPosition );
start = -1;
length = upName.length();
}
/**
* Store the type, after having trimmed and lowercased it.
*
* @param type The AVA type
*/
public void setTypeNormalized( String type ) throws LdapInvalidDnException
{
if ( StringTools.isEmpty( type ) || StringTools.isEmpty( type.trim() ) )
{
LOG.error( I18n.err( I18n.ERR_04191 ) );
throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04191 ) );
}
normType = type.trim().toLowerCase();
upType = type;
upName = type + upName.substring( upName.indexOf( '=' ) );
start = -1;
length = upName.length();
}
/**
* Get the Value of a AVA
*
* @return The value
*/
public Value<?> getNormValue()
{
return normValue;
}
/**
* Get the User Provided Value of a AVA
*
* @return The value
*/
public Value<?> getUpValue()
{
return upValue;
}
/**
* Get the normalized Name of a AVA
*
* @return The name
*/
public String getNormName()
{
return normalize();
}
/**
* Store the value of a AVA.
*
* @param upValue The user provided value of the AVA
* @param normValue The normalized value
*/
public void setValue( Value<?> upValue, Value<?> normValue )
{
this.normValue = normValue;
this.upValue = upValue;
upName = upName.substring( 0, upName.indexOf( '=' ) + 1 ) + upValue;
start = -1;
length = upName.length();
}
/**
* Get the upName length
*
* @return the upName length
*/
public int getLength()
{
return length;
}
/**
* get the position in the original upName where this atav starts.
*
* @return The starting position of this atav
*/
public int getStart()
{
return start;
}
/**
* Get the user provided form of this attribute type and value
*
* @return The user provided form of this atav
*/
public String getUpName()
{
return upName;
}
/**
* Store the value of a AVA, after having trimmed it.
*
* @param value The value of the AVA
*/
public void setValueNormalized( String value )
{
String newValue = StringTools.trim( value );
if ( StringTools.isEmpty( newValue ) )
{
this.normValue = new StringValue( "" );
}
else
{
this.normValue = new StringValue( newValue );
}
upName = upName.substring( 0, upName.indexOf( '=' ) + 1 ) + value;
start = -1;
length = upName.length();
}
/**
* Implements the cloning.
*
* @return a clone of this object
*/
public Object clone()
{
try
{
return super.clone();
}
catch ( CloneNotSupportedException cnse )
{
throw new Error( "Assertion failure" );
}
}
/**
* Compares two NameComponents. They are equals if :
* - types are equals, case insensitive,
* - values are equals, case sensitive
*
* @param object
* @return 0 if both NC are equals, otherwise a positive value if the
* original NC is superior to the second one, a negative value if
* the second NC is superior.
*/
public int compareTo( Object object )
{
if ( object instanceof AVA )
{
AVA nc = ( AVA ) object;
int res = compareType( normType, nc.normType );
if ( res != 0 )
{
return res;
}
else
{
return compareValue( normValue, nc.normValue, CASE_SENSITIVE );
}
}
else
{
return 1;
}
}
/**
* Compares two NameComponents. They are equals if :
* - types are equals, case insensitive,
* - values are equals, case insensitive
*
* @param object
* @return 0 if both NC are equals, otherwise a positive value if the
* original NC is superior to the second one, a negative value if
* the second NC is superior.
*/
public int compareToIgnoreCase( Object object )
{
if ( object instanceof AVA )
{
AVA nc = ( AVA ) object;
int res = compareType( normType, nc.normType );
if ( res != 0 )
{
return res;
}
else
{
return compareValue( normValue, nc.normValue, CASE_INSENSITIVE );
}
}
else
{
return 1;
}
}
/**
* Compare two types, trimed and case insensitive
*
* @param val1 First String
* @param val2 Second String
* @return true if both strings are equals or null.
*/
private int compareType( String val1, String val2 )
{
if ( StringTools.isEmpty( val1 ) )
{
return StringTools.isEmpty( val2 ) ? 0 : -1;
}
else if ( StringTools.isEmpty( val2 ) )
{
return 1;
}
else
{
return ( StringTools.trim( val1 ) ).compareToIgnoreCase( StringTools.trim( val2 ) );
}
}
/**
* Compare two values
*
* @param val1 First value
* @param val2 Second value
* @param sensitivity A flag to define the case sensitivity
* @return -1 if the first value is inferior to the second one, +1 if
* its superior, 0 if both values are equal
*/
private int compareValue( Value<?> val1, Value<?> val2, boolean sensitivity )
{
if ( !val1.isBinary() )
{
if ( !val2.isBinary() )
{
int val = ( sensitivity == CASE_SENSITIVE ) ?
( val1.getString() ).compareTo( val2.getString() )
: ( val1.getString() ).compareToIgnoreCase( val2.getString() );
return ( val < 0 ? -1 : ( val > 0 ? 1 : val ) );
}
else
{
return 1;
}
}
else
{
if ( val2.isBinary() )
{
if ( Arrays.equals( val1.getBytes(), val2.getBytes() ) )
{
return 0;
}
else
{
return 1;
}
}
else
{
return 1;
}
}
}
private static final boolean[] DN_ESCAPED_CHARS = new boolean[]
{
true, true, true, true, true, true, true, true, // 0x00 -> 0x07
true, true, true, true, true, true, true, true, // 0x08 -> 0x0F
true, true, true, true, true, true, true, true, // 0x10 -> 0x17
true, true, true, true, true, true, true, true, // 0x18 -> 0x1F
true, false, true, true, false, false, false, false, // 0x20 -> 0x27 ' ', '"', '#'
false, false, false, true, true, false, false, false, // 0x28 -> 0x2F '+', ','
false, false, false, false, false, false, false, false, // 0x30 -> 0x37
false, false, false, true, true, false, true, false, // 0x38 -> 0x3F ';', '<', '>'
false, false, false, false, false, false, false, false, // 0x40 -> 0x47
false, false, false, false, false, false, false, false, // 0x48 -> 0x4F
false, false, false, false, false, false, false, false, // 0x50 -> 0x57
false, false, false, false, true, false, false, false, // 0x58 -> 0x5F
false, false, false, false, false, false, false, false, // 0x60 -> 0x67
false, false, false, false, false, false, false, false, // 0x68 -> 0x6F
false, false, false, false, false, false, false, false, // 0x70 -> 0x77
false, false, false, false, false, false, false, false, // 0x78 -> 0x7F
};
public String normalizeValue()
{
// The result will be gathered in a stringBuilder
StringBuilder sb = new StringBuilder();
String normalizedValue = normValue.getString();
int valueLength = normalizedValue.length();
if ( normalizedValue.length() > 0 )
{
char[] chars = normalizedValue.toCharArray();
// Here, we have a char to escape. Start again the loop...
for ( int i = 0; i < valueLength; i++ )
{
char c = chars[i];
if ( ( c >= 0 ) && ( c < DN_ESCAPED_CHARS.length ) && DN_ESCAPED_CHARS[ c ] )
{
// Some chars need to be escaped even if they are US ASCII
// Just prefix them with a '\'
// Special cases are ' ' (space), '#') which need a special
// treatment.
switch ( c )
{
case ' ' :
if ( ( i == 0 ) || ( i == valueLength - 1 ) )
{
sb.append( "\\ " );
}
else
{
sb.append( ' ' );
}
break;
case '#' :
if ( i == 0 )
{
sb.append( "\\#" );
continue;
}
else
{
sb.append( '#' );
}
break;
default :
sb.append( '\\' ).append( c );
}
}
else
{
// Standard ASCII chars are just appended
sb.append( c );
}
}
}
return sb.toString();
}
/**
* A Normalized String representation of a AVA :
* - type is trimed and lowercased
* - value is trimed and lowercased, and special characters
* are escaped if needed.
*
* @return A normalized string representing a AVA
*/
public String normalize()
{
if ( !normValue.isBinary() )
{
// The result will be gathered in a stringBuilder
StringBuilder sb = new StringBuilder();
// First, store the type and the '=' char
sb.append( normType ).append( '=' );
String normalizedValue = normValue.getString();
if ( normalizedValue.length() > 0 )
{
sb.append( normalizeValue() );
}
return sb.toString();
}
else
{
return normType + "=#"
+ StringTools.dumpHexPairs( normValue .getBytes() );
}
}
/**
* Gets the hashcode of this object.
*
* @see java.lang.Object#hashCode()
* @return The instance hash code
*/
public int hashCode()
{
int result = 37;
result = result*17 + ( normType != null ? normType.hashCode() : 0 );
result = result*17 + ( normValue != null ? normValue.hashCode() : 0 );
return result;
}
/**
* @see Object#equals(Object)
*/
public boolean equals( Object obj )
{
if ( this == obj )
{
return true;
}
if ( !( obj instanceof AVA ) )
{
return false;
}
AVA instance = (AVA)obj;
// Compare the type
if ( normType == null )
{
if ( instance.normType != null )
{
return false;
}
}
else
{
if ( !normType.equals( instance.normType ) )
{
return false;
}
}
// Compare the values
if ( normValue.isNull() )
{
return instance.normValue.isNull();
}
else
{
return normValue.equals( instance.normValue );
}
}
/**
* @see Externalizable#readExternal(ObjectInput)<p>
*
* An AVA is composed of a type and a value.
* The data are stored following the structure :
*
* <li>upName</li> The User provided ATAV
* <li>start</li> The position of this ATAV in the DN
* <li>length</li> The ATAV length
* <li>upType</li> The user Provided Type
* <li>normType</li> The normalized AttributeType
* <li>isHR<li> Tells if the value is a String or not
* <p>
* if the value is a String :
* <li>upValue</li> The User Provided value.
* <li>value</li> The normalized value.
* <p>
* if the value is binary :
* <li>upValueLength</li>
* <li>upValue</li> The User Provided value.
* <li>valueLength</li>
* <li>value</li> The normalized value.
*/
public void writeExternal( ObjectOutput out ) throws IOException
{
if ( StringTools.isEmpty( upName ) ||
StringTools.isEmpty( upType ) ||
StringTools.isEmpty( normType ) ||
( start < 0 ) ||
( length < 2 ) || // At least a type and '='
( upValue.isNull() ) ||
( normValue.isNull() ) )
{
String message = "Cannot serialize an wrong ATAV, ";
if ( StringTools.isEmpty( upName ) )
{
message += "the upName should not be null or empty";
}
else if ( StringTools.isEmpty( upType ) )
{
message += "the upType should not be null or empty";
}
else if ( StringTools.isEmpty( normType ) )
{
message += "the normType should not be null or empty";
}
else if ( start < 0 )
{
message += "the start should not be < 0";
}
else if ( length < 2 )
{
message += "the length should not be < 2";
}
else if ( upValue.isNull() )
{
message += "the upValue should not be null";
}
else if ( normValue.isNull() )
{
message += "the value should not be null";
}
LOG.error( message );
throw new IOException( message );
}
out.writeUTF( upName );
out.writeInt( start );
out.writeInt( length );
out.writeUTF( upType );
out.writeUTF( normType );
boolean isHR = !normValue.isBinary();
out.writeBoolean( isHR );
if ( isHR )
{
out.writeUTF( upValue.getString() );
out.writeUTF( normValue.getString() );
}
else
{
out.writeInt( upValue.length() );
out.write( upValue.getBytes() );
out.writeInt( normValue.length() );
out.write( normValue.getBytes() );
}
}
/**
* @see Externalizable#readExternal(ObjectInput)
*
* We read back the data to create a new ATAV. The structure
* read is exposed in the {@link AVA#writeExternal(ObjectOutput)}
* method<p>
*/
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
{
upName = in.readUTF();
start = in.readInt();
length = in.readInt();
upType = in.readUTF();
normType = in.readUTF();
boolean isHR = in.readBoolean();
if ( isHR )
{
upValue = new StringValue( in.readUTF() );
normValue = new StringValue( in.readUTF() );
}
else
{
int upValueLength = in.readInt();
byte[] upValueBytes = new byte[upValueLength];
in.readFully( upValueBytes );
upValue = new BinaryValue( upValueBytes );
int valueLength = in.readInt();
byte[] normValueBytes = new byte[valueLength];
in.readFully( normValueBytes );
normValue = new BinaryValue( normValueBytes );
}
}
/**
* A String representation of a AVA.
*
* @return A string representing a AVA
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
if ( StringTools.isEmpty( normType ) || StringTools.isEmpty( normType.trim() ) )
{
return "";
}
sb.append( normType ).append( "=" );
if ( normValue != null )
{
sb.append( normValue.getString() );
}
return sb.toString();
}
}