001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *      Copyright 2014-2015 ForgeRock AS
024 */
025package org.opends.server.replication.protocol;
026
027import java.io.UnsupportedEncodingException;
028import java.util.Collection;
029import java.util.Map;
030import java.util.Map.Entry;
031
032import org.forgerock.opendj.io.ASN1;
033import org.forgerock.opendj.io.ASN1Writer;
034import org.forgerock.opendj.ldap.ByteStringBuilder;
035import org.opends.server.replication.common.CSN;
036import org.opends.server.replication.common.ServerState;
037import org.opends.server.types.DN;
038
039/**
040 * Byte array builder class encodes data into byte arrays to send messages over
041 * the replication protocol. Built on top of {@link ByteStringBuilder}, it
042 * isolates the latter against legacy type conversions from the replication
043 * protocol. It exposes a fluent API.
044 *
045 * @see ByteArrayScanner ByteArrayScanner class that decodes messages built with
046 *      current class.
047 */
048public class ByteArrayBuilder
049{
050
051  private final ByteStringBuilder builder;
052
053  /**
054   * Constructs a ByteArrayBuilder.
055   */
056  public ByteArrayBuilder()
057  {
058    builder = new ByteStringBuilder(256);
059  }
060
061  /**
062   * Constructs a ByteArrayBuilder.
063   *
064   * @param capacity
065   *          the capacity of the underlying ByteStringBuilder
066   */
067  public ByteArrayBuilder(int capacity)
068  {
069    builder = new ByteStringBuilder(capacity);
070  }
071
072  /**
073   * Append a boolean to this ByteArrayBuilder.
074   *
075   * @param b
076   *          the boolean to append.
077   * @return this ByteArrayBuilder
078   */
079  public ByteArrayBuilder appendBoolean(boolean b)
080  {
081    appendByte(b ? 1 : 0);
082    return this;
083  }
084
085  /**
086   * Append a byte to this ByteArrayBuilder.
087   *
088   * @param b
089   *          the byte to append.
090   * @return this ByteArrayBuilder
091   */
092  public ByteArrayBuilder appendByte(int b)
093  {
094    builder.appendByte(b);
095    return this;
096  }
097
098  /**
099   * Append a short to this ByteArrayBuilder.
100   *
101   * @param s
102   *          the short to append.
103   * @return this ByteArrayBuilder
104   */
105  public ByteArrayBuilder appendShort(int s)
106  {
107    builder.appendShort(s);
108    return this;
109  }
110
111  /**
112   * Append an int to this ByteArrayBuilder.
113   *
114   * @param i
115   *          the long to append.
116   * @return this ByteArrayBuilder
117   */
118  public ByteArrayBuilder appendInt(int i)
119  {
120    builder.appendInt(i);
121    return this;
122  }
123
124  /**
125   * Append a long to this ByteArrayBuilder.
126   *
127   * @param l
128   *          the long to append.
129   * @return this ByteArrayBuilder
130   */
131  public ByteArrayBuilder appendLong(long l)
132  {
133    builder.appendLong(l);
134    return this;
135  }
136
137  /**
138   * Append an int to this ByteArrayBuilder by converting it to a String then
139   * encoding that string to a UTF-8 byte array.
140   *
141   * @param i
142   *          the int to append.
143   * @return this ByteArrayBuilder
144   */
145  public ByteArrayBuilder appendIntUTF8(int i)
146  {
147    return appendString(Integer.toString(i));
148  }
149
150  /**
151   * Append a long to this ByteArrayBuilder by converting it to a String then
152   * encoding that string to a UTF-8 byte array.
153   *
154   * @param l
155   *          the long to append.
156   * @return this ByteArrayBuilder
157   */
158  public ByteArrayBuilder appendLongUTF8(long l)
159  {
160    return appendString(Long.toString(l));
161  }
162
163  /**
164   * Append a Collection of Strings to this ByteArrayBuilder.
165   *
166   * @param col
167   *          the Collection of Strings to append.
168   * @return this ByteArrayBuilder
169   */
170  public ByteArrayBuilder appendStrings(Collection<String> col)
171  {
172    //appendInt() would have been safer, but byte is compatible with legacy code
173    appendByte(col.size());
174    for (String s : col)
175    {
176      appendString(s);
177    }
178    return this;
179  }
180
181  /**
182   * Append a String with a zero separator to this ByteArrayBuilder,
183   * or only the zero separator if the string is null
184   * or if the string length is zero.
185   *
186   * @param s
187   *          the String to append. Can be null.
188   * @return this ByteArrayBuilder
189   */
190  public ByteArrayBuilder appendString(String s)
191  {
192    try
193    {
194      if (s != null && s.length() > 0)
195      {
196        appendByteArray(s.getBytes("UTF-8"));
197      }
198      return appendZeroSeparator();
199    }
200    catch (UnsupportedEncodingException e)
201    {
202      throw new RuntimeException("Should never happen", e);
203    }
204  }
205
206  /**
207   * Append a CSN to this ByteArrayBuilder.
208   *
209   * @param csn
210   *          the CSN to append.
211   * @return this ByteArrayBuilder
212   */
213  public ByteArrayBuilder appendCSN(CSN csn)
214  {
215    csn.toByteString(builder);
216    return this;
217  }
218
219  /**
220   * Append a CSN to this ByteArrayBuilder by converting it to a String then
221   * encoding that string to a UTF-8 byte array.
222   *
223   * @param csn
224   *          the CSN to append.
225   * @return this ByteArrayBuilder
226   */
227  public ByteArrayBuilder appendCSNUTF8(CSN csn)
228  {
229    appendString(csn.toString());
230    return this;
231  }
232
233  /**
234   * Append a DN to this ByteArrayBuilder by converting it to a String then
235   * encoding that string to a UTF-8 byte array.
236   *
237   * @param dn
238   *          the DN to append.
239   * @return this ByteArrayBuilder
240   */
241  public ByteArrayBuilder appendDN(DN dn)
242  {
243    appendString(dn.toString());
244    return this;
245  }
246
247  /**
248   * Append all the bytes from the byte array to this ByteArrayBuilder.
249   *
250   * @param bytes
251   *          the byte array to append.
252   * @return this ByteArrayBuilder
253   */
254  public ByteArrayBuilder appendByteArray(byte[] bytes)
255  {
256    builder.appendBytes(bytes);
257    return this;
258  }
259
260  /**
261   * Append all the bytes from the byte array to this ByteArrayBuilder
262   * and then append a final zero byte separator for compatibility
263   * with legacy implementations.
264   * <p>
265   * Note: the super long method name it is intentional:
266   * nobody will want to use it, which is good because nobody should.
267   *
268   * @param bytes
269   *          the byte array to append.
270   * @return this ByteArrayBuilder
271   */
272  public ByteArrayBuilder appendZeroTerminatedByteArray(byte[] bytes)
273  {
274    builder.appendBytes(bytes);
275    return appendZeroSeparator();
276  }
277
278  private ByteArrayBuilder appendZeroSeparator()
279  {
280    builder.appendByte(0);
281    return this;
282  }
283
284  /**
285   * Append the byte representation of a ServerState to this ByteArrayBuilder
286   * and then append a final zero byte separator.
287   * <p>
288   * Caution: ServerState MUST be the last field. Because ServerState can
289   * contain null character (string termination of serverId string ..) it cannot
290   * be decoded using {@link ByteArrayScanner#nextString()} like the other
291   * fields. The only way is to rely on the end of the input buffer: and that
292   * forces the ServerState to be the last field. This should be changed if we
293   * want to have more than one ServerState field.
294   * <p>
295   * Note: the super long method name it is intentional:
296   * nobody will want to use it, which is good because nobody should.
297   *
298   * @param serverState
299   *          the ServerState to append.
300   * @return this ByteArrayBuilder
301   * @see ByteArrayScanner#nextServerStateMustComeLast()
302   */
303  public ByteArrayBuilder appendServerStateMustComeLast(ServerState serverState)
304  {
305    final Map<Integer, CSN> serverIdToCSN = serverState.getServerIdToCSNMap();
306    for (Entry<Integer, CSN> entry : serverIdToCSN.entrySet())
307    {
308      // FIXME JNR: why append the serverId in addition to the CSN
309      // since the CSN already contains the serverId?
310      appendIntUTF8(entry.getKey()); // serverId
311      appendCSNUTF8(entry.getValue()); // CSN
312    }
313    return appendZeroSeparator(); // stupid legacy zero separator
314  }
315
316  /**
317   * Returns a new ASN1Writer that will append bytes to this ByteArrayBuilder.
318   *
319   * @return a new ASN1Writer that will append bytes to this ByteArrayBuilder.
320   */
321  public ASN1Writer getASN1Writer()
322  {
323    return ASN1.getWriter(builder);
324  }
325
326  /**
327   * Converts the content of this ByteStringBuilder to a byte array.
328   *
329   * @return the content of this ByteStringBuilder converted to a byte array.
330   */
331  public byte[] toByteArray()
332  {
333    return builder.toByteArray();
334  }
335
336  /** {@inheritDoc} */
337  @Override
338  public String toString()
339  {
340    return builder.toString();
341  }
342
343  /**
344   * Helper method that returns the number of bytes that would be used by the
345   * boolean fields when appended to a ByteArrayBuilder.
346   *
347   * @param nbFields
348   *          the number of boolean fields that will be appended to a
349   *          ByteArrayBuilder
350   * @return the number of bytes occupied by the appended boolean fields.
351   */
352  public static int booleans(int nbFields)
353  {
354    return nbFields;
355  }
356
357  /**
358   * Helper method that returns the number of bytes that would be used by the
359   * byte fields when appended to a ByteArrayBuilder.
360   *
361   * @param nbFields
362   *          the number of byte fields that will be appended to a
363   *          ByteArrayBuilder
364   * @return the number of bytes occupied by the appended byte fields.
365   */
366  public static int bytes(int nbFields)
367  {
368    return nbFields;
369  }
370
371  /**
372   * Helper method that returns the number of bytes that would be used by the
373   * short fields when appended to a ByteArrayBuilder.
374   *
375   * @param nbFields
376   *          the number of short fields that will be appended to a
377   *          ByteArrayBuilder
378   * @return the number of bytes occupied by the appended short fields.
379   */
380  public static int shorts(int nbFields)
381  {
382    return 2 * nbFields;
383  }
384
385  /**
386   * Helper method that returns the number of bytes that would be used by the
387   * int fields when appended to a ByteArrayBuilder.
388   *
389   * @param nbFields
390   *          the number of int fields that will be appended to a
391   *          ByteArrayBuilder
392   * @return the number of bytes occupied by the appended int fields.
393   */
394  public static int ints(int nbFields)
395  {
396    return 4 * nbFields;
397  }
398
399  /**
400   * Helper method that returns the number of bytes that would be used by the
401   * long fields when appended to a ByteArrayBuilder.
402   *
403   * @param nbFields
404   *          the number of long fields that will be appended to a
405   *          ByteArrayBuilder
406   * @return the number of bytes occupied by the appended long fields.
407   */
408  public static int longs(int nbFields)
409  {
410    return 8 * nbFields;
411  }
412
413  /**
414   * Helper method that returns the number of bytes that would be used by the
415   * CSN fields when appended to a ByteArrayBuilder.
416   *
417   * @param nbFields
418   *          the number of CSN fields that will be appended to a
419   *          ByteArrayBuilder
420   * @return the number of bytes occupied by the appended CSN fields.
421   */
422  public static int csns(int nbFields)
423  {
424    return CSN.BYTE_ENCODING_LENGTH * nbFields;
425  }
426
427  /**
428   * Helper method that returns the number of bytes that would be used by the
429   * CSN fields encoded as a UTF8 string when appended to a ByteArrayBuilder.
430   *
431   * @param nbFields
432   *          the number of CSN fields that will be appended to a
433   *          ByteArrayBuilder
434   * @return the number of bytes occupied by the appended legacy-encoded CSN
435   *         fields.
436   */
437  public static int csnsUTF8(int nbFields)
438  {
439    return CSN.STRING_ENCODING_LENGTH * nbFields + 1 /* null byte */;
440  }
441}