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