blob: 52319a04fed51097d46fa02385031e9d92a29ba7 [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.entry;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import javax.naming.NamingException;
import javax.naming.directory.InvalidAttributeValueException;
import org.apache.directory.shared.asn1.primitives.OID;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A server side entry attribute aware of schema.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public final class DefaultServerAttribute extends DefaultClientAttribute implements ServerAttribute
{
public static final long serialVersionUID = 1L;
/** logger for reporting errors that might not be handled properly upstream */
private static final Logger LOG = LoggerFactory.getLogger( DefaultServerAttribute.class );
/** The associated AttributeType */
private AttributeType attributeType;
//-----------------------------------------------------------------------
// utility methods
//-----------------------------------------------------------------------
/**
* Private helper method used to set an UpId from an attributeType
*
* @param at The attributeType for which we want the upID
* @return the ID of the given attributeType
*/
private String getUpId( AttributeType at )
{
String atUpId = at.getName();
if ( atUpId == null )
{
atUpId = at.getOid();
}
return atUpId;
}
//-------------------------------------------------------------------------
// Constructors
//-------------------------------------------------------------------------
/**
*
* Creates a new instance of DefaultServerAttribute, by copying
* another attribute, which can be a ClientAttribute. If the other
* attribute is a ServerAttribute, it will be copied.
*
* @param attributeType The attribute's type
* @param attribute The attribute to be copied
*/
public DefaultServerAttribute( AttributeType attributeType, EntryAttribute attribute )
{
// Copy the common values. isHR is only available on a ServerAttribute
this.attributeType = attributeType;
this.id = attribute.getId();
this.upId = attribute.getUpId();
if ( attribute instanceof ServerAttribute )
{
isHR = attribute.isHR();
// Copy all the values
for ( Value<?> value:attribute )
{
add( value.clone() );
}
}
else
{
try
{
isHR = attributeType.getSyntax().isHumanReadable();
}
catch ( NamingException ne )
{
// Do nothing : the syntax should always exist ...
}
// Copy all the values
for ( Value<?> clientValue:attribute )
{
Value<?> serverValue = null;
// We have to convert the value first
if ( clientValue instanceof ClientStringValue )
{
if ( isHR )
{
serverValue = new ServerStringValue( attributeType, (String)clientValue.get() );
}
else
{
// We have to convert the value to a binary value first
serverValue = new ServerBinaryValue( attributeType,
StringTools.getBytesUtf8( (String)clientValue.get() ) );
}
}
else if ( clientValue instanceof ClientBinaryValue )
{
if ( isHR )
{
// We have to convert the value to a String value first
serverValue = new ServerStringValue( attributeType,
StringTools.utf8ToString( (byte[])clientValue.get() ) );
}
else
{
serverValue = new ServerBinaryValue( attributeType, (byte[])clientValue.get() );
}
}
add( serverValue );
}
}
}
// maybe have some additional convenience constructors which take
// an initial value as a string or a byte[]
/**
* Create a new instance of a EntryAttribute, without ID nor value.
*
* @param attributeType the attributeType for the empty attribute added into the entry
*/
public DefaultServerAttribute( AttributeType attributeType )
{
if ( attributeType == null )
{
throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
}
setAttributeType( attributeType );
}
/**
* Create a new instance of a EntryAttribute, without value.
*
* @param upId the ID for the added attributeType
* @param attributeType the added AttributeType
*/
public DefaultServerAttribute( String upId, AttributeType attributeType )
{
if ( attributeType == null )
{
String message = "The AttributeType parameter should not be null";
LOG.error( message );
throw new IllegalArgumentException( message );
}
setAttributeType( attributeType );
setUpId( upId );
}
/**
* Doc me more!
*
* If the value does not correspond to the same attributeType, then it's
* wrapped value is copied into a new Value which uses the specified
* attributeType.
*
* @param attributeType the attribute type according to the schema
* @param vals an initial set of values for this attribute
*/
public DefaultServerAttribute( AttributeType attributeType, Value<?>... vals )
{
this( null, attributeType, vals );
}
/**
* Doc me more!
*
* If the value does not correspond to the same attributeType, then it's
* wrapped value is copied into a new Value which uses the specified
* attributeType.
*
* Otherwise, the value is stored, but as a reference. It's not a copy.
*
* @param upId the ID of the added attribute
* @param attributeType the attribute type according to the schema
* @param vals an initial set of values for this attribute
*/
public DefaultServerAttribute( String upId, AttributeType attributeType, Value<?>... vals )
{
if ( attributeType == null )
{
throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
}
setAttributeType( attributeType );
setUpId( upId, attributeType );
add( vals );
}
/**
* Create a new instance of a EntryAttribute, without ID but with some values.
*
* @param attributeType The attributeType added on creation
* @param vals The added value for this attribute
*/
public DefaultServerAttribute( AttributeType attributeType, String... vals )
{
this( null, attributeType, vals );
}
/**
* Create a new instance of a EntryAttribute.
*
* @param upId the ID for the added attribute
* @param attributeType The attributeType added on creation
* @param vals the added values for this attribute
*/
public DefaultServerAttribute( String upId, AttributeType attributeType, String... vals )
{
if ( attributeType == null )
{
throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
}
setAttributeType( attributeType );
add( vals );
setUpId( upId, attributeType );
}
/**
* Create a new instance of a EntryAttribute, with some byte[] values.
*
* @param attributeType The attributeType added on creation
* @param vals The value for the added attribute
*/
public DefaultServerAttribute( AttributeType attributeType, byte[]... vals )
{
this( null, attributeType, vals );
}
/**
* Create a new instance of a EntryAttribute, with some byte[] values.
*
* @param upId the ID for the added attribute
* @param attributeType the AttributeType to be added
* @param vals the values for the added attribute
*/
public DefaultServerAttribute( String upId, AttributeType attributeType, byte[]... vals )
{
if ( attributeType == null )
{
throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
}
setAttributeType( attributeType );
add( vals );
setUpId( upId, attributeType );
}
//-------------------------------------------------------------------------
// API
//-------------------------------------------------------------------------
/**
* <p>
* Adds some values to this attribute. If the new values are already present in
* the attribute values, the method has no effect.
* </p>
* <p>
* The new values are added at the end of list of values.
* </p>
* <p>
* This method returns the number of values that were added.
* </p>
* <p>
* If the value's type is different from the attribute's type,
* the value is not added.
* </p>
* It's the responsibility of the caller to check if the stored
* values are consistent with the attribute's type.
* <p>
*
* @param vals some new values to be added which may be null
* @return the number of added values, or 0 if none has been added
*/
public int add( byte[]... vals )
{
if ( !isHR )
{
int nbAdded = 0;
for ( byte[] val:vals )
{
Value<?> value = new ServerBinaryValue( attributeType, val );
try
{
value.normalize();
}
catch( NamingException ne )
{
// The value can't be normalized : we don't add it.
LOG.error( "The value '" + val + "' can't be normalized, it hasn't been added" );
return 0;
}
if ( add( value ) != 0 )
{
nbAdded++;
}
else
{
LOG.error( "The value '" + val + "' is incorrect, it hasn't been added" );
}
}
return nbAdded;
}
else
{
// We can't add Binary values into a String serverAttribute
return 0;
}
}
/**
* <p>
* Adds some values to this attribute. If the new values are already present in
* the attribute values, the method has no effect.
* </p>
* <p>
* The new values are added at the end of list of values.
* </p>
* <p>
* This method returns the number of values that were added.
* </p>
* If the value's type is different from the attribute's type,
* the value is not added.
*
* @param vals some new values to be added which may be null
* @return the number of added values, or 0 if none has been added
*/
public int add( String... vals )
{
if ( isHR )
{
int nbAdded = 0;
for ( String val:vals )
{
Value<String> newValue = new ServerStringValue( attributeType, val );
if ( !contains( newValue ) )
{
if ( add( newValue ) != 0 )
{
nbAdded++;
}
else
{
LOG.error( "The value '" + val + "' is incorrect, it hasn't been added" );
}
}
}
return nbAdded;
}
else
{
// We can't add String values into a Binary serverAttribute
return 0;
}
}
/**
* @see EntryAttribute#add(org.apache.directory.shared.ldap.entry.Value...)
*
* @return the number of added values into this attribute
*/
public int add( Value<?>... vals )
{
int nbAdded = 0;
for ( Value<?> val:vals )
{
try
{
if ( attributeType.getSyntax().isHumanReadable() )
{
if ( val == null )
{
Value<String> nullSV = new ServerStringValue( attributeType, (String)null );
if ( !values.contains( nullSV ) )
{
values.add( nullSV );
nbAdded++;
}
}
else if ( val instanceof ServerStringValue )
{
if ( !values.contains( val ) )
{
if ( values.add( val ) )
{
nbAdded++;
}
}
}
else if ( val instanceof ClientStringValue )
{
// If we get a Client value, convert it to a Server value first
Value<String> serverStringValue = new ServerStringValue( attributeType, (String)val.get() );
if ( !values.contains( serverStringValue ) )
{
if ( values.add( serverStringValue ) )
{
nbAdded++;
}
}
}
else
{
String message = "The value must be a String, as its AttributeType is H/R";
LOG.error( message );
}
}
else
{
if ( val == null )
{
Value<byte[]> nullSV = new ServerBinaryValue( attributeType, (byte[])null );
if ( !values.contains( nullSV ) )
{
values.add( nullSV );
nbAdded++;
}
}
else if ( ( val instanceof ClientBinaryValue ) )
{
Value<byte[]> serverBinaryValue = new ServerBinaryValue( attributeType, (byte[])val.get() );
if ( !values.contains( serverBinaryValue ) )
{
if ( values.add( serverBinaryValue ) )
{
nbAdded++;
}
}
}
else if ( val instanceof ServerBinaryValue )
{
if ( !values.contains( val ) )
{
if ( values.add( val ) )
{
nbAdded++;
}
}
}
else
{
String message = "The value must be a byte[], as its AttributeType is not H/R";
LOG.error( message );
}
}
}
catch ( NamingException ne )
{
String message = "Error while adding value '" + val.toString() +"' : " + ne.getMessage();
LOG.error( message );
}
}
return nbAdded;
}
/**
* Remove all the values from this attribute type, including a
* null value.
*/
public void clear()
{
values.clear();
}
/**
* <p>
* Indicates whether all the specified values are attribute's values. If
* at least one value is not an attribute's value, this method will return
* <code>false</code>
* </p>
* <p>
* If the Attribute is HR, this method will returns <code>false</code>
* </p>
*
* @param vals the values
* @return true if this attribute contains all the values, otherwise false
*/
public boolean contains( byte[]... vals )
{
if ( !isHR )
{
// Iterate through all the values, and quit if we
// don't find one in the values
for ( byte[] val:vals )
{
ServerBinaryValue value = new ServerBinaryValue( attributeType, val );
try
{
value.normalize();
}
catch ( NamingException ne )
{
return false;
}
if ( !values.contains( value ) )
{
return false;
}
}
return true;
}
else
{
return false;
}
}
/**
* <p>
* Indicates whether all the specified values are attribute's values. If
* at least one value is not an attribute's value, this method will return
* <code>false</code>
* </p>
* <p>
* If the Attribute is not HR, this method will returns <code>false</code>
* </p>
*
* @param vals the values
* @return true if this attribute contains all the values, otherwise false
*/
public boolean contains( String... vals )
{
if ( isHR )
{
// Iterate through all the values, and quit if we
// don't find one in the values
for ( String val:vals )
{
ServerStringValue value = new ServerStringValue( attributeType, val );
if ( !values.contains( value ) )
{
return false;
}
}
return true;
}
else
{
return false;
}
}
/**
* <p>
* Indicates whether the specified values are some of the attribute's values.
* </p>
* <p>
* If the Attribute is HR, te metho will only accept String Values. Otherwise,
* it will only accept Binary values.
* </p>
*
* @param vals the values
* @return true if this attribute contains all the values, otherwise false
*/
public boolean contains( Value<?>... vals )
{
// Iterate through all the values, and quit if we
// don't find one in the values. We have to separate the check
// depending on the isHR flag value.
if ( isHR )
{
for ( Value<?> val:vals )
{
if ( val instanceof ServerStringValue )
{
if ( !values.contains( val ) )
{
return false;
}
}
else if ( val instanceof ClientStringValue )
{
ServerStringValue serverValue = new ServerStringValue( attributeType, (String)val.get() );
if ( !values.contains( serverValue ) )
{
return false;
}
}
else
{
// Not a String value
return false;
}
}
}
else
{
for ( Value<?> val:vals )
{
if ( val instanceof ClientBinaryValue )
{
if ( !values.contains( val ) )
{
return false;
}
}
else
{
// Not a Binary value
return false;
}
}
}
return true;
}
/**
* Get the attribute type associated with this ServerAttribute.
*
* @return the attributeType associated with this entry attribute
*/
public AttributeType getAttributeType()
{
return attributeType;
}
/**
* <p>
* Check if the current attribute type is of the expected attributeType
* </p>
* <p>
* This method won't tell if the current attribute is a descendant of
* the attributeType. For instance, the "CN" serverAttribute will return
* false if we ask if it's an instance of "Name".
* </p>
*
* @param attributeId The AttributeType ID to check
* @return True if the current attribute is of the expected attributeType
* @throws InvalidAttributeValueException If there is no AttributeType
*/
public boolean instanceOf( String attributeId ) throws InvalidAttributeValueException
{
String trimmedId = StringTools.trim( attributeId );
if ( StringTools.isEmpty( trimmedId ) )
{
return false;
}
String normId = StringTools.lowerCaseAscii( trimmedId );
for ( String name:attributeType.getNamesRef() )
{
if ( normId.equalsIgnoreCase( name ) )
{
return true;
}
}
return normId.equalsIgnoreCase( attributeType.getOid() );
}
/**
* <p>
* Checks to see if this attribute is valid along with the values it contains.
* </p>
* <p>
* An attribute is valid if :
* <li>All of its values are valid with respect to the attributeType's syntax checker</li>
* <li>If the attributeType is SINGLE-VALUE, then no more than a value should be present</li>
*</p>
* @return true if the attribute and it's values are valid, false otherwise
* @throws NamingException if there is a failure to check syntaxes of values
*/
public boolean isValid() throws NamingException
{
// First check if the attribute has more than one value
// if the attribute is supposed to be SINGLE_VALUE
if ( attributeType.isSingleValue() && ( values.size() > 1 ) )
{
return false;
}
for ( Value<?> value : values )
{
if ( ! value.isValid() )
{
return false;
}
}
return true;
}
/**
* @see EntryAttribute#remove(byte[]...)
*
* @return <code>true</code> if all the values shave been removed from this attribute
*/
public boolean remove( byte[]... vals )
{
if ( isHR )
{
return false;
}
boolean removed = true;
for ( byte[] val:vals )
{
ServerBinaryValue value = new ServerBinaryValue( attributeType, val );
removed &= values.remove( value );
}
return removed;
}
/**
* @see EntryAttribute#remove(String...)
*
* @return <code>true</code> if all the values shave been removed from this attribute
*/
public boolean remove( String... vals )
{
if ( !isHR )
{
return false;
}
boolean removed = true;
for ( String val:vals )
{
ServerStringValue value = new ServerStringValue( attributeType, val );
removed &= values.remove( value );
}
return removed;
}
/**
* @see EntryAttribute#remove(org.apache.directory.shared.ldap.entry.Value...)
*
* @return <code>true</code> if all the values shave been removed from this attribute
*/
public boolean remove( Value<?>... vals )
{
boolean removed = true;
// Loop through all the values to remove. If one of
// them is not present, the method will return false.
// As the attribute may be HR or not, we have two separated treatments
if ( isHR )
{
for ( Value<?> val:vals )
{
if ( val instanceof ClientStringValue )
{
ServerStringValue ssv = new ServerStringValue( attributeType, (String)val.get() );
removed &= values.remove( ssv );
}
else if ( val instanceof ServerStringValue )
{
removed &= values.remove( val );
}
else
{
removed = false;
}
}
}
else
{
for ( Value<?> val:vals )
{
if ( val instanceof ClientBinaryValue )
{
ServerBinaryValue sbv = new ServerBinaryValue( attributeType, (byte[])val.get() );
removed &= values.remove( sbv );
}
else if ( val instanceof ServerBinaryValue )
{
removed &= values.remove( val );
}
else
{
removed = false;
}
}
}
return removed;
}
/**
* <p>
* Set the attribute type associated with this ServerAttribute.
* </p>
* <p>
* The current attributeType will be replaced. It is the responsibility of
* the caller to insure that the existing values are compatible with the new
* AttributeType
* </p>
*
* @param attributeType the attributeType associated with this entry attribute
*/
public void setAttributeType( AttributeType attributeType )
{
if ( attributeType == null )
{
throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
}
this.attributeType = attributeType;
setUpId( null, attributeType );
try
{
if ( attributeType.getSyntax().isHumanReadable() )
{
isHR = true;
}
else
{
isHR = false;
}
}
catch ( NamingException ne )
{
// If we have an exception while trying to get the Syntax for this attribute
// just set it as Binary
isHR = false;
}
}
/**
* <p>
* Overload the ClientAttribte isHR method : we can't change this flag
* for a ServerAttribute, as the HR is already set using the AttributeType.
* Set the attribute to Human Readable or to Binary.
* </p>
*
* @param isHR <code>true</code> for a Human Readable attribute,
* <code>false</code> for a Binary attribute.
*/
public void setHR( boolean isHR )
{
// Do nothing...
}
/**
* <p>
* Overload the {@link DefaultClientAttribute#setId(String)} method.
* </p>
* <p>
* As the attributeType has already been set, we have to be sure that the
* argument is compatible with the attributeType's name.
* </p>
* <p>
* If the given ID is not compatible with the attributeType's possible
* names, the previously loaded ID will be kept.
* </p>
*
* @param id The attribute ID
*/
public void setId( String id )
{
if ( !StringTools.isEmpty( StringTools.trim( id ) ) )
{
if ( attributeType.getName() == null )
{
// If the name is null, then we may have to store an OID
if ( OID.isOID( id ) && attributeType.getOid().equals( id ) )
{
// Everything is fine, store the upId.
// This should not happen...
super.setId( id );
}
}
else
{
// We have at least one name. Check that the normalized upId
// is one of those names. Otherwise, the upId may be an OID too.
// In this case, it must be equals to the attributeType OID.
String normId = StringTools.lowerCaseAscii( StringTools.trim( id ) );
for ( String atName:attributeType.getNamesRef() )
{
if ( atName.equalsIgnoreCase( normId ) )
{
// Found ! We can store the upId and get out
super.setId( normId );
return;
}
}
// Last case, the UpId is an OID
if ( OID.isOID( normId ) && attributeType.getOid().equals( normId ) )
{
// We have an OID : stores it
super.setUpId( normId );
}
else
{
// The id is incorrect : this is not allowed
throw new IllegalArgumentException( "The ID '" + id + "'is incompatible with the AttributeType's id '" +
attributeType.getName() + "'" );
}
}
}
else
{
throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" +
" value when trimmed" );
}
}
/**
* <p>
* Overload the {@link DefaultClientAttribute#setUpId(String)} method.
* </p>
* <p>
* As the attributeType has already been set, we have to be sure that the
* argument is compatible with the attributeType's name.
* </p>
* <p>
* If the given ID is not compatible with the attributeType's possible
* names, the previously loaded ID will be kept.
* </p>
*
* @param upId The attribute ID
*/
public void setUpId( String upId )
{
if ( !StringTools.isEmpty( StringTools.trim( upId ) ) )
{
if ( attributeType.getName() == null )
{
// If the name is null, then we may have to store an OID
if ( OID.isOID( upId ) && attributeType.getOid().equals( upId ) )
{
// Everything is fine, store the upId.
// This should not happen...
super.setUpId( upId );
}
}
else
{
// We have at least one name. Check that the normalized upId
// is one of those names. Otherwise, the upId may be an OID too.
// In this case, it must be equals to the attributeType OID.
String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
for ( String atId:attributeType.getNamesRef() )
{
if ( atId.equalsIgnoreCase( normUpId ) )
{
// Found ! We can store the upId and get out
super.setUpId( upId );
return;
}
}
// Last case, the UpId is an OID
if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
{
// We have an OID : stores it
super.setUpId( upId );
}
}
}
}
/**
* <p>
* Set the user provided ID. If we have none, the upId is assigned
* the attributetype's name. If it does not have any name, we will
* use the OID.
* </p>
* <p>
* If we have an upId and an AttributeType, they must be compatible. :
* - if the upId is an OID, it must be the AttributeType's OID
* - otherwise, its normalized form must be equals to ones of
* the attributeType's names.
* </p>
* <p>
* In any case, the ATtributeType will be changed. The caller is responsible for
* the present values to be compatoble with the new AttributeType.
* </p>
*
* @param upId The attribute ID
* @param attributeType The associated attributeType
*/
public void setUpId( String upId, AttributeType attributeType )
{
if ( StringTools.isEmpty( StringTools.trim( upId ) ) )
{
super.setUpId( getUpId( attributeType ) );
this.attributeType = attributeType;
}
else
{
String name = attributeType.getName();
if ( name == null )
{
// If the name is null, then we may have to store an OID
if ( OID.isOID( upId ) && attributeType.getOid().equals( upId ) )
{
// Everything is fine, store the upId.
super.setUpId( upId );
this.attributeType = attributeType;
}
else
{
// We have a difference or the upId is not a valid OID :
// we will use the attributeTypeOID in this case.
LOG.warn( "The upID ({}) is not an OID or is different from the AttributeType OID({})",
upId, attributeType.getOid() );
super.setUpId( attributeType.getOid() );
this.attributeType = attributeType;
}
}
else
{
// We have at least one name. Check that the normalized upId
// is one of those names. Otherwise, the upId may be an OID too.
// In this case, it must be equals to the attributeType OID.
String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
for ( String atId:attributeType.getNamesRef() )
{
if ( atId.equalsIgnoreCase( normUpId ) )
{
// Found ! We can store the upId and get out
super.setUpId( upId );
this.attributeType = attributeType;
return;
}
}
// UpId was not found in names. It should be an OID, or if not, we
// will use the AttributeType name.
if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
{
// We have an OID : stores it
super.setUpId( upId );
this.attributeType = attributeType;
}
else
{
String message = "The upID (" + upId + ") is not an OID or is different from the AttributeType OID (" +
attributeType.getOid() + ")";
// Not a valid OID : use the AttributeTypes OID name instead
LOG.error( message );
throw new IllegalArgumentException( message );
}
}
}
}
/**
* Convert the ServerAttribute to a ClientAttribute
*
* @return An instance of ClientAttribute
*/
public EntryAttribute toClientAttribute()
{
// Create the new EntryAttribute
EntryAttribute clientAttribute = new DefaultClientAttribute( upId );
// Copy the values
for ( Value<?> value:this )
{
Value<?> clientValue = null;
if ( value instanceof ServerStringValue )
{
clientValue = new ClientStringValue( (String)value.get() );
}
else
{
clientValue = new ClientBinaryValue( (byte[])value.get() );
}
clientAttribute.add( clientValue );
}
return clientAttribute;
}
//-------------------------------------------------------------------------
// Serialization methods
//-------------------------------------------------------------------------
/**
* @see java.io.Externalizable#writeExternal(ObjectOutput)
*
* We can't use this method for a ServerAttribute, as we have to feed the value
* with an AttributeType object
*/
public void writeExternal( ObjectOutput out ) throws IOException
{
throw new IllegalStateException( "Cannot use standard serialization for a ServerAttribute" );
}
/**
* @see Externalizable#writeExternal(ObjectOutput)
* <p>
*
* This is the place where we serialize attributes, and all theirs
* elements.
*
* The inner structure is the same as the client attribute, but we can't call
* it as we won't be able to serialize the serverValues
*
*/
public void serialize( ObjectOutput out ) throws IOException
{
// Write the UPId (the id will be deduced from the upID)
out.writeUTF( upId );
// Write the HR flag, if not null
if ( isHR != null )
{
out.writeBoolean( true );
out.writeBoolean( isHR );
}
else
{
out.writeBoolean( false );
}
// Write the number of values
out.writeInt( size() );
if ( size() > 0 )
{
// Write each value
for ( Value<?> value:values )
{
// Write the value, using the correct method
if ( value instanceof ServerStringValue )
{
((ServerStringValue)value).serialize( out );
}
else
{
((ServerBinaryValue)value).serialize( out );
}
}
}
}
/**
* @see java.io.Externalizable#readExternal(ObjectInput)
*
* We can't use this method for a ServerAttribute, as we have to feed the value
* with an AttributeType object
*/
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
{
throw new IllegalStateException( "Cannot use standard serialization for a ServerAttribute" );
}
/**
* @see Externalizable#readExternal(ObjectInput)
*/
public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
{
// Read the ID and the UPId
upId = in.readUTF();
// Compute the id
setUpId( upId );
// Read the HR flag, if not null
if ( in.readBoolean() )
{
isHR = in.readBoolean();
}
// Read the number of values
int nbValues = in.readInt();
if ( nbValues > 0 )
{
for ( int i = 0; i < nbValues; i++ )
{
Value<?> value = null;
if ( isHR )
{
value = new ServerStringValue( attributeType );
((ServerStringValue)value).deserialize( in );
}
else
{
value = new ServerBinaryValue( attributeType );
((ServerBinaryValue)value).deserialize( in );
}
try
{
value.normalize();
}
catch ( NamingException ne )
{
// Do nothing...
}
values.add( value );
}
}
}
//-------------------------------------------------------------------------
// Overloaded Object class methods
//-------------------------------------------------------------------------
/**
* Clone an attribute. All the element are duplicated, so a modification on
* the original object won't affect the cloned object, as a modification
* on the cloned object has no impact on the original object
*
* @return a clone of the current attribute
*/
public ServerAttribute clone()
{
// clone the structure by cloner the inherited class
ServerAttribute clone = (ServerAttribute)super.clone();
// We are done !
return clone;
}
/**
* @see Object#equals(Object)
*
* @return <code>true</code> if the two objects are equal
*/
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
if ( ! (obj instanceof ServerAttribute ) )
{
return false;
}
ServerAttribute other = (ServerAttribute)obj;
if ( !attributeType.equals( other.getAttributeType() ) )
{
return false;
}
if ( values.size() != other.size() )
{
return false;
}
for ( Value<?> val:values )
{
if ( ! other.contains( val ) )
{
return false;
}
}
return true;
}
/**
* The hashCode is based on the id, the isHR flag and
* on the internal values.
*
* @see Object#hashCode()
*
* @return the instance's hash code
*/
public int hashCode()
{
int h = super.hashCode();
if ( attributeType != null )
{
h = h*17 + attributeType.hashCode();
}
return h;
}
/**
* @see Object#toString()
*
* @return A String representation of this instance
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
if ( ( values != null ) && ( values.size() != 0 ) )
{
for ( Value<?> value:values )
{
sb.append( " " ).append( upId ).append( ": " );
if ( value.isNull() )
{
sb.append( "''" );
}
else
{
sb.append( value );
}
sb.append( '\n' );
}
}
else
{
sb.append( " " ).append( upId ).append( ": (null)\n" );
}
return sb.toString();
}
}