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