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