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 2006-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.replication.protocol; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.zip.DataFormatException; 023 024import org.forgerock.opendj.io.ASN1; 025import org.forgerock.opendj.ldap.DecodeException; 026import org.forgerock.opendj.io.ASN1Reader; 027import org.forgerock.opendj.io.ASN1Writer; 028import org.opends.server.protocols.internal.InternalClientConnection; 029import org.opends.server.protocols.ldap.LDAPAttribute; 030import org.opends.server.replication.common.AssuredMode; 031import org.opends.server.replication.common.CSN; 032import org.opends.server.types.*; 033import org.forgerock.opendj.ldap.ByteString; 034import org.forgerock.opendj.ldap.ByteStringBuilder; 035import org.forgerock.opendj.ldap.ByteSequenceReader; 036import org.forgerock.opendj.ldap.DN; 037import org.opends.server.types.operation.*; 038 039/** 040 * Abstract class that must be extended to define a message 041 * used for sending Updates between servers. 042 */ 043public abstract class LDAPUpdateMsg extends UpdateMsg 044{ 045 /** 046 * The DN on which the update was originally done. 047 */ 048 protected DN dn; 049 050 /** 051 * The entryUUID of the entry that was updated. 052 */ 053 protected String entryUUID; 054 055 /** 056 * Encoded form of the LDAPUpdateMsg. 057 */ 058 protected byte[] bytes; 059 060 /** 061 * Encoded form of entry attributes. 062 */ 063 protected byte[] encodedEclIncludes = new byte[0]; 064 065 /** 066 * Creates a new UpdateMsg. 067 */ 068 protected LDAPUpdateMsg() 069 { 070 } 071 072 /** 073 * Creates a new UpdateMsg with the given information. 074 * 075 * @param ctx The replication Context of the operation for which the 076 * update message must be created,. 077 * @param dn The DN of the entry on which the change 078 * that caused the creation of this object happened 079 */ 080 LDAPUpdateMsg(OperationContext ctx, DN dn) 081 { 082 this.protocolVersion = ProtocolVersion.getCurrentVersion(); 083 this.csn = ctx.getCSN(); 084 this.entryUUID = ctx.getEntryUUID(); 085 this.dn = dn; 086 } 087 088 /** 089 * Creates a new UpdateMessage with the given information. 090 * 091 * @param csn The CSN of the operation for which the 092 * UpdateMessage is created. 093 * @param entryUUID The Unique identifier of the entry that is updated 094 * by the operation for which the UpdateMessage is created. 095 * @param dn The DN of the entry on which the change 096 * that caused the creation of this object happened 097 */ 098 LDAPUpdateMsg(CSN csn, String entryUUID, DN dn) 099 { 100 this.protocolVersion = ProtocolVersion.getCurrentVersion(); 101 this.csn = csn; 102 this.entryUUID = entryUUID; 103 this.dn = dn; 104 } 105 106 /** 107 * Generates an Update message with the provided information. 108 * 109 * @param op The operation for which the message must be created. 110 * @return The generated message. 111 */ 112 public static LDAPUpdateMsg generateMsg(PostOperationOperation op) 113 { 114 switch (op.getOperationType()) 115 { 116 case MODIFY : 117 return new ModifyMsg((PostOperationModifyOperation) op); 118 case ADD: 119 return new AddMsg((PostOperationAddOperation) op); 120 case DELETE : 121 return new DeleteMsg((PostOperationDeleteOperation) op); 122 case MODIFY_DN : 123 return new ModifyDNMsg( (PostOperationModifyDNOperation) op); 124 default: 125 return null; 126 } 127 } 128 129 /** 130 * Get the DN on which the operation happened. 131 * 132 * @return The DN on which the operations happened. 133 */ 134 public DN getDN() 135 { 136 return dn; 137 } 138 139 /** 140 * Set the DN. 141 * @param dn The dn that must now be used for this message. 142 */ 143 public void setDN(DN dn) 144 { 145 this.dn = dn; 146 } 147 148 /** 149 * Get the entryUUID of the entry on which the operation happened. 150 * 151 * @return The entryUUID of the entry on which the operation happened. 152 */ 153 public String getEntryUUID() 154 { 155 return entryUUID; 156 } 157 158 /** 159 * Create and Operation from the message. 160 * 161 * @param conn connection to use when creating the message 162 * @return the created Operation 163 * @throws LDAPException In case of LDAP decoding exception. 164 * @throws IOException In case of ASN1 decoding exception. 165 * @throws DataFormatException In case of bad msg format. 166 */ 167 public Operation createOperation(InternalClientConnection conn) 168 throws LDAPException, IOException, DataFormatException 169 { 170 return createOperation(conn, dn); 171 } 172 173 174 /** 175 * Create and Operation from the message using the provided DN. 176 * 177 * @param conn connection to use when creating the message. 178 * @param newDN the DN to use when creating the operation. 179 * @return the created Operation. 180 * @throws LDAPException In case of LDAP decoding exception. 181 * @throws IOException In case of ASN1 decoding exception. 182 * @throws DataFormatException In case of bad msg format. 183 */ 184 public abstract Operation createOperation(InternalClientConnection conn, 185 DN newDN) throws LDAPException, IOException, DataFormatException; 186 187 188 // ============ 189 // Msg encoding 190 // ============ 191 192 /** 193 * Do all the work necessary for the encoding. 194 * 195 * This is useful in case when one wants to perform this outside 196 * of a synchronized portion of code. 197 * 198 * This method is not synchronized and therefore not MT safe. 199 */ 200 public void encode() 201 { 202 bytes = getBytes(ProtocolVersion.getCurrentVersion()); 203 } 204 205 /** {@inheritDoc} */ 206 @Override 207 public ByteArrayBuilder encodeHeader(byte msgType, short protocolVersion) 208 { 209 /* The message header is stored in the form : 210 * <operation type><protocol version><CSN><dn><entryuuid><assured> 211 * <assured mode> <safe data level> 212 */ 213 final ByteArrayBuilder builder = new ByteArrayBuilder(); 214 builder.appendByte(msgType); 215 builder.appendByte(protocolVersion); 216 builder.appendCSNUTF8(csn); 217 builder.appendDN(dn); 218 builder.appendString(entryUUID); 219 builder.appendBoolean(assuredFlag); 220 builder.appendByte(assuredMode.getValue()); 221 builder.appendByte(safeDataLevel); 222 return builder; 223 } 224 225 /** 226 * Encode the common header for all the UpdateMessage. This uses the version 227 * 1 of the replication protocol (used for compatibility purpose). 228 * 229 * @param msgType the type of UpdateMessage to encode. 230 * @return a byte array builder containing the common header 231 */ 232 ByteArrayBuilder encodeHeader_V1(byte msgType) 233 { 234 /* The message header is stored in the form : 235 * <operation type><CSN><dn><assured><entryuuid><change> 236 */ 237 final ByteArrayBuilder builder = new ByteArrayBuilder(); 238 builder.appendByte(msgType); 239 builder.appendCSNUTF8(csn); 240 builder.appendBoolean(assuredFlag); 241 builder.appendDN(dn); 242 builder.appendString(entryUUID); 243 return builder; 244 } 245 246 /** {@inheritDoc} */ 247 @Override 248 public byte[] getBytes(short protocolVersion) 249 { 250 if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1) 251 { 252 return getBytes_V1(); 253 } 254 else if (protocolVersion <= ProtocolVersion.REPLICATION_PROTOCOL_V3) 255 { 256 return getBytes_V23(); 257 } 258 else 259 { 260 // Encode in the current protocol version 261 if (bytes == null) 262 { 263 // this is the current version of the protocol 264 bytes = getBytes_V45(protocolVersion); 265 } 266 return bytes; 267 } 268 } 269 270 /** 271 * Get the byte array representation of this message. This uses the version 272 * 1 of the replication protocol (used for compatibility purpose). 273 * 274 * @return The byte array representation of this message. 275 */ 276 protected abstract byte[] getBytes_V1(); 277 278 /** 279 * Get the byte array representation of this message. This uses the version 280 * 2 of the replication protocol (used for compatibility purpose). 281 * 282 * @return The byte array representation of this message. 283 */ 284 protected abstract byte[] getBytes_V23(); 285 286 /** 287 * Get the byte array representation of this message. This uses the provided 288 * version number which must be version 4 or newer. 289 * 290 * @param protocolVersion the actual protocol version to encode into 291 * @return The byte array representation of this Message. 292 */ 293 protected abstract byte[] getBytes_V45(short protocolVersion); 294 295 /** 296 * Encode a list of attributes. 297 */ 298 private static byte[] encodeAttributes(Collection<Attribute> attributes) 299 { 300 if (attributes==null) 301 { 302 return new byte[0]; 303 } 304 try 305 { 306 ByteStringBuilder byteBuilder = new ByteStringBuilder(); 307 ASN1Writer writer = ASN1.getWriter(byteBuilder); 308 for (Attribute a : attributes) 309 { 310 new LDAPAttribute(a).write(writer); 311 } 312 return byteBuilder.toByteArray(); 313 } 314 catch (Exception e) 315 { 316 return null; 317 } 318 } 319 320 // ============ 321 // Msg decoding 322 // ============ 323 324 /** 325 * Decode the Header part of this Update message, and check its type. 326 * 327 * @param scanner the encoded form of the UpdateMsg. 328 * @param allowedTypes The allowed types of this Update Message. 329 * @throws DataFormatException if the encodedMsg does not contain a valid 330 * common header. 331 */ 332 void decodeHeader(ByteArrayScanner scanner, byte... allowedTypes) 333 throws DataFormatException 334 { 335 final byte msgType = scanner.nextByte(); 336 if (!isTypeAllowed(msgType, allowedTypes)) 337 { 338 throw new DataFormatException("byte[] is not a valid update msg: " 339 + msgType); 340 } 341 342 if (msgType == MSG_TYPE_ADD_V1 343 || msgType == MSG_TYPE_DELETE_V1 344 || msgType == MSG_TYPE_MODIFYDN_V1 345 || msgType == MSG_TYPE_MODIFY_V1) 346 { 347 /* 348 * For older protocol versions, decode the matching version header instead 349 */ 350 // Force version to V1 (other new parameters take their default values 351 // (assured stuff...)) 352 protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1; 353 csn = scanner.nextCSNUTF8(); 354 assuredFlag = scanner.nextBoolean(); 355 dn = scanner.nextDN(); 356 entryUUID = scanner.nextString(); 357 } 358 else 359 { 360 protocolVersion = scanner.nextByte(); 361 csn = scanner.nextCSNUTF8(); 362 dn = scanner.nextDN(); 363 entryUUID = scanner.nextString(); 364 assuredFlag = scanner.nextBoolean(); 365 assuredMode = AssuredMode.valueOf(scanner.nextByte()); 366 safeDataLevel = scanner.nextByte(); 367 } 368 } 369 370 private boolean isTypeAllowed(final byte msgType, byte... allowedTypes) 371 { 372 for (byte allowedType : allowedTypes) 373 { 374 if (msgType == allowedType) 375 { 376 return true; 377 } 378 } 379 return false; 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public abstract int size(); 385 386 /** 387 * Return the number of bytes used by the header. 388 * @return The number of bytes used by the header. 389 */ 390 protected int headerSize() 391 { 392 return 100; // 100 let's assume header size is 100 393 } 394 395 /** 396 * Set a provided list of entry attributes. 397 * @param entryAttrs The provided list of entry attributes. 398 */ 399 public void setEclIncludes(Collection<Attribute> entryAttrs) 400 { 401 this.encodedEclIncludes = encodeAttributes(entryAttrs); 402 } 403 404 /** 405 * Returns the list of entry attributes. 406 * @return The list of entry attributes. 407 */ 408 public ArrayList<RawAttribute> getEclIncludes() 409 { 410 try 411 { 412 return decodeRawAttributes(this.encodedEclIncludes); 413 } 414 catch(Exception e) 415 { 416 return null; 417 } 418 } 419 420 /** 421 * Decode a provided byte array as a list of RawAttribute. 422 * @param in The provided byte array. 423 * @return The list of RawAttribute objects. 424 * @throws LDAPException when it occurs. 425 * @throws DecodeException when it occurs. 426 */ 427 ArrayList<RawAttribute> decodeRawAttributes(byte[] in) 428 throws LDAPException, DecodeException 429 { 430 ArrayList<RawAttribute> rattr = new ArrayList<>(); 431 try 432 { 433 ByteSequenceReader reader = 434 ByteString.wrap(in).asReader(); 435 ASN1Reader asn1Reader = ASN1.getReader(reader); 436 // loop on attributes 437 while(asn1Reader.hasNextElement()) 438 { 439 rattr.add(LDAPAttribute.decode(asn1Reader)); 440 } 441 return rattr; 442 } 443 catch(Exception e) 444 { 445 return null; 446 } 447 } 448 449 /** 450 * Decode a provided byte array as a list of Attribute. 451 * @param in The provided byte array. 452 * @return The list of Attribute objects. 453 * @throws LDAPException when it occurs. 454 * @throws DecodeException when it occurs. 455 */ 456 ArrayList<Attribute> decodeAttributes(byte[] in) 457 throws LDAPException, DecodeException 458 { 459 ArrayList<Attribute> lattr = new ArrayList<>(); 460 try 461 { 462 ByteSequenceReader reader = 463 ByteString.wrap(in).asReader(); 464 ASN1Reader asn1Reader = ASN1.getReader(reader); 465 // loop on attributes 466 while(asn1Reader.hasNextElement()) 467 { 468 lattr.add(LDAPAttribute.decode(asn1Reader).toAttribute()); 469 } 470 return lattr; 471 } 472 catch(Exception e) 473 { 474 return null; 475 } 476 } 477}