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}