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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.replication.protocol;
018
019import static org.opends.server.replication.protocol.OperationContext.*;
020import static org.opends.server.replication.protocol.ProtocolVersion.*;
021
022import java.io.IOException;
023import java.util.List;
024import java.util.zip.DataFormatException;
025
026import org.forgerock.i18n.LocalizedIllegalArgumentException;
027import org.forgerock.opendj.ldap.ByteString;
028import org.forgerock.opendj.ldap.DN;
029import org.forgerock.opendj.ldap.RDN;
030import org.opends.server.core.ModifyDNOperation;
031import org.opends.server.core.ModifyDNOperationBasis;
032import org.opends.server.protocols.internal.InternalClientConnection;
033import org.opends.server.replication.common.CSN;
034import org.opends.server.types.LDAPException;
035import org.opends.server.types.Modification;
036import org.opends.server.types.operation.PostOperationModifyDNOperation;
037
038/** Message used to send Modify DN information. */
039public class ModifyDNMsg extends ModifyCommonMsg
040{
041  private String newRDN;
042  private String newSuperior;
043  private boolean deleteOldRdn;
044  private String newSuperiorEntryUUID;
045
046  /**
047   * Construct a new Modify DN message.
048   *
049   * @param operation The operation to use for building the message
050   */
051  public ModifyDNMsg(PostOperationModifyDNOperation operation)
052  {
053    super((OperationContext) operation.getAttachment(SYNCHROCONTEXT),
054        operation.getEntryDN());
055
056    encodedMods = encodeMods(operation.getModifications());
057
058    ModifyDnContext ctx =
059      (ModifyDnContext) operation.getAttachment(SYNCHROCONTEXT);
060    newSuperiorEntryUUID = ctx.getNewSuperiorEntryUUID();
061
062    deleteOldRdn = operation.deleteOldRDN();
063    final ByteString rawNewSuperior = operation.getRawNewSuperior();
064    newSuperior = rawNewSuperior != null ? rawNewSuperior.toString() : null;
065    newRDN = operation.getRawNewRDN().toString();
066  }
067
068  /**
069   * Construct a new Modify DN message (no mods).
070   * Note: Keep this constructor version to support already written tests, not
071   * using mods.
072   *
073   * @param dn The dn to use for building the message.
074   * @param csn The CSN to use for building the message.
075   * @param entryUUID          The unique id to use for building the message.
076   * @param newSuperiorEntryUUID The new parent unique id to use for building
077   *                     the message.
078   * @param deleteOldRdn boolean indicating if old rdn must be deleted to use
079   *                     for building the message.
080   * @param newSuperior  The new Superior entry to use for building the message.
081   * @param newRDN       The new Rdn to use for building the message.
082   */
083  public ModifyDNMsg(DN dn, CSN csn, String entryUUID,
084                     String newSuperiorEntryUUID, boolean deleteOldRdn,
085                     String newSuperior, String newRDN)
086  {
087    super(new ModifyDnContext(csn, entryUUID, newSuperiorEntryUUID), dn);
088
089    this.newSuperiorEntryUUID = newSuperiorEntryUUID;
090    this.deleteOldRdn = deleteOldRdn;
091    this.newSuperior = newSuperior;
092    this.newRDN = newRDN;
093  }
094
095  /**
096   * Construct a new Modify DN message (with mods).
097   *
098   * @param dn The dn to use for building the message.
099   * @param csn The CSNto use for building the message.
100   * @param entryUUID The unique id to use for building the message.
101   * @param newSuperiorEntryUUID The new parent unique id to use for building
102   *                     the message.
103   * @param deleteOldRdn boolean indicating if old rdn must be deleted to use
104   *                     for building the message.
105   * @param newSuperior  The new Superior entry to use for building the message.
106   * @param newRDN       The new Rdn to use for building the message.
107   * @param mods         The mod of the operation.
108   */
109  public ModifyDNMsg(DN dn, CSN csn, String entryUUID,
110      String newSuperiorEntryUUID, boolean deleteOldRdn, String newSuperior,
111      String newRDN, List<Modification> mods)
112  {
113    this(dn, csn, entryUUID, newSuperiorEntryUUID, deleteOldRdn,
114        newSuperior, newRDN);
115    this.encodedMods = encodeMods(mods);
116  }
117
118  /**
119   * Creates a new ModifyDN message from a byte[].
120   *
121   * @param in The byte[] from which the operation must be read.
122   * @throws DataFormatException The input byte[] is not a valid ModifyDNMsg.
123   */
124  ModifyDNMsg(byte[] in) throws DataFormatException
125  {
126    final ByteArrayScanner scanner = new ByteArrayScanner(in);
127    decodeHeader(scanner, MSG_TYPE_MODIFYDN, MSG_TYPE_MODIFYDN_V1);
128
129    if (protocolVersion <= 3)
130    {
131      decodeBody_V123(scanner, in[0]);
132    }
133    else
134    {
135      decodeBody_V4(scanner);
136    }
137
138    if (protocolVersion==ProtocolVersion.getCurrentVersion())
139    {
140      bytes = in;
141    }
142  }
143
144  @Override
145  public ModifyDNOperation createOperation(InternalClientConnection connection,
146      DN newDN) throws LDAPException, IOException
147  {
148    ModifyDNOperation moddn =  new ModifyDNOperationBasis(connection,
149        InternalClientConnection.nextOperationID(),
150        InternalClientConnection.nextMessageID(), null,
151        ByteString.valueOfUtf8(newDN.toString()),
152        ByteString.valueOfUtf8(newRDN),
153        deleteOldRdn,
154        (newSuperior == null ? null : ByteString.valueOfUtf8(newSuperior)));
155
156    for (Modification mod : decodeMods(encodedMods))
157    {
158      moddn.addModification(mod);
159    }
160
161    ModifyDnContext ctx = new ModifyDnContext(getCSN(), getEntryUUID(),
162        newSuperiorEntryUUID);
163    moddn.setAttachment(SYNCHROCONTEXT, ctx);
164    return moddn;
165  }
166
167  // ============
168  // Msg Encoding
169  // ============
170
171  @Override
172  public byte[] getBytes_V1()
173  {
174    final ByteArrayBuilder builder = encodeHeader_V1(MSG_TYPE_MODIFYDN_V1);
175    builder.appendString(newRDN);
176    builder.appendString(newSuperior);
177    builder.appendString(newSuperiorEntryUUID);
178    builder.appendBoolean(deleteOldRdn);
179    return builder.toByteArray();
180  }
181
182  @Override
183  public byte[] getBytes_V23()
184  {
185    final ByteArrayBuilder builder = encodeHeader(MSG_TYPE_MODIFYDN, REPLICATION_PROTOCOL_V3);
186    builder.appendString(newRDN);
187    builder.appendString(newSuperior);
188    builder.appendString(newSuperiorEntryUUID);
189    builder.appendBoolean(deleteOldRdn);
190    builder.appendZeroTerminatedByteArray(encodedMods);
191    return builder.toByteArray();
192  }
193
194  @Override
195  public byte[] getBytes_V45(short protocolVersion)
196  {
197    final ByteArrayBuilder builder = encodeHeader(MSG_TYPE_MODIFYDN, protocolVersion);
198    builder.appendString(newRDN);
199    builder.appendString(newSuperior);
200    builder.appendString(newSuperiorEntryUUID);
201    builder.appendBoolean(deleteOldRdn);
202    builder.appendIntUTF8(encodedMods.length);
203    builder.appendZeroTerminatedByteArray(encodedMods);
204    builder.appendIntUTF8(encodedEclIncludes.length);
205    builder.appendZeroTerminatedByteArray(encodedEclIncludes);
206    return builder.toByteArray();
207  }
208
209  // ============
210  // Msg decoding
211  // ============
212
213  private void decodeBody_V123(ByteArrayScanner scanner, byte msgType)
214      throws DataFormatException
215  {
216    newRDN = scanner.nextString();
217    newSuperior = scanner.nextString();
218    newSuperiorEntryUUID = scanner.nextString();
219    deleteOldRdn = scanner.nextBoolean();
220
221    // For easiness (no additional method), simply compare PDU type to
222    // know if we have to read the mods of V2
223    if (msgType == MSG_TYPE_MODIFYDN)
224    {
225      encodedMods = scanner.remainingBytesZeroTerminated();
226    }
227  }
228
229  private void decodeBody_V4(ByteArrayScanner scanner)
230      throws DataFormatException
231  {
232    newRDN = scanner.nextString();
233    newSuperior = scanner.nextString();
234    newSuperiorEntryUUID = scanner.nextString();
235    deleteOldRdn = scanner.nextBoolean();
236
237    final int modsLen = scanner.nextIntUTF8();
238    encodedMods = scanner.nextByteArray(modsLen);
239    scanner.skipZeroSeparator();
240
241    final int eclAttrLen = scanner.nextIntUTF8();
242    encodedEclIncludes = scanner.nextByteArray(eclAttrLen);
243  }
244
245  @Override
246  public String toString()
247  {
248    if (protocolVersion >= REPLICATION_PROTOCOL_V1)
249    {
250      return "ModifyDNMsg content: " +
251        " protocolVersion: " + protocolVersion +
252        " dn: " + dn +
253        " csn: " + csn +
254        " uniqueId: " + entryUUID +
255        " newRDN: " + newRDN +
256        " newSuperior: " + newSuperior +
257        " deleteOldRdn: " + deleteOldRdn +
258        " assuredFlag: " + assuredFlag +
259        (protocolVersion >= REPLICATION_PROTOCOL_V2 ?
260          " assuredMode: " + assuredMode +
261          " safeDataLevel: " + safeDataLevel
262          : "");
263    }
264    return "!!! Unknown version: " + protocolVersion + "!!!";
265  }
266
267  /**
268   * Set the new superior.
269   * @param string the new superior.
270   */
271  public void setNewSuperior(String string)
272  {
273    newSuperior = string;
274  }
275
276  /**
277   * Get the new superior.
278   *
279   * @return The new superior.
280   */
281  public String getNewSuperior()
282  {
283    return newSuperior;
284  }
285
286  /**
287   * Get the new superior id.
288   *
289   * @return The new superior id.
290   */
291  public String getNewSuperiorEntryUUID()
292  {
293    return newSuperiorEntryUUID;
294  }
295
296  /**
297   * Get the delete old rdn option.
298   *
299   * @return The delete old rdn option.
300   */
301  public boolean deleteOldRdn()
302  {
303    return deleteOldRdn;
304  }
305
306  /**
307   * Set the new superior id.
308   *
309   * @param newSup The new superior id.
310   */
311  public void setNewSuperiorEntryUUID(String newSup)
312  {
313    newSuperiorEntryUUID = newSup;
314  }
315
316  /**
317   * Set the delete old rdn option.
318   *
319   * @param delete The delete old rdn option.
320   */
321  public void  setDeleteOldRdn(boolean delete)
322  {
323    deleteOldRdn = delete;
324  }
325
326  /**
327   * Get the delete old rdn option.
328   * @return true if delete old rdn option
329   */
330  public boolean getDeleteOldRdn()
331  {
332    return deleteOldRdn;
333  }
334
335  /**
336   * Get the new RDN of this operation.
337   *
338   * @return The new RDN of this operation.
339   */
340  public String getNewRDN()
341  {
342    return newRDN;
343  }
344
345  /**
346   * Set the new RDN of this operation.
347   * @param newRDN the new RDN of this operation.
348   */
349  public void setNewRDN(String newRDN)
350  {
351    this.newRDN = newRDN;
352  }
353
354  /**
355   * Computes and return the new DN that the entry should have after this operation.
356   *
357   * @return the newDN.
358   * @throws LocalizedIllegalArgumentException
359   *           in case of decoding problems.
360   */
361  private DN computeNewDN() throws LocalizedIllegalArgumentException
362  {
363    if (newSuperior != null)
364    {
365      return DN.valueOf(newRDN + "," + newSuperior);
366    }
367    final DN parentDn = getDN().parent();
368    return parentDn.child(RDN.valueOf(newRDN));
369  }
370
371  /**
372   * Check if this MSG will change the DN of the target entry to be
373   * the same as the dn given as a parameter.
374   * @param targetDn the DN to use when checking if this MSG will change
375   *                 the DN of the entry to a given DN.
376   * @return A boolean indicating if the modify DN MSG will change the DN of
377   *         the target entry to be the same as the dn given as a parameter.
378   */
379  public boolean newDNIsParent(DN targetDn)
380  {
381    try
382    {
383      DN newDN = computeNewDN();
384      return newDN.isSuperiorOrEqualTo(targetDn);
385    }
386    catch (LocalizedIllegalArgumentException e)
387    {
388      // The DN was not a correct DN, and therefore does not a parent of the
389      // DN given as a parameter.
390      return false;
391    }
392  }
393
394  /**
395   * Check if the new dn of this ModifyDNMsg is the same as the targetDN
396   * given in parameter.
397   *
398   * @param targetDN The targetDN to use to check for equality.
399   *
400   * @return A boolean indicating if the targetDN if the same as the new DN of
401   *         the ModifyDNMsg.
402   */
403  public boolean newDNIsEqual(DN targetDN)
404  {
405    try
406    {
407      DN newDN = computeNewDN();
408      return newDN.equals(targetDN);
409    }
410    catch (LocalizedIllegalArgumentException e)
411    {
412      // The DN was not a correct DN, and therefore does not match the
413      // DN given as a parameter.
414      return false;
415    }
416  }
417
418  /**
419   * Check if the new parent of the modifyDNMsg is the same as the targetDN
420   * given in parameter.
421   *
422   * @param targetDN the targetDN to use when checking equality.
423   *
424   * @return A boolean indicating if the new parent of the modifyDNMsg is the
425   *         same as the targetDN.
426   */
427  public boolean newParentIsEqual(DN targetDN)
428  {
429    try
430    {
431      DN newSuperiorDN = newSuperior != null ? DN.valueOf(newSuperior) : DN.rootDN();
432      return newSuperiorDN.equals(targetDN);
433    }
434    catch (LocalizedIllegalArgumentException e)
435    {
436      // The newsuperior was not a correct DN, and therefore does not match the
437      // DN given as a parameter.
438      return false;
439    }
440  }
441
442  @Override
443  public int size()
444  {
445    return encodedMods.length + newRDN.length() +
446      encodedEclIncludes.length + headerSize();
447  }
448}