/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014-2015 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.opends.server.types.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 */; } }