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 }