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 }