/*
|
* The contents of this file are subject to the terms of the Common Development and
|
* Distribution License (the License). You may not use this file except in compliance with the
|
* License.
|
*
|
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
* specific language governing permission and limitations under the License.
|
*
|
* When distributing Covered Software, include this CDDL Header Notice in each file and include
|
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
* Header, with the fields enclosed by brackets [] replaced by your own identifying
|
* information: "Portions Copyright [year] [name of copyright owner]".
|
*
|
* Copyright 2014-2016 ForgeRock AS.
|
*/
|
package org.opends.server.replication.protocol;
|
|
import java.io.UnsupportedEncodingException;
|
import java.util.Collection;
|
import java.util.Map;
|
import java.util.Map.Entry;
|
|
import org.forgerock.opendj.io.ASN1;
|
import org.forgerock.opendj.io.ASN1Writer;
|
import org.forgerock.opendj.ldap.ByteStringBuilder;
|
import org.opends.server.replication.common.CSN;
|
import org.opends.server.replication.common.ServerState;
|
import org.forgerock.opendj.ldap.DN;
|
|
/**
|
* Byte array builder class encodes data into byte arrays to send messages over
|
* the replication protocol. Built on top of {@link ByteStringBuilder}, it
|
* isolates the latter against legacy type conversions from the replication
|
* protocol. It exposes a fluent API.
|
*
|
* @see ByteArrayScanner ByteArrayScanner class that decodes messages built with
|
* current class.
|
*/
|
public class ByteArrayBuilder
|
{
|
|
private final ByteStringBuilder builder;
|
|
/**
|
* Constructs a ByteArrayBuilder.
|
*/
|
public ByteArrayBuilder()
|
{
|
builder = new ByteStringBuilder(256);
|
}
|
|
/**
|
* Constructs a ByteArrayBuilder.
|
*
|
* @param capacity
|
* the capacity of the underlying ByteStringBuilder
|
*/
|
public ByteArrayBuilder(int capacity)
|
{
|
builder = new ByteStringBuilder(capacity);
|
}
|
|
/**
|
* Append a boolean to this ByteArrayBuilder.
|
*
|
* @param b
|
* the boolean to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendBoolean(boolean b)
|
{
|
appendByte(b ? 1 : 0);
|
return this;
|
}
|
|
/**
|
* Append a byte to this ByteArrayBuilder.
|
*
|
* @param b
|
* the byte to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendByte(int b)
|
{
|
builder.appendByte(b);
|
return this;
|
}
|
|
/**
|
* Append a short to this ByteArrayBuilder.
|
*
|
* @param s
|
* the short to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendShort(int s)
|
{
|
builder.appendShort(s);
|
return this;
|
}
|
|
/**
|
* Append an int to this ByteArrayBuilder.
|
*
|
* @param i
|
* the long to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendInt(int i)
|
{
|
builder.appendInt(i);
|
return this;
|
}
|
|
/**
|
* Append a long to this ByteArrayBuilder.
|
*
|
* @param l
|
* the long to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendLong(long l)
|
{
|
builder.appendLong(l);
|
return this;
|
}
|
|
/**
|
* Append an int to this ByteArrayBuilder by converting it to a String then
|
* encoding that string to a UTF-8 byte array.
|
*
|
* @param i
|
* the int to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendIntUTF8(int i)
|
{
|
return appendString(Integer.toString(i));
|
}
|
|
/**
|
* Append a long to this ByteArrayBuilder by converting it to a String then
|
* encoding that string to a UTF-8 byte array.
|
*
|
* @param l
|
* the long to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendLongUTF8(long l)
|
{
|
return appendString(Long.toString(l));
|
}
|
|
/**
|
* Append a Collection of Strings to this ByteArrayBuilder.
|
*
|
* @param col
|
* the Collection of Strings to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendStrings(Collection<String> col)
|
{
|
//appendInt() would have been safer, but byte is compatible with legacy code
|
appendByte(col.size());
|
for (String s : col)
|
{
|
appendString(s);
|
}
|
return this;
|
}
|
|
/**
|
* Append a String with a zero separator to this ByteArrayBuilder,
|
* or only the zero separator if the string is null
|
* or if the string length is zero.
|
*
|
* @param s
|
* the String to append. Can be null.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendString(String s)
|
{
|
try
|
{
|
if (s != null && s.length() > 0)
|
{
|
appendByteArray(s.getBytes("UTF-8"));
|
}
|
return appendZeroSeparator();
|
}
|
catch (UnsupportedEncodingException e)
|
{
|
throw new RuntimeException("Should never happen", e);
|
}
|
}
|
|
/**
|
* Append a CSN to this ByteArrayBuilder.
|
*
|
* @param csn
|
* the CSN to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendCSN(CSN csn)
|
{
|
csn.toByteString(builder);
|
return this;
|
}
|
|
/**
|
* Append a CSN to this ByteArrayBuilder by converting it to a String then
|
* encoding that string to a UTF-8 byte array.
|
*
|
* @param csn
|
* the CSN to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendCSNUTF8(CSN csn)
|
{
|
appendString(csn.toString());
|
return this;
|
}
|
|
/**
|
* Append a DN to this ByteArrayBuilder by converting it to a String then
|
* encoding that string to a UTF-8 byte array.
|
*
|
* @param dn
|
* the DN to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendDN(DN dn)
|
{
|
appendString(dn.toString());
|
return this;
|
}
|
|
/**
|
* Append all the bytes from the byte array to this ByteArrayBuilder.
|
*
|
* @param bytes
|
* the byte array to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendByteArray(byte[] bytes)
|
{
|
builder.appendBytes(bytes);
|
return this;
|
}
|
|
/**
|
* Append all the bytes from the byte array to this ByteArrayBuilder
|
* and then append a final zero byte separator for compatibility
|
* with legacy implementations.
|
* <p>
|
* Note: the super long method name it is intentional:
|
* nobody will want to use it, which is good because nobody should.
|
*
|
* @param bytes
|
* the byte array to append.
|
* @return this ByteArrayBuilder
|
*/
|
public ByteArrayBuilder appendZeroTerminatedByteArray(byte[] bytes)
|
{
|
builder.appendBytes(bytes);
|
return appendZeroSeparator();
|
}
|
|
private ByteArrayBuilder appendZeroSeparator()
|
{
|
builder.appendByte(0);
|
return this;
|
}
|
|
/**
|
* Append the byte representation of a ServerState to this ByteArrayBuilder
|
* and then append a final zero byte separator.
|
* <p>
|
* Caution: ServerState MUST be the last field. Because ServerState can
|
* contain null character (string termination of serverId string ..) it cannot
|
* be decoded using {@link ByteArrayScanner#nextString()} like the other
|
* fields. The only way is to rely on the end of the input buffer: and that
|
* forces the ServerState to be the last field. This should be changed if we
|
* want to have more than one ServerState field.
|
* <p>
|
* Note: the super long method name it is intentional:
|
* nobody will want to use it, which is good because nobody should.
|
*
|
* @param serverState
|
* the ServerState to append.
|
* @return this ByteArrayBuilder
|
* @see ByteArrayScanner#nextServerStateMustComeLast()
|
*/
|
public ByteArrayBuilder appendServerStateMustComeLast(ServerState serverState)
|
{
|
final Map<Integer, CSN> serverIdToCSN = serverState.getServerIdToCSNMap();
|
for (Entry<Integer, CSN> entry : serverIdToCSN.entrySet())
|
{
|
// FIXME JNR: why append the serverId in addition to the CSN
|
// since the CSN already contains the serverId?
|
appendIntUTF8(entry.getKey()); // serverId
|
appendCSNUTF8(entry.getValue()); // CSN
|
}
|
return appendZeroSeparator(); // stupid legacy zero separator
|
}
|
|
/**
|
* Returns a new ASN1Writer that will append bytes to this ByteArrayBuilder.
|
*
|
* @return a new ASN1Writer that will append bytes to this ByteArrayBuilder.
|
*/
|
public ASN1Writer getASN1Writer()
|
{
|
return ASN1.getWriter(builder);
|
}
|
|
/**
|
* Converts the content of this ByteStringBuilder to a byte array.
|
*
|
* @return the content of this ByteStringBuilder converted to a byte array.
|
*/
|
public byte[] toByteArray()
|
{
|
return builder.toByteArray();
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public String toString()
|
{
|
return builder.toString();
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* boolean fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of boolean fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended boolean fields.
|
*/
|
public static int booleans(int nbFields)
|
{
|
return nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* byte fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of byte fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended byte fields.
|
*/
|
public static int bytes(int nbFields)
|
{
|
return nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* short fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of short fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended short fields.
|
*/
|
public static int shorts(int nbFields)
|
{
|
return 2 * nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* int fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of int fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended int fields.
|
*/
|
public static int ints(int nbFields)
|
{
|
return 4 * nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* long fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of long fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended long fields.
|
*/
|
public static int longs(int nbFields)
|
{
|
return 8 * nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* CSN fields when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of CSN fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended CSN fields.
|
*/
|
public static int csns(int nbFields)
|
{
|
return CSN.BYTE_ENCODING_LENGTH * nbFields;
|
}
|
|
/**
|
* Helper method that returns the number of bytes that would be used by the
|
* CSN fields encoded as a UTF8 string when appended to a ByteArrayBuilder.
|
*
|
* @param nbFields
|
* the number of CSN fields that will be appended to a
|
* ByteArrayBuilder
|
* @return the number of bytes occupied by the appended legacy-encoded CSN
|
* fields.
|
*/
|
public static int csnsUTF8(int nbFields)
|
{
|
return CSN.STRING_ENCODING_LENGTH * nbFields + 1 /* null byte */;
|
}
|
}
|