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.common;
028    
029    import java.io.UnsupportedEncodingException;
030    import java.util.ArrayList;
031    import java.util.Date;
032    import java.util.HashMap;
033    import java.util.HashSet;
034    import java.util.Iterator;
035    import java.util.List;
036    import java.util.Set;
037    import java.util.zip.DataFormatException;
038    
039    import org.opends.server.protocols.asn1.ASN1OctetString;
040    
041    
042    /**
043     * ServerState class.
044     * This object is used to store the last update seen on this server
045     * from each server.
046     * It is exchanged with the replication servers at connection establishment
047     * time.
048     */
049    public class ServerState implements Iterable<Short>
050    {
051      private HashMap<Short, ChangeNumber> list;
052    
053      /**
054       * Creates a new empty ServerState.
055       */
056      public ServerState()
057      {
058        list = new HashMap<Short, ChangeNumber>();
059      }
060    
061      /**
062       * Empty the ServerState.
063       * After this call the Server State will be in the same state
064       * as if it was just created.
065       */
066      public void clear()
067      {
068        synchronized (this)
069        {
070          list.clear();
071        }
072      }
073    
074    
075      /**
076       * Creates a new ServerState object from its encoded form.
077       *
078       * @param in The byte array containing the encoded ServerState form.
079       * @param pos The position in the byte array where the encoded ServerState
080       *            starts.
081       * @param endpos The position in the byte array where the encoded ServerState
082       *               ends.
083       * @throws DataFormatException If the encoded form was not correct.
084       */
085      public ServerState(byte[] in, int pos, int endpos)
086             throws DataFormatException
087      {
088        try
089        {
090          list = new HashMap<Short, ChangeNumber>();
091    
092          while (endpos > pos)
093          {
094            /*
095             * read the ServerId
096             */
097            int length = getNextLength(in, pos);
098            String serverIdString = new String(in, pos, length, "UTF-8");
099            short serverId = Short.valueOf(serverIdString);
100            pos += length +1;
101    
102            /*
103             * read the ChangeNumber
104             */
105            length = getNextLength(in, pos);
106            String cnString = new String(in, pos, length, "UTF-8");
107            ChangeNumber cn = new ChangeNumber(cnString);
108            pos += length +1;
109    
110            /*
111             * Add the serverid
112             */
113            list.put(serverId, cn);
114          }
115    
116        } catch (UnsupportedEncodingException e)
117        {
118          throw new DataFormatException("UTF-8 is not supported by this jvm.");
119        }
120      }
121    
122      /**
123       * Get the length of the next String encoded in the in byte array.
124       *
125       * @param in the byte array where to calculate the string.
126       * @param pos the position whre to start from in the byte array.
127       * @return the length of the next string.
128       * @throws DataFormatException If the byte array does not end with null.
129       */
130      private int getNextLength(byte[] in, int pos) throws DataFormatException
131      {
132        int offset = pos;
133        int length = 0;
134        while (in[offset++] != 0)
135        {
136          if (offset >= in.length)
137            throw new DataFormatException("byte[] is not a valid modify msg");
138          length++;
139        }
140        return length;
141      }
142    
143      /**
144       * Update the Server State with a ChangeNumber.
145       * All operations with smaller CSN and the same serverID must be committed
146       * before calling this method.
147       * @param changeNumber the committed ChangeNumber.
148       * @return a boolean indicating if the update was meaningfull.
149       */
150      public boolean update(ChangeNumber changeNumber)
151      {
152        if (changeNumber == null)
153          return false;
154    
155        synchronized(this)
156        {
157          Short id =  changeNumber.getServerId();
158          ChangeNumber oldCN = list.get(id);
159          if (oldCN == null || changeNumber.newer(oldCN))
160          {
161            list.put(id,changeNumber);
162            return true;
163          }
164          else
165          {
166            return false;
167          }
168        }
169      }
170    
171      /**
172       * return a Set of String usable as a textual representation of
173       * a Server state.
174       * format : time seqnum id
175       *
176       * example :
177       *  1 00000109e4666da600220001
178       *  2 00000109e44567a600220002
179       *
180       * @return the representation of the Server state
181       */
182      public Set<String> toStringSet()
183      {
184        HashSet<String> set = new HashSet<String>();
185    
186        synchronized (this)
187        {
188          for (Short key  : list.keySet())
189          {
190            ChangeNumber change = list.get(key);
191            Date date = new Date(change.getTime());
192            set.add(change.toString() + " " + date.toString());
193          }
194        }
195    
196        return set;
197      }
198    
199      /**
200       * Return an ArrayList of ANS1OctetString encoding the ChangeNumbers
201       * contained in the ServerState.
202       * @return an ArrayList of ANS1OctetString encoding the ChangeNumbers
203       * contained in the ServerState.
204       */
205      public ArrayList<ASN1OctetString> toASN1ArrayList()
206      {
207        ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>(0);
208    
209        synchronized (this)
210        {
211          for (Short id : list.keySet())
212          {
213            ASN1OctetString value = new ASN1OctetString(list.get(id).toString());
214            values.add(value);
215          }
216        }
217        return values;
218      }
219      /**
220       * Return the text representation of ServerState.
221       * @return the text representation of ServerState
222       */
223      @Override
224      public String toString()
225      {
226        StringBuilder buffer = new StringBuilder();
227    
228        synchronized (this)
229        {
230          for (Short key  : list.keySet())
231          {
232            ChangeNumber change = list.get(key);
233            buffer.append(" ");
234            buffer.append(change);
235          }
236        }
237    
238        return buffer.toString();
239      }
240    
241      /**
242       * Get the largest ChangeNumber seen for a given LDAP server ID.
243       *
244       * @param serverId : the server ID
245       * @return the largest ChangeNumber seen
246       */
247      public ChangeNumber getMaxChangeNumber(short serverId)
248      {
249        return list.get(serverId);
250      }
251    
252      /**
253       * Add the tail into resultByteArray at position pos.
254       */
255      private int addByteArray(byte[] tail, byte[] resultByteArray, int pos)
256      {
257        for (int i=0; i<tail.length; i++,pos++)
258        {
259          resultByteArray[pos] = tail[i];
260        }
261        resultByteArray[pos++] = 0;
262        return pos;
263      }
264    
265      /**
266       * Encode this ServerState object and return its byte array representation.
267       *
268       * @return a byte array with an encoded representation of this object.
269       * @throws UnsupportedEncodingException if UTF8 is not supported by the JVM.
270       */
271      public byte[] getBytes() throws UnsupportedEncodingException
272      {
273        synchronized (this)
274        {
275          int length = 0;
276          List<String> idList = new ArrayList<String>(list.size());
277          for (short id : list.keySet())
278          {
279            String temp = String.valueOf(id);
280            idList.add(temp);
281            length += temp.length() + 1;
282          }
283          List<String> cnList = new ArrayList<String>(list.size());
284          for (ChangeNumber cn : list.values())
285          {
286            String temp = cn.toString();
287            cnList.add(temp);
288            length += temp.length() + 1;
289          }
290          byte[] result = new byte[length];
291    
292          int pos = 0;
293          for (int i=0; i< list.size(); i++)
294          {
295            String str = idList.get(i);
296            pos = addByteArray(str.getBytes("UTF-8"), result, pos);
297            str = cnList.get(i);
298            pos = addByteArray(str.getBytes("UTF-8"), result, pos);
299          }
300          return result;
301        }
302      }
303    
304      /**
305       * {@inheritDoc}
306       */
307      public Iterator<Short> iterator()
308      {
309        return list.keySet().iterator();
310      }
311    
312      /**
313       * Check that all the ChangeNumbers in the covered serverState are also in
314       * this serverState.
315       *
316       * @param covered The ServerState that needs to be checked.
317       * @return A boolean indicating if this ServerState covers the ServerState
318       *         given in parameter.
319       */
320      public boolean cover(ServerState covered)
321      {
322        for (ChangeNumber coveredChange : covered.list.values())
323        {
324          ChangeNumber change = this.list.get(coveredChange.getServerId());
325          if ((change == null) || (change.older(coveredChange)))
326          {
327            return false;
328          }
329        }
330        return true;
331      }
332    
333      /**
334       * Tests if the state is empty.
335       *
336       * @return True if the state is empty.
337       */
338      public boolean isEmpty()
339      {
340        return list.isEmpty();
341      }
342    
343      /**
344       * Make a duplicate of this state.
345       * @return The duplicate of this state.
346       */
347      public ServerState duplicate()
348      {
349        ServerState newState = new ServerState();
350        synchronized (this)
351        {
352          for (Short key  : list.keySet())
353          {
354            ChangeNumber change = list.get(key);
355            Short id =  change.getServerId();
356            newState.list.put(id,change);
357          }
358        }
359        return newState;
360      }
361    }