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