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 }