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}