001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.shared.ldap.csn; 021 022 023 import java.io.Serializable; 024 import java.text.ParseException; 025 import java.text.SimpleDateFormat; 026 import java.util.Date; 027 028 import org.apache.directory.shared.i18n.I18n; 029 import org.apache.directory.shared.ldap.util.StringTools; 030 import org.slf4j.Logger; 031 import org.slf4j.LoggerFactory; 032 033 034 /** 035 * Represents 'Change Sequence Number' in LDUP specification. 036 * 037 * A CSN is a composition of a timestamp, a replica ID and a 038 * operation sequence number. 039 * 040 * It's described in http://tools.ietf.org/html/draft-ietf-ldup-model-09. 041 * 042 * The CSN syntax is : 043 * <pre> 044 * <CSN> ::= <timestamp> # <changeCount> # <replicaId> # <modifierNumber> 045 * <timestamp> ::= A GMT based time, YYYYmmddHHMMSS.uuuuuuZ 046 * <changeCount> ::= [000000-ffffff] 047 * <replicaId> ::= [000-fff] 048 * <modifierNumber> ::= [000000-ffffff] 049 * </pre> 050 * 051 * It distinguishes a change made on an object on a server, 052 * and if two operations take place during the same timeStamp, 053 * the operation sequence number makes those operations distinct. 054 * 055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 056 */ 057 public class Csn implements Serializable, Comparable<Csn> 058 { 059 /** 060 * Declares the Serial Version Uid. 061 * 062 * @see <a 063 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always 064 * Declare Serial Version Uid</a> 065 */ 066 private static final long serialVersionUID = 1L; 067 068 /** The logger for this class */ 069 private static final Logger LOG = LoggerFactory.getLogger( Csn.class ); 070 071 /** The timeStamp of this operation */ 072 private final long timestamp; 073 074 /** The server identification */ 075 private final int replicaId; 076 077 /** The operation number in a modification operation */ 078 private final int operationNumber; 079 080 /** The changeCount to distinguish operations done in the same second */ 081 private final int changeCount; 082 083 /** Stores the String representation of the CSN */ 084 private transient String csnStr; 085 086 /** Stores the byte array representation of the CSN */ 087 private transient byte[] bytes; 088 089 /** The Timestamp syntax. The last 'z' is _not_ the Time Zone */ 090 private static final SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMddHHmmss" ); 091 092 /** Padding used to format number with a fixed size */ 093 private static final String[] PADDING_6 = new String[] { "00000", "0000", "000", "00", "0", "" }; 094 private static final String[] PADDING_3 = new String[] { "00", "0", "" }; 095 096 097 /** 098 * Creates a new instance. 099 * <b>This method should be used only for deserializing a CSN</b> 100 * 101 * @param timestamp GMT timestamp of modification 102 * @param changeCount The operation increment 103 * @param replicaId Replica ID where modification occurred (<tt>[-_A-Za-z0-9]{1,16}</tt>) 104 * @param operationNumber Operation number in a modification operation 105 */ 106 public Csn( long timestamp, int changeCount, int replicaId, int operationNumber ) 107 { 108 this.timestamp = timestamp; 109 this.replicaId = replicaId; 110 this.operationNumber = operationNumber; 111 this.changeCount = changeCount; 112 } 113 114 115 /** 116 * Creates a new instance of SimpleCSN from a String. 117 * 118 * The string format must be : 119 * <timestamp> # <changeCount> # <replica ID> # <operation number> 120 * 121 * @param value The String containing the CSN 122 */ 123 public Csn( String value ) throws InvalidCSNException 124 { 125 if ( StringTools.isEmpty( value ) ) 126 { 127 String message = I18n.err( I18n.ERR_04114 ); 128 LOG.error( message ); 129 throw new InvalidCSNException( message ); 130 } 131 132 if ( value.length() != 40 ) 133 { 134 String message = I18n.err( I18n.ERR_04115 ); 135 LOG.error( message ); 136 throw new InvalidCSNException( message ); 137 } 138 139 // Get the Timestamp 140 int sepTS = value.indexOf( '#' ); 141 142 if ( sepTS < 0 ) 143 { 144 String message = I18n.err( I18n.ERR_04116 ); 145 LOG.error( message ); 146 throw new InvalidCSNException( message ); 147 } 148 149 String timestampStr = value.substring( 0, sepTS ).trim(); 150 151 if ( timestampStr.length() != 22 ) 152 { 153 String message = I18n.err( I18n.ERR_04117 ); 154 LOG.error( message ); 155 throw new InvalidCSNException( message ); 156 } 157 158 // Let's transform the Timestamp by removing the mulliseconds and microseconds 159 String realTimestamp = timestampStr.substring( 0, 14 ); 160 161 long tempTimestamp = 0L; 162 163 synchronized ( sdf ) 164 { 165 try 166 { 167 tempTimestamp = sdf.parse( realTimestamp ).getTime(); 168 } 169 catch ( ParseException pe ) 170 { 171 String message = I18n.err( I18n.ERR_04118, timestampStr ); 172 LOG.error( message ); 173 throw new InvalidCSNException( message ); 174 } 175 } 176 177 int millis = 0; 178 179 // And add the milliseconds and microseconds now 180 try 181 { 182 millis = Integer.valueOf( timestampStr.substring( 15, 21 ) ); 183 } 184 catch ( NumberFormatException nfe ) 185 { 186 String message = I18n.err( I18n.ERR_04119 ); 187 LOG.error( message ); 188 throw new InvalidCSNException( message ); 189 } 190 191 tempTimestamp += (millis/1000); 192 timestamp = tempTimestamp; 193 194 // Get the changeCount. It should be an hex number prefixed with '0x' 195 int sepCC = value.indexOf( '#', sepTS + 1 ); 196 197 if ( sepCC < 0 ) 198 { 199 String message = I18n.err( I18n.ERR_04110, value ); 200 LOG.error( message ); 201 throw new InvalidCSNException( message ); 202 } 203 204 String changeCountStr = value.substring( sepTS + 1, sepCC ).trim(); 205 206 try 207 { 208 changeCount = Integer.parseInt( changeCountStr, 16 ); 209 } 210 catch ( NumberFormatException nfe ) 211 { 212 String message = I18n.err( I18n.ERR_04121, changeCountStr ); 213 LOG.error( message ); 214 throw new InvalidCSNException( message ); 215 } 216 217 // Get the replicaID 218 int sepRI = value.indexOf( '#', sepCC + 1 ); 219 220 if ( sepRI < 0 ) 221 { 222 String message = I18n.err( I18n.ERR_04122, value ); 223 LOG.error( message ); 224 throw new InvalidCSNException( message ); 225 } 226 227 String replicaIdStr = value.substring( sepCC + 1, sepRI).trim(); 228 229 if ( StringTools.isEmpty( replicaIdStr ) ) 230 { 231 String message = I18n.err( I18n.ERR_04123 ); 232 LOG.error( message ); 233 throw new InvalidCSNException( message ); 234 } 235 236 try 237 { 238 replicaId = Integer.parseInt( replicaIdStr, 16 ); 239 } 240 catch ( NumberFormatException nfe ) 241 { 242 String message = I18n.err( I18n.ERR_04124, replicaIdStr ); 243 LOG.error( message ); 244 throw new InvalidCSNException( message ); 245 } 246 247 // Get the modification number 248 if ( sepCC == value.length() ) 249 { 250 String message = I18n.err( I18n.ERR_04125 ); 251 LOG.error( message ); 252 throw new InvalidCSNException( message ); 253 } 254 255 String operationNumberStr = value.substring( sepRI + 1 ).trim(); 256 257 try 258 { 259 operationNumber = Integer.parseInt( operationNumberStr, 16 ); 260 } 261 catch ( NumberFormatException nfe ) 262 { 263 String message = I18n.err( I18n.ERR_04126, operationNumberStr ); 264 LOG.error( message ); 265 throw new InvalidCSNException( message ); 266 } 267 268 csnStr = value; 269 bytes = StringTools.getBytesUtf8( csnStr ); 270 } 271 272 273 /** 274 * Check if the given String is a valid CSN. 275 * 276 * @param value The String to check 277 * @return <code>true</code> if the String is a valid CSN 278 */ 279 public static boolean isValid( String value ) 280 { 281 if ( StringTools.isEmpty( value ) ) 282 { 283 return false; 284 } 285 286 if ( value.length() != 40 ) 287 { 288 return false; 289 } 290 291 // Get the Timestamp 292 int sepTS = value.indexOf( '#' ); 293 294 if ( sepTS < 0 ) 295 { 296 return false; 297 } 298 299 String timestampStr = value.substring( 0, sepTS ).trim(); 300 301 if ( timestampStr.length() != 22 ) 302 { 303 return false; 304 } 305 306 // Let's transform the Timestamp by removing the mulliseconds and microseconds 307 String realTimestamp = timestampStr.substring( 0, 14 ); 308 309 synchronized ( sdf ) 310 { 311 try 312 { 313 sdf.parse( realTimestamp ).getTime(); 314 } 315 catch ( ParseException pe ) 316 { 317 return false; 318 } 319 } 320 321 // And add the milliseconds and microseconds now 322 String millisStr = timestampStr.substring( 15, 21 ); 323 324 if ( StringTools.isEmpty( millisStr ) ) 325 { 326 return false; 327 } 328 329 for ( int i = 0; i < 6; i++ ) 330 { 331 if ( !StringTools.isDigit( millisStr, i ) ) 332 { 333 return false; 334 } 335 } 336 337 try 338 { 339 Integer.valueOf( millisStr ); 340 } 341 catch ( NumberFormatException nfe ) 342 { 343 return false; 344 } 345 346 // Get the changeCount. It should be an hex number prefixed with '0x' 347 int sepCC = value.indexOf( '#', sepTS + 1 ); 348 349 if ( sepCC < 0 ) 350 { 351 return false; 352 } 353 354 String changeCountStr = value.substring( sepTS + 1, sepCC ).trim(); 355 356 if ( StringTools.isEmpty( changeCountStr ) ) 357 { 358 return false; 359 } 360 361 if ( changeCountStr.length() != 6 ) 362 { 363 return false; 364 } 365 366 try 367 { 368 for ( int i = 0; i < 6; i++ ) 369 { 370 if ( !StringTools.isHex( changeCountStr, i ) ) 371 { 372 return false; 373 } 374 } 375 376 Integer.parseInt( changeCountStr, 16 ); 377 } 378 catch ( NumberFormatException nfe ) 379 { 380 return false; 381 } 382 383 // Get the replicaIDfalse 384 int sepRI = value.indexOf( '#', sepCC + 1 ); 385 386 if ( sepRI < 0 ) 387 { 388 return false; 389 } 390 391 String replicaIdStr = value.substring( sepCC + 1, sepRI ).trim(); 392 393 if ( StringTools.isEmpty( replicaIdStr ) ) 394 { 395 return false; 396 } 397 398 if ( replicaIdStr.length() != 3 ) 399 { 400 return false; 401 } 402 403 for ( int i = 0; i < 3; i++ ) 404 { 405 if ( !StringTools.isHex( replicaIdStr, i ) ) 406 { 407 return false; 408 } 409 } 410 411 try 412 { 413 Integer.parseInt( replicaIdStr, 16 ); 414 } 415 catch ( NumberFormatException nfe ) 416 { 417 return false; 418 } 419 420 // Get the modification number 421 if ( sepCC == value.length() ) 422 { 423 return false; 424 } 425 426 String operationNumberStr = value.substring( sepRI + 1 ).trim(); 427 428 if ( operationNumberStr.length() != 6 ) 429 { 430 return false; 431 } 432 433 for ( int i = 0; i < 6; i++ ) 434 { 435 if ( !StringTools.isHex( operationNumberStr, i ) ) 436 { 437 return false; 438 } 439 } 440 441 try 442 { 443 Integer.parseInt( operationNumberStr, 16 ); 444 } 445 catch ( NumberFormatException nfe ) 446 { 447 return false; 448 } 449 450 return true; 451 } 452 453 454 /** 455 * Creates a new instance of SimpleCSN from the serialized data 456 * 457 * @param value The byte array which contains the serialized CSN 458 */ 459 Csn( byte[] value ) 460 { 461 csnStr = StringTools.utf8ToString( value ); 462 Csn csn = new Csn( csnStr ); 463 timestamp = csn.timestamp; 464 changeCount = csn.changeCount; 465 replicaId = csn.replicaId; 466 operationNumber = csn.operationNumber; 467 bytes = StringTools.getBytesUtf8( csnStr ); 468 } 469 470 471 /** 472 * Get the CSN as a byte array. The data are stored as : 473 * bytes 1 to 8 : timestamp, big-endian 474 * bytes 9 to 12 : change count, big endian 475 * bytes 13 to ... : ReplicaId 476 * 477 * @return A copy of the byte array representing theCSN 478 */ 479 public byte[] getBytes() 480 { 481 if ( bytes == null ) 482 { 483 bytes = StringTools.getBytesUtf8( csnStr ); 484 } 485 486 byte[] copy = new byte[bytes.length]; 487 System.arraycopy( bytes, 0, copy, 0, bytes.length ); 488 return copy; 489 } 490 491 492 /** 493 * @return The timestamp 494 */ 495 public long getTimestamp() 496 { 497 return timestamp; 498 } 499 500 501 /** 502 * @return The changeCount 503 */ 504 public int getChangeCount() 505 { 506 return changeCount; 507 } 508 509 510 /** 511 * @return The replicaId 512 */ 513 public int getReplicaId() 514 { 515 return replicaId; 516 } 517 518 519 /** 520 * @return The operation number 521 */ 522 public int getOperationNumber() 523 { 524 return operationNumber; 525 } 526 527 528 /** 529 * @return The CSN as a String 530 */ 531 public String toString() 532 { 533 if ( csnStr == null ) 534 { 535 StringBuilder buf = new StringBuilder( 40 ); 536 537 synchronized( sdf ) 538 { 539 buf.append( sdf.format( new Date( timestamp ) ) ); 540 } 541 542 // Add the milliseconds part 543 long millis = (timestamp % 1000 ) * 1000; 544 String millisStr = Long.toString( millis ); 545 546 buf.append( '.' ).append( PADDING_6[ millisStr.length() - 1 ] ).append( millisStr ).append( "Z#" ); 547 548 String countStr = Integer.toHexString( changeCount ); 549 550 buf.append( PADDING_6[countStr.length() - 1] ).append( countStr ); 551 buf.append( '#' ); 552 553 String replicaIdStr = Integer.toHexString( replicaId ); 554 555 buf.append( PADDING_3[replicaIdStr.length() - 1]).append( replicaIdStr ); 556 buf.append( '#' ); 557 558 String operationNumberStr = Integer.toHexString( operationNumber ); 559 560 buf.append( PADDING_6[operationNumberStr.length() - 1] ).append( operationNumberStr ); 561 562 csnStr = buf.toString(); 563 } 564 565 return csnStr; 566 } 567 568 569 /** 570 * Returns a hash code value for the object. 571 * 572 * @return a hash code value for this object. 573 */ 574 public int hashCode() 575 { 576 int h = 37; 577 578 h = h*17 + (int)(timestamp ^ (timestamp >>> 32)); 579 h = h*17 + changeCount; 580 h = h*17 + replicaId; 581 h = h*17 + operationNumber; 582 583 return h; 584 } 585 586 587 /** 588 * Indicates whether some other object is "equal to" this one 589 * 590 * @param o the reference object with which to compare. 591 * @return <code>true</code> if this object is the same as the obj argument; 592 * <code>false</code> otherwise. 593 */ 594 public boolean equals( Object o ) 595 { 596 if ( this == o ) 597 { 598 return true; 599 } 600 601 if ( !( o instanceof Csn ) ) 602 { 603 return false; 604 } 605 606 Csn that = ( Csn ) o; 607 608 return 609 ( timestamp == that.timestamp ) && 610 ( changeCount == that.changeCount ) && 611 ( replicaId == that.replicaId ) && 612 ( operationNumber == that.operationNumber ); 613 } 614 615 616 /** 617 * Compares this object with the specified object for order. Returns a 618 * negative integer, zero, or a positive integer as this object is less 619 * than, equal to, or greater than the specified object.<p> 620 * 621 * @param o the Object to be compared. 622 * @return a negative integer, zero, or a positive integer as this object 623 * is less than, equal to, or greater than the specified object. 624 */ 625 public int compareTo( Csn csn ) 626 { 627 if ( csn == null ) 628 { 629 return 1; 630 } 631 632 // Compares the timestamp first 633 if ( this.timestamp < csn.timestamp ) 634 { 635 return -1; 636 } 637 else if ( this.timestamp > csn.timestamp ) 638 { 639 return 1; 640 } 641 642 // Then the change count 643 if ( this.changeCount < csn.changeCount ) 644 { 645 return -1; 646 } 647 else if ( this.changeCount > csn.changeCount ) 648 { 649 return 1; 650 } 651 652 // Then the replicaId 653 int replicaIdCompareResult= 654 ( this.replicaId < csn.replicaId ? 655 -1 : 656 ( this.replicaId > csn.replicaId ? 657 1 : 0 ) ); 658 659 if ( replicaIdCompareResult != 0 ) 660 { 661 return replicaIdCompareResult; 662 } 663 664 // Last, not least, compares the operation number 665 if ( this.operationNumber < csn.operationNumber ) 666 { 667 return -1; 668 } 669 else if ( this.operationNumber > csn.operationNumber ) 670 { 671 return 1; 672 } 673 else 674 { 675 return 0; 676 } 677 } 678 }