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.util.Collection;
020import java.util.List;
021import java.util.Map;
022import java.util.zip.DataFormatException;
023
024import org.forgerock.opendj.io.ASN1;
025import org.forgerock.opendj.io.ASN1Writer;
026import org.forgerock.opendj.ldap.ByteString;
027import org.forgerock.opendj.ldap.ByteStringBuilder;
028import org.forgerock.opendj.ldap.DN;
029import org.forgerock.opendj.ldap.DecodeException;
030import org.opends.server.core.AddOperation;
031import org.opends.server.core.AddOperationBasis;
032import org.opends.server.core.DirectoryServer;
033import org.opends.server.protocols.internal.InternalClientConnection;
034import org.opends.server.protocols.ldap.LDAPAttribute;
035import org.opends.server.replication.common.CSN;
036import org.opends.server.replication.plugin.EntryHistorical;
037import org.forgerock.opendj.ldap.schema.AttributeType;
038import org.opends.server.types.*;
039import org.opends.server.types.operation.PostOperationAddOperation;
040
041import static org.opends.server.replication.protocol.OperationContext.*;
042
043/**
044 * This class is used to exchange Add operation between LDAP servers
045 * and replication servers.
046 */
047public class AddMsg extends LDAPUpdateMsg
048{
049  /** Attributes are managed encoded. */
050  private byte[] encodedAttributes;
051
052  /** Parent is managed decoded. */
053  private String parentEntryUUID;
054
055  /**
056   * Creates a new AddMessage.
057   * @param op the operation to use when creating the message
058   */
059  AddMsg(PostOperationAddOperation op)
060  {
061    super((AddContext) op.getAttachment(SYNCHROCONTEXT), op.getEntryDN());
062
063    AddContext ctx = (AddContext) op.getAttachment(SYNCHROCONTEXT);
064
065    // Stores parentUniqueID not encoded
066    this.parentEntryUUID = ctx.getParentEntryUUID();
067
068    // Stores attributes encoded
069    this.encodedAttributes = encodeAttributes(op.getObjectClasses(),
070        op.getUserAttributes(), op.getOperationalAttributes());
071  }
072
073  /**
074   * Creates a new AddMessage.
075   *
076   * @param csn                   CSN of the add.
077   * @param dn                    DN of the added entry.
078   * @param entryUUID             The Unique identifier of the added entry.
079   * @param parentEntryUUID       The unique Id of the parent of the added
080   *                              entry.
081   * @param objectClasses           objectclass of the added entry.
082   * @param userAttributes        user attributes of the added entry.
083   * @param operationalAttributes operational attributes of the added entry.
084   */
085  public AddMsg(CSN csn,
086                DN dn,
087                String entryUUID,
088                String parentEntryUUID,
089                Map<ObjectClass, String> objectClasses,
090                Map<AttributeType,List<Attribute>> userAttributes,
091                Map<AttributeType,List<Attribute>> operationalAttributes)
092  {
093    super (csn, entryUUID, dn);
094
095    // Stores parentUniqueID not encoded
096    this.parentEntryUUID = parentEntryUUID;
097
098    // Stores attributes encoded
099    this.encodedAttributes = encodeAttributes(objectClasses, userAttributes,
100        operationalAttributes);
101  }
102
103
104  /**
105   * Creates a new AddMessage.
106   *
107   * @param csn CSN of the add.
108   * @param dn DN of the added entry.
109   * @param uniqueId The Unique identifier of the added entry.
110   * @param parentId The unique Id of the parent of the added entry.
111   * @param objectClass objectclass of the added entry.
112   * @param userAttributes user attributes of the added entry.
113   * @param operationalAttributes operational attributes of the added entry.
114   */
115  public AddMsg(CSN csn,
116                DN dn,
117                String uniqueId,
118                String parentId,
119                Attribute objectClass,
120                Collection<Attribute> userAttributes,
121                Collection<Attribute> operationalAttributes)
122  {
123    super (csn, uniqueId, dn);
124
125    // Stores parentUniqueID not encoded
126    this.parentEntryUUID = parentId;
127
128    // Stores attributes encoded
129    this.encodedAttributes = encodeAttributes(objectClass, userAttributes,
130        operationalAttributes);
131  }
132
133  /**
134   * Creates a new Add message from a byte[].
135   *
136   * @param in The byte[] from which the operation must be read.
137   * @throws DataFormatException The input byte[] is not a valid AddMsg
138   */
139  public AddMsg(byte[] in) throws DataFormatException
140  {
141    final ByteArrayScanner scanner = new ByteArrayScanner(in);
142    decodeHeader(scanner, MSG_TYPE_ADD, MSG_TYPE_ADD_V1);
143
144    if (protocolVersion <= 3)
145    {
146      decodeBody_V123(scanner);
147    }
148    else
149    {
150      decodeBody_V4(scanner);
151    }
152    if (protocolVersion==ProtocolVersion.getCurrentVersion())
153    {
154      bytes = in;
155    }
156  }
157
158  /** {@inheritDoc} */
159  @Override
160  public AddOperation createOperation(
161      InternalClientConnection connection, DN newDN)
162  throws LDAPException, DecodeException
163  {
164    List<RawAttribute> attr = decodeRawAttributes(encodedAttributes);
165
166    AddOperation add =  new AddOperationBasis(connection,
167        InternalClientConnection.nextOperationID(),
168        InternalClientConnection.nextMessageID(), null,
169        ByteString.valueOfUtf8(newDN.toString()), attr);
170    AddContext ctx = new AddContext(getCSN(), getEntryUUID(), parentEntryUUID);
171    add.setAttachment(SYNCHROCONTEXT, ctx);
172    return add;
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_ADD_V1);
184    builder.appendString(parentEntryUUID);
185    builder.appendByteArray(encodedAttributes);
186    return builder.toByteArray();
187  }
188
189  /** {@inheritDoc} */
190  @Override
191  public byte[] getBytes_V23()
192  {
193    final ByteArrayBuilder builder =
194        encodeHeader(MSG_TYPE_ADD, ProtocolVersion.REPLICATION_PROTOCOL_V3);
195    builder.appendString(parentEntryUUID);
196    builder.appendByteArray(encodedAttributes);
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_ADD, protocolVersion);
206    builder.appendString(parentEntryUUID);
207    builder.appendIntUTF8(encodedAttributes.length);
208    builder.appendZeroTerminatedByteArray(encodedAttributes);
209    builder.appendIntUTF8(encodedEclIncludes.length);
210    builder.appendZeroTerminatedByteArray(encodedEclIncludes);
211    return builder.toByteArray();
212  }
213
214  private byte[] encodeAttributes(
215      Map<ObjectClass, String> objectClasses,
216      Map<AttributeType, List<Attribute>> userAttributes,
217      Map<AttributeType, List<Attribute>> operationalAttributes)
218  {
219    ByteStringBuilder byteBuilder = new ByteStringBuilder();
220    ASN1Writer writer = ASN1.getWriter(byteBuilder);
221
222    try
223    {
224      //  Encode the object classes (SET OF LDAPString).
225      AttributeBuilder builder = new AttributeBuilder(
226          DirectoryServer.getObjectClassAttributeType());
227      builder.addAllStrings(objectClasses.values());
228      new LDAPAttribute(builder.toAttribute()).write(writer);
229
230      // Encode the user and operational attributes (AttributeList).
231      encodeAttributes(userAttributes, writer);
232      encodeAttributes(operationalAttributes, writer);
233    }
234    catch(Exception e)
235    {
236      // TODO: DO something
237    }
238
239    // Encode the sequence.
240    return byteBuilder.toByteArray();
241  }
242
243  private void encodeAttributes(Map<AttributeType, List<Attribute>> attributes,
244      ASN1Writer writer) throws Exception
245  {
246    for (List<Attribute> list : attributes.values())
247    {
248      for (Attribute a : list)
249      {
250        if (!a.isVirtual() && !EntryHistorical.isHistoricalAttribute(a))
251        {
252          new LDAPAttribute(a).write(writer);
253        }
254      }
255    }
256  }
257
258  private byte[] encodeAttributes(
259      Attribute objectClass,
260      Collection<Attribute> userAttributes,
261      Collection<Attribute> operationalAttributes)
262  {
263    ByteStringBuilder byteBuilder = new ByteStringBuilder();
264    ASN1Writer writer = ASN1.getWriter(byteBuilder);
265    try
266    {
267      new LDAPAttribute(objectClass).write(writer);
268
269      for (Attribute a : userAttributes)
270      {
271        new LDAPAttribute(a).write(writer);
272      }
273
274      if (operationalAttributes != null)
275      {
276        for (Attribute a : operationalAttributes)
277        {
278          new LDAPAttribute(a).write(writer);
279        }
280      }
281    }
282    catch(Exception e)
283    {
284      // Do something
285    }
286    return byteBuilder.toByteArray();
287  }
288
289  // ============
290  // Msg decoding
291  // ============
292
293  private void decodeBody_V123(ByteArrayScanner scanner)
294      throws DataFormatException
295  {
296    parentEntryUUID = scanner.nextString();
297    encodedAttributes = scanner.remainingBytes();
298  }
299
300  private void decodeBody_V4(ByteArrayScanner scanner)
301      throws DataFormatException
302  {
303    parentEntryUUID = scanner.nextString();
304
305    final int attrLen = scanner.nextIntUTF8();
306    encodedAttributes = scanner.nextByteArray(attrLen);
307    scanner.skipZeroSeparator();
308
309    final int eclAttrLen = scanner.nextIntUTF8();
310    encodedEclIncludes = scanner.nextByteArray(eclAttrLen);
311  }
312
313  /** {@inheritDoc} */
314  @Override
315  public String toString()
316  {
317    if (protocolVersion >= ProtocolVersion.REPLICATION_PROTOCOL_V1)
318    {
319      return "AddMsg content: " +
320        " protocolVersion: " + protocolVersion +
321        " dn: " + dn +
322        " csn: " + csn +
323        " uniqueId: " + entryUUID +
324        " assuredFlag: " + assuredFlag +
325        (protocolVersion >= ProtocolVersion.REPLICATION_PROTOCOL_V2 ?
326          " assuredMode: " + assuredMode +
327          " safeDataLevel: " + safeDataLevel
328          : "");
329    }
330    return "!!! Unknown version: " + protocolVersion + "!!!";
331  }
332
333  /**
334   * Add the specified attribute/attribute value in the entry contained
335   * in this AddMsg.
336   *
337   * @param name  The name of the attribute to add.
338   * @param value The value of the attribute to add.
339   * @throws DecodeException When this Msg is not valid.
340   */
341  public void addAttribute(String name, String value) throws DecodeException
342  {
343    ByteStringBuilder byteBuilder = new ByteStringBuilder();
344    byteBuilder.appendBytes(encodedAttributes);
345
346    ASN1Writer writer = ASN1.getWriter(byteBuilder);
347
348    try
349    {
350      new LDAPAttribute(name, value).write(writer);
351
352      encodedAttributes = byteBuilder.toByteArray();
353    }
354    catch(Exception e)
355    {
356      // DO SOMETHING
357    }
358  }
359
360  /**
361   * Get the attributes of this add msg.
362   * @throws LDAPException In case of LDAP decoding exception
363   * @throws DecodeException In case of ASN1 decoding exception
364   * @return the list of attributes
365   */
366  public List<Attribute> getAttributes() throws LDAPException, DecodeException
367  {
368    return decodeAttributes(encodedAttributes);
369  }
370
371  /**
372   * Set the parent unique id of this add msg.
373   *
374   * @param entryUUID the parent unique id.
375   */
376  public void setParentEntryUUID(String entryUUID)
377  {
378    parentEntryUUID = entryUUID;
379  }
380
381  /**
382   * Get the parent unique id of this add msg.
383   * @return the parent unique id.
384   */
385  public String getParentEntryUUID()
386  {
387    return parentEntryUUID;
388  }
389
390  /** {@inheritDoc} */
391  @Override
392  public int size()
393  {
394    return encodedAttributes.length + encodedEclIncludes.length + headerSize();
395  }
396
397}