| /* |
| * 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.entry; |
| |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| |
| import org.apache.directory.shared.i18n.I18n; |
| import org.apache.directory.shared.ldap.exception.LdapException; |
| import org.apache.directory.shared.ldap.schema.AttributeType; |
| import org.apache.directory.shared.ldap.schema.LdapComparator; |
| import org.apache.directory.shared.ldap.schema.Normalizer; |
| import org.apache.directory.shared.ldap.schema.SyntaxChecker; |
| import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator; |
| import org.apache.directory.shared.ldap.util.StringTools; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * A server side schema aware wrapper around a binary attribute value. |
| * This value wrapper uses schema information to syntax check values, |
| * and to compare them for equality and ordering. It caches results |
| * and invalidates them when the wrapped value changes. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class BinaryValue extends AbstractValue<byte[]> |
| { |
| /** Used for serialization */ |
| public static final long serialVersionUID = 2L; |
| |
| /** logger for reporting errors that might not be handled properly upstream */ |
| private static final Logger LOG = LoggerFactory.getLogger( BinaryValue.class ); |
| |
| /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */ |
| private volatile int h; |
| |
| |
| /** |
| * Creates a BinaryValue without an initial wrapped value. |
| */ |
| public BinaryValue() |
| { |
| wrappedValue = null; |
| normalized = false; |
| valid = null; |
| normalizedValue = null; |
| } |
| |
| |
| /** |
| * Creates a BinaryValue without an initial wrapped value. |
| * |
| * @param attributeType the schema type associated with this BinaryValue |
| */ |
| public BinaryValue( AttributeType attributeType ) |
| { |
| if ( attributeType == null ) |
| { |
| String message = I18n.err( I18n.ERR_04442_NULL_AT_NOT_ALLOWED ); |
| LOG.error( message ); |
| throw new IllegalArgumentException( message ); |
| } |
| |
| if ( attributeType.getSyntax() == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); |
| } |
| |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() ); |
| } |
| |
| this.attributeType = attributeType; |
| } |
| |
| |
| /** |
| * Creates a BinaryValue with an initial wrapped binary value. |
| * |
| * @param value the binary value to wrap which may be null, or a zero length byte array |
| */ |
| public BinaryValue( byte[] value ) |
| { |
| if ( value != null ) |
| { |
| this.wrappedValue = new byte[value.length]; |
| System.arraycopy( value, 0, this.wrappedValue, 0, value.length ); |
| } |
| else |
| { |
| this.wrappedValue = null; |
| } |
| |
| normalized = false; |
| valid = null; |
| normalizedValue = null; |
| } |
| |
| |
| /** |
| * Creates a BinaryValue with an initial wrapped binary value. |
| * |
| * @param attributeType the schema type associated with this BinaryValue |
| * @param value the binary value to wrap which may be null, or a zero length byte array |
| */ |
| public BinaryValue( AttributeType attributeType, byte[] value ) |
| { |
| this( attributeType ); |
| SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker(); |
| |
| if ( syntaxChecker != null ) |
| { |
| if ( syntaxChecker.isValidSyntax( value ) ) |
| { |
| this.wrappedValue = value; |
| } |
| else |
| { |
| String msg = I18n.err( I18n.ERR_04479_INVALID_SYNTAX_VALUE, value, attributeType.getName() ); |
| LOG.info( msg ); |
| throw new IllegalArgumentException( msg ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Gets a direct reference to the normalized representation for the |
| * wrapped value of this ServerValue wrapper. Implementations will most |
| * likely leverage the attributeType this value is associated with to |
| * determine how to properly normalize the wrapped value. |
| * |
| * @return the normalized version of the wrapped value |
| * @throws LdapException if schema entity resolution fails or normalization fails |
| */ |
| public byte[] getNormalizedValue() |
| { |
| if ( isNull() ) |
| { |
| return null; |
| } |
| |
| if ( !normalized ) |
| { |
| try |
| { |
| normalize(); |
| } |
| catch ( LdapException ne ) |
| { |
| String message = "Cannot normalize the value :" + ne.getLocalizedMessage(); |
| LOG.warn( message ); |
| normalized = false; |
| } |
| } |
| |
| if ( normalizedValue != null ) |
| { |
| byte[] copy = new byte[normalizedValue.length]; |
| System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length ); |
| return copy; |
| } |
| else |
| { |
| return StringTools.EMPTY_BYTES; |
| } |
| } |
| |
| |
| /** |
| * Gets the normalized (canonical) representation for the wrapped string. |
| * If the wrapped String is null, null is returned, otherwise the normalized |
| * form is returned. If no the normalizedValue is null, then this method |
| * will attempt to generate it from the wrapped value: repeated calls to |
| * this method do not unnecessarily normalize the wrapped value. Only changes |
| * to the wrapped value result in attempts to normalize the wrapped value. |
| * |
| * @return a reference to the normalized version of the wrapped value |
| */ |
| public byte[] getNormalizedValueReference() |
| { |
| if ( isNull() ) |
| { |
| return null; |
| } |
| |
| if ( !isNormalized() ) |
| { |
| try |
| { |
| normalize(); |
| } |
| catch ( LdapException ne ) |
| { |
| String message = "Cannot normalize the value :" + ne.getLocalizedMessage(); |
| LOG.warn( message ); |
| normalized = false; |
| } |
| } |
| |
| if ( normalizedValue != null ) |
| { |
| return normalizedValue; |
| } |
| else |
| { |
| return wrappedValue; |
| } |
| } |
| |
| |
| /** |
| * Normalize the value. For a client String value, applies the given normalizer. |
| * |
| * It supposes that the client has access to the schema in order to select the |
| * appropriate normalizer. |
| * |
| * @param normalizer The normalizer to apply to the value |
| * @exception LdapException If the value cannot be normalized |
| */ |
| public final void normalize( Normalizer normalizer ) throws LdapException |
| { |
| if ( normalizer != null ) |
| { |
| if ( wrappedValue == null ) |
| { |
| normalizedValue = wrappedValue; |
| normalized = true; |
| same = true; |
| } |
| else |
| { |
| normalizedValue = normalizer.normalize( this ).getBytes(); |
| normalized = true; |
| same = Arrays.equals( wrappedValue, normalizedValue ); |
| } |
| } |
| else |
| { |
| normalizedValue = wrappedValue; |
| normalized = false; |
| same = true; |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void normalize() throws LdapException |
| { |
| if ( isNormalized() ) |
| { |
| // Bypass the normalization if it has already been done. |
| return; |
| } |
| |
| if ( attributeType != null ) |
| { |
| Normalizer normalizer = getNormalizer(); |
| normalize( normalizer ); |
| } |
| else |
| { |
| normalizedValue = wrappedValue; |
| normalized = true; |
| same = true; |
| } |
| } |
| |
| |
| /** |
| * |
| * @see ServerValue#compareTo(Value) |
| * @throws IllegalStateException on failures to extract the comparator, or the |
| * normalizers needed to perform the required comparisons based on the schema |
| */ |
| public int compareTo( Value<byte[]> value ) |
| { |
| if ( isNull() ) |
| { |
| if ( ( value == null ) || value.isNull() ) |
| { |
| return 0; |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| else |
| { |
| if ( ( value == null ) || value.isNull() ) |
| { |
| return 1; |
| } |
| } |
| |
| BinaryValue binaryValue = ( BinaryValue ) value; |
| |
| if ( attributeType != null ) |
| { |
| try |
| { |
| LdapComparator<byte[]> comparator = getLdapComparator(); |
| |
| if ( comparator != null ) |
| { |
| return comparator |
| .compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() ); |
| } |
| else |
| { |
| return new ByteArrayComparator( null ).compare( getNormalizedValueReference(), binaryValue |
| .getNormalizedValueReference() ); |
| } |
| } |
| catch ( LdapException e ) |
| { |
| String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value ); |
| LOG.error( msg, e ); |
| throw new IllegalStateException( msg, e ); |
| } |
| } |
| else |
| { |
| return new ByteArrayComparator( null ).compare( getNormalizedValue(), binaryValue.getNormalizedValue() ); |
| } |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Object Methods |
| // ----------------------------------------------------------------------- |
| /** |
| * @see Object#hashCode() |
| * @return the instance's hashcode |
| */ |
| public int hashCode() |
| { |
| if ( h == 0 ) |
| { |
| // return zero if the value is null so only one null value can be |
| // stored in an attribute - the string version does the same |
| if ( isNull() ) |
| { |
| return 0; |
| } |
| |
| byte[] normalizedValue = getNormalizedValueReference(); |
| h = Arrays.hashCode( normalizedValue ); |
| } |
| |
| return h; |
| } |
| |
| |
| /** |
| * Checks to see if this BinaryValue equals the supplied object. |
| * |
| * This equals implementation overrides the BinaryValue implementation which |
| * is not schema aware. |
| * @throws IllegalStateException on failures to extract the comparator, or the |
| * normalizers needed to perform the required comparisons based on the schema |
| */ |
| public boolean equals( Object obj ) |
| { |
| if ( this == obj ) |
| { |
| return true; |
| } |
| |
| if ( !( obj instanceof BinaryValue ) ) |
| { |
| return false; |
| } |
| |
| BinaryValue other = ( BinaryValue ) obj; |
| |
| if ( isNull() ) |
| { |
| return other.isNull(); |
| } |
| |
| // If we have an attributeType, it must be equal |
| // We should also use the comparator if we have an AT |
| if ( attributeType != null ) |
| { |
| if ( other.attributeType != null ) |
| { |
| if ( !attributeType.equals( other.attributeType ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| other.attributeType = attributeType; |
| } |
| } |
| else if ( other.attributeType != null ) |
| { |
| attributeType = other.attributeType; |
| } |
| |
| // Shortcut : if the values are equals, no need to compare |
| // the normalized values |
| if ( Arrays.equals( wrappedValue, other.wrappedValue ) ) |
| { |
| return true; |
| } |
| |
| if ( attributeType != null ) |
| { |
| // We have an AttributeType, we eed to use the comparator |
| try |
| { |
| Comparator<byte[]> comparator = ( Comparator<byte[]> ) getLdapComparator(); |
| |
| // Compare normalized values |
| if ( comparator == null ) |
| { |
| return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() ); |
| } |
| else |
| { |
| return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0; |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| return false; |
| } |
| |
| } |
| else |
| { |
| // now unlike regular values we have to compare the normalized values |
| return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() ); |
| } |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Private Helper Methods (might be put into abstract base class) |
| // ----------------------------------------------------------------------- |
| /** |
| * @return a copy of the current value |
| */ |
| public BinaryValue clone() |
| { |
| BinaryValue clone = ( BinaryValue ) super.clone(); |
| |
| if ( normalizedValue != null ) |
| { |
| clone.normalizedValue = new byte[normalizedValue.length]; |
| System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length ); |
| } |
| |
| if ( wrappedValue != null ) |
| { |
| clone.wrappedValue = new byte[wrappedValue.length]; |
| System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length ); |
| } |
| |
| return clone; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public byte[] get() |
| { |
| if ( wrappedValue == null ) |
| { |
| return null; |
| } |
| |
| final byte[] copy = new byte[wrappedValue.length]; |
| System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length ); |
| |
| return copy; |
| } |
| |
| |
| /** |
| * Tells if the current value is Binary or String |
| * |
| * @return <code>true</code> if the value is Binary, <code>false</code> otherwise |
| */ |
| public boolean isBinary() |
| { |
| return true; |
| } |
| |
| |
| /** |
| * @return The length of the interned value |
| */ |
| public int length() |
| { |
| return wrappedValue != null ? wrappedValue.length : 0; |
| } |
| |
| |
| /** |
| * Get the wrapped value as a byte[]. This method returns a copy of |
| * the wrapped byte[]. |
| * |
| * @return the wrapped value as a byte[] |
| */ |
| public byte[] getBytes() |
| { |
| return get(); |
| } |
| |
| |
| /** |
| * Get the wrapped value as a String. |
| * |
| * @return the wrapped value as a String |
| */ |
| public String getString() |
| { |
| return StringTools.utf8ToString( wrappedValue ); |
| } |
| |
| |
| /** |
| * @see Externalizable#readExternal(ObjectInput) |
| */ |
| public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // Read the wrapped value, if it's not null |
| int wrappedLength = in.readInt(); |
| |
| if ( wrappedLength >= 0 ) |
| { |
| wrappedValue = new byte[wrappedLength]; |
| |
| if ( wrappedLength > 0 ) |
| { |
| if ( in.read( wrappedValue ) == -1 ) |
| { |
| throw new IOException( I18n.err( I18n.ERR_04480_END_OF_STREAM ) ); |
| } |
| } |
| } |
| |
| // Read the isNormalized flag |
| normalized = in.readBoolean(); |
| |
| if ( normalized ) |
| { |
| int normalizedLength = in.readInt(); |
| |
| if ( normalizedLength >= 0 ) |
| { |
| normalizedValue = new byte[normalizedLength]; |
| |
| if ( normalizedLength > 0 ) |
| { |
| in.read( normalizedValue ); |
| } |
| } |
| } |
| |
| h = 0; |
| } |
| |
| |
| /** |
| * @see Externalizable#writeExternal(ObjectOutput) |
| */ |
| public void writeExternal( ObjectOutput out ) throws IOException |
| { |
| // Write the wrapped value, if it's not null |
| if ( wrappedValue != null ) |
| { |
| out.writeInt( wrappedValue.length ); |
| |
| if ( wrappedValue.length > 0 ) |
| { |
| out.write( wrappedValue, 0, wrappedValue.length ); |
| } |
| } |
| else |
| { |
| out.writeInt( -1 ); |
| } |
| |
| // Write the isNormalized flag |
| if ( normalized ) |
| { |
| out.writeBoolean( true ); |
| |
| // Write the normalized value, if not null |
| if ( normalizedValue != null ) |
| { |
| out.writeInt( normalizedValue.length ); |
| |
| if ( normalizedValue.length > 0 ) |
| { |
| out.write( normalizedValue, 0, normalizedValue.length ); |
| } |
| } |
| else |
| { |
| out.writeInt( -1 ); |
| } |
| } |
| else |
| { |
| out.writeBoolean( false ); |
| } |
| } |
| |
| |
| /** |
| * We will write the value and the normalized value, only |
| * if the normalized value is different. |
| * |
| * If the value is empty, a flag is written at the beginning with |
| * the value true, otherwise, a false is written. |
| * |
| * The data will be stored following this structure : |
| * [length] the wrapped length. Can be -1, if wrapped is null |
| * [value length] |
| * [UP value] if not empty |
| * [normalized] (will be false if the value can't be normalized) |
| * [same] (a flag set to true if the normalized value equals the UP value) |
| * [Norm value] (the normalized value if different from the UP value, and not empty) |
| * |
| * @param out the buffer in which we will stored the serialized form of the value |
| * @throws IOException if we can't write into the buffer |
| */ |
| public void serialize( ObjectOutput out ) throws IOException |
| { |
| if ( wrappedValue != null ) |
| { |
| // write a the wrapped length |
| out.writeInt( wrappedValue.length ); |
| |
| // Write the data if not empty |
| if ( wrappedValue.length > 0 ) |
| { |
| // The data |
| out.write( wrappedValue ); |
| |
| // Normalize the data |
| try |
| { |
| normalize(); |
| |
| if ( !normalized ) |
| { |
| // We may not have a normalizer. Just get out |
| // after having writen the flag |
| out.writeBoolean( false ); |
| } |
| else |
| { |
| // Write a flag indicating that the data has been normalized |
| out.writeBoolean( true ); |
| |
| if ( Arrays.equals( getReference(), normalizedValue ) ) |
| { |
| // Write the 'same = true' flag |
| out.writeBoolean( true ); |
| } |
| else |
| { |
| // Write the 'same = false' flag |
| out.writeBoolean( false ); |
| |
| // Write the normalized value length |
| out.writeInt( normalizedValue.length ); |
| |
| if ( normalizedValue.length > 0 ) |
| { |
| // Write the normalized value if not empty |
| out.write( normalizedValue ); |
| } |
| } |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| // The value can't be normalized, we don't write the |
| // normalized value. |
| normalizedValue = null; |
| out.writeBoolean( false ); |
| } |
| } |
| } |
| else |
| { |
| // Write -1 indicating that the value is null |
| out.writeInt( -1 ); |
| } |
| } |
| |
| |
| /** |
| * |
| * Deserialize a BinaryValue. |
| * |
| * @param in the buffer containing the bytes with the serialized value |
| * @throws IOException |
| * @throws ClassNotFoundException |
| */ |
| public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // The UP value length |
| int wrappedLength = in.readInt(); |
| |
| if ( wrappedLength == -1 ) |
| { |
| // If the value is null, the length will be set to -1 |
| same = true; |
| wrappedValue = null; |
| } |
| else if ( wrappedLength == 0 ) |
| { |
| wrappedValue = StringTools.EMPTY_BYTES; |
| same = true; |
| normalized = true; |
| normalizedValue = wrappedValue; |
| } |
| else |
| { |
| wrappedValue = new byte[wrappedLength]; |
| |
| // Read the data |
| in.readFully( wrappedValue ); |
| |
| // Check if we have a normalized value |
| normalized = in.readBoolean(); |
| |
| if ( normalized ) |
| { |
| // Read the 'same' flag |
| same = in.readBoolean(); |
| |
| if ( !same ) |
| { |
| // Read the normalizedvalue length |
| int normalizedLength = in.readInt(); |
| |
| if ( normalizedLength > 0 ) |
| { |
| normalizedValue = new byte[normalizedLength]; |
| |
| // Read the normalized value |
| if ( in.read( normalizedValue, 0, normalizedLength ) == -1 ) |
| { |
| throw new IOException( I18n.err( I18n.ERR_04480_END_OF_STREAM ) ); |
| } |
| } |
| else |
| { |
| normalizedValue = StringTools.EMPTY_BYTES; |
| } |
| } |
| else |
| { |
| normalizedValue = new byte[wrappedLength]; |
| System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength ); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Dumps binary in hex with label. |
| * |
| * @see Object#toString() |
| */ |
| public String toString() |
| { |
| if ( wrappedValue == null ) |
| { |
| return "null"; |
| } |
| else if ( wrappedValue.length > 16 ) |
| { |
| // Just dump the first 16 bytes... |
| byte[] copy = new byte[16]; |
| |
| System.arraycopy( wrappedValue, 0, copy, 0, 16 ); |
| |
| return "'" + StringTools.dumpBytes( copy ) + "...'"; |
| } |
| else |
| { |
| return "'" + StringTools.dumpBytes( wrappedValue ) + "'"; |
| } |
| } |
| } |