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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.replication.protocol;
028    
029    import java.io.Serializable;
030    import java.io.UnsupportedEncodingException;
031    import java.util.zip.DataFormatException;
032    
033    import org.opends.server.protocols.asn1.ASN1Exception;
034    import org.opends.server.protocols.internal.InternalClientConnection;
035    import org.opends.server.replication.common.ChangeNumber;
036    import org.opends.server.types.AbstractOperation;
037    import org.opends.server.types.LDAPException;
038    import org.opends.server.types.operation.PostOperationAddOperation;
039    import org.opends.server.types.operation.PostOperationDeleteOperation;
040    import org.opends.server.types.operation.PostOperationModifyDNOperation;
041    import org.opends.server.types.operation.PostOperationModifyOperation;
042    import org.opends.server.types.operation.PostOperationOperation;
043    
044    /**
045     * Abstract class that must be extended to define a message
046     * used for sending Updates between servers.
047     */
048    public abstract class UpdateMessage extends ReplicationMessage
049                                        implements Serializable,
050                                                   Comparable<UpdateMessage>
051    {
052      /**
053       * The ChangeNumber of this update.
054       */
055      private ChangeNumber changeNumber;
056    
057      /**
058       * The DN on which the update was originally done.
059       */
060      private String dn = null;
061    
062      /**
063       * True when the update must use assured replication.
064       */
065      private boolean assuredFlag = false;
066    
067      /**
068       * The UniqueId of the entry that was updated.
069       */
070      private String UniqueId;
071    
072      /**
073       * Creates a new UpdateMessage with the given informations.
074       *
075       * @param ctx The replication Context of the operation for which the
076       *            update message must be created,.
077       * @param dn The dn of the entry on which the change
078       *           that caused the creation of this object happened
079       */
080      public UpdateMessage(OperationContext ctx, String dn)
081      {
082        this.changeNumber = ctx.getChangeNumber();
083        this.UniqueId = ctx.getEntryUid();
084        this.dn = dn;
085      }
086    
087      /**
088       * Creates a new UpdateMessage from an ecoded byte array.
089       *
090       * @param in The encoded byte array containind the UpdateMessage.
091       * @throws DataFormatException if the encoded byte array is not valid.
092       * @throws UnsupportedEncodingException if UTF-8 is not supprted.
093       */
094      protected UpdateMessage(byte[] in) throws DataFormatException,
095                                             UnsupportedEncodingException
096      {
097        /* read the changeNumber */
098        int pos = 1;
099        int length = getNextLength(in, pos);
100        String changenumberStr = new String(in, pos, length, "UTF-8");
101        this.changeNumber = new ChangeNumber(changenumberStr);
102      }
103    
104      /**
105       * Generates an Update Message which the provided information.
106       *
107       * @param op The operation for which the message must be created.
108       * @param isAssured flag indicating if the operation is an assured operation.
109       * @return The generated message.
110       */
111      public static UpdateMessage generateMsg(
112             PostOperationOperation op, boolean isAssured)
113      {
114        UpdateMessage msg = null;
115        switch (op.getOperationType())
116        {
117        case MODIFY :
118          msg = new ModifyMsg((PostOperationModifyOperation) op);
119          if (isAssured)
120            msg.setAssured();
121          break;
122    
123        case ADD:
124          msg = new AddMsg((PostOperationAddOperation) op);
125          if (isAssured)
126            msg.setAssured();
127          break;
128    
129        case DELETE :
130          msg = new DeleteMsg((PostOperationDeleteOperation) op);
131          if (isAssured)
132            msg.setAssured();
133          break;
134    
135        case MODIFY_DN :
136          msg = new ModifyDNMsg( (PostOperationModifyDNOperation) op);
137          if (isAssured)
138            msg.setAssured();
139          break;
140        }
141    
142        return msg;
143      }
144    
145      /**
146       * Get the ChangeNumber from the message.
147       * @return the ChangeNumber
148       */
149      public ChangeNumber getChangeNumber()
150      {
151        return changeNumber;
152      }
153    
154      /**
155       * Get the DN on which the operation happened.
156       *
157       * @return The DN on which the operations happened.
158       */
159      public String getDn()
160      {
161        return dn;
162      }
163    
164      /**
165       * Set the DN.
166       * @param dn The dn that must now be used for this message.
167       */
168      public void setDn(String dn)
169      {
170        this.dn = dn;
171      }
172    
173      /**
174       * Get the Unique Identifier of the entry on which the operation happened.
175       *
176       * @return The Unique Identifier of the entry on which the operation happened.
177       */
178      public String getUniqueId()
179      {
180        return UniqueId;
181      }
182    
183      /**
184       * Get a boolean indicating if the Update must be processed as an
185       * Asynchronous or as an assured replication.
186       *
187       * @return Returns the assuredFlag.
188       */
189      public boolean isAssured()
190      {
191        return assuredFlag;
192      }
193    
194      /**
195       * Set the Update message as an assured message.
196       */
197      public void setAssured()
198      {
199        assuredFlag = true;
200      }
201    
202      /**
203       * {@inheritDoc}
204       */
205      @Override
206      public boolean equals(Object obj)
207      {
208        if (obj != null)
209        {
210          if (obj.getClass() != this.getClass())
211            return false;
212          return changeNumber.equals(((UpdateMessage) obj).changeNumber);
213        }
214        else
215        {
216          return false;
217        }
218      }
219    
220      /**
221       * {@inheritDoc}
222       */
223      @Override
224      public int hashCode()
225      {
226        return changeNumber.hashCode();
227      }
228    
229      /**
230       * {@inheritDoc}
231       */
232      public int compareTo(UpdateMessage msg)
233      {
234        return changeNumber.compareTo(msg.getChangeNumber());
235      }
236    
237      /**
238       * Create and Operation from the message.
239       *
240       * @param   conn connection to use when creating the message
241       * @return  the created Operation
242       * @throws  LDAPException In case of LDAP decoding exception.
243       * @throws  ASN1Exception In case of ASN1 decoding exception.
244       * @throws DataFormatException In case of bad msg format.
245       */
246      public AbstractOperation createOperation(InternalClientConnection conn)
247             throws LDAPException, ASN1Exception, DataFormatException
248      {
249        return createOperation(conn, dn);
250      }
251    
252    
253      /**
254       * Create and Operation from the message using the provided DN.
255       *
256       * @param   conn connection to use when creating the message.
257       * @param   newDn the DN to use when creating the operation.
258       * @return  the created Operation.
259       * @throws  LDAPException In case of LDAP decoding exception.
260       * @throws  ASN1Exception In case of ASN1 decoding exception.
261       * @throws DataFormatException In case of bad msg format.
262       */
263      public abstract AbstractOperation createOperation(
264             InternalClientConnection conn, String newDn)
265             throws LDAPException, ASN1Exception, DataFormatException;
266    
267      /**
268       * Encode the common header for all the UpdateMessage.
269       *
270       * @param type the type of UpdateMessage to encode.
271       * @param additionalLength additional length needed to encode the remaining
272       *                         part of the UpdateMessage.
273       * @return a byte array containing the common header and enough space to
274       *         encode the reamining bytes of the UpdateMessage as was specified
275       *         by the additionalLength.
276       *         (byte array length = common header length + additionalLength)
277       * @throws UnsupportedEncodingException if UTF-8 is not supported.
278       */
279      public byte[] encodeHeader(byte type, int additionalLength)
280        throws UnsupportedEncodingException
281      {
282        byte[] byteDn = dn.getBytes("UTF-8");
283        byte[] changeNumberByte =
284          this.getChangeNumber().toString().getBytes("UTF-8");
285        byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8");
286    
287        /* The message header is stored in the form :
288         * <operation type>changenumber><dn><assured><entryuuid><change>
289         * the length of result byte array is therefore :
290         *   1 + change number length + 1 + dn length + 1  + 1 +
291         *   uuid length + 1 + additional_length
292         */
293        int length = 5 + changeNumberByte.length + byteDn.length
294                     + byteEntryuuid.length + additionalLength;
295    
296        byte[] encodedMsg = new byte[length];
297    
298        /* put the type of the operation */
299        encodedMsg[0] = type;
300        int pos = 1;
301    
302        /* put the ChangeNumber */
303        pos = addByteArray(changeNumberByte, encodedMsg, pos);
304    
305        /* put the assured information */
306        encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
307    
308        /* put the DN and a terminating 0 */
309        pos = addByteArray(byteDn, encodedMsg, pos);
310    
311        /* put the entry uuid and a terminating 0 */
312        pos = addByteArray(byteEntryuuid, encodedMsg, pos);
313    
314        return encodedMsg;
315      }
316    
317      /**
318       * Decode the Header part of this Update Message, and check its type.
319       *
320       * @param type The type of this Update Message.
321       * @param encodedMsg the encoded form of the UpdateMessage.
322       * @return the position at which the remaining part of the message starts.
323       * @throws DataFormatException if the encodedMsg does not contain a valid
324       *         common header.
325       */
326      public int decodeHeader(byte type, byte [] encodedMsg)
327                              throws DataFormatException
328      {
329        /* first byte is the type */
330        if (encodedMsg[0] != type)
331          throw new DataFormatException("byte[] is not a valid msg");
332    
333        try
334        {
335          /* read the changeNumber */
336          int pos = 1;
337          int length = getNextLength(encodedMsg, pos);
338          String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
339          pos += length + 1;
340          changeNumber = new ChangeNumber(changenumberStr);
341    
342          /* read the assured information */
343          if (encodedMsg[pos++] == 1)
344            assuredFlag = true;
345          else
346            assuredFlag = false;
347    
348          /* read the dn */
349          length = getNextLength(encodedMsg, pos);
350          dn = new String(encodedMsg, pos, length, "UTF-8");
351          pos += length + 1;
352    
353          /* read the entryuuid */
354          length = getNextLength(encodedMsg, pos);
355          UniqueId = new String(encodedMsg, pos, length, "UTF-8");
356          pos += length + 1;
357    
358          return pos;
359        } catch (UnsupportedEncodingException e)
360        {
361          throw new DataFormatException("UTF-8 is not supported by this jvm.");
362        }
363    
364      }
365    }