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