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