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 package org.apache.directory.server.core.changelog; 020 021 import java.io.BufferedReader; 022 import java.io.File; 023 import java.io.FileInputStream; 024 import java.io.FileOutputStream; 025 import java.io.FileReader; 026 import java.io.FileWriter; 027 import java.io.IOException; 028 import java.io.ObjectInputStream; 029 import java.io.ObjectOutputStream; 030 import java.io.PrintWriter; 031 import java.util.ArrayList; 032 import java.util.Collections; 033 import java.util.HashMap; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Properties; 037 038 import org.apache.directory.server.core.DirectoryService; 039 import org.apache.directory.server.core.LdapPrincipal; 040 import org.apache.directory.server.i18n.I18n; 041 import org.apache.directory.shared.ldap.cursor.Cursor; 042 import org.apache.directory.shared.ldap.cursor.ListCursor; 043 import org.apache.directory.shared.ldap.ldif.LdifEntry; 044 import org.apache.directory.shared.ldap.util.DateUtils; 045 046 047 /** 048 * A change log store that keeps it's information in memory. 049 * 050 * @org.apache.xbean.XBean 051 * 052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 053 * @version $Rev$, $Date$ 054 * TODO remove the NamingException 055 */ 056 public class MemoryChangeLogStore implements TaggableChangeLogStore 057 { 058 059 private static final String REV_FILE = "revision"; 060 private static final String TAG_FILE = "tags"; 061 private static final String CHANGELOG_FILE = "changelog.dat"; 062 063 /** An incremental number giving the current revision */ 064 private long currentRevision; 065 066 /** The latest tag */ 067 private Tag latest; 068 069 /** A Map of tags and revisions */ 070 private final Map<Long,Tag> tags = new HashMap<Long,Tag>( 100 ); 071 072 private final List<ChangeLogEvent> events = new ArrayList<ChangeLogEvent>(); 073 private File workingDirectory; 074 075 076 /** 077 * {@inheritDoc} 078 */ 079 public Tag tag( long revision ) throws Exception 080 { 081 if ( tags.containsKey( revision ) ) 082 { 083 return tags.get( revision ); 084 } 085 086 latest = new Tag( revision, null ); 087 tags.put( revision, latest ); 088 return latest; 089 } 090 091 092 /** 093 * {@inheritDoc} 094 */ 095 public Tag tag() throws Exception 096 { 097 if ( ( latest != null) && ( latest.getRevision() == currentRevision ) ) 098 { 099 return latest; 100 } 101 102 latest = new Tag( currentRevision, null ); 103 tags.put( currentRevision, latest ); 104 return latest; 105 } 106 107 108 public Tag tag( String description ) throws Exception 109 { 110 if ( ( latest != null ) && ( latest.getRevision() == currentRevision ) ) 111 { 112 return latest; 113 } 114 115 latest = new Tag( currentRevision, description ); 116 tags.put( currentRevision, latest ); 117 return latest; 118 } 119 120 121 public void init( DirectoryService service ) throws Exception 122 { 123 workingDirectory = service.getWorkingDirectory(); 124 loadRevision(); 125 loadTags(); 126 loadChangeLog(); 127 } 128 129 130 private void loadRevision() throws Exception 131 { 132 File revFile = new File( workingDirectory, REV_FILE ); 133 134 if ( revFile.exists() ) 135 { 136 BufferedReader reader = null; 137 138 try 139 { 140 reader = new BufferedReader( new FileReader( revFile ) ); 141 String line = reader.readLine(); 142 currentRevision = Long.valueOf( line ); 143 } 144 catch ( IOException e ) 145 { 146 throw e; 147 } 148 finally 149 { 150 if ( reader != null ) 151 { 152 //noinspection EmptyCatchBlock 153 try 154 { 155 reader.close(); 156 } 157 catch ( IOException e ) 158 { 159 } 160 } 161 } 162 } 163 } 164 165 166 private void saveRevision() throws Exception 167 { 168 File revFile = new File( workingDirectory, REV_FILE ); 169 170 if ( revFile.exists() ) 171 { 172 revFile.delete(); 173 } 174 175 PrintWriter out = null; 176 177 try 178 { 179 out = new PrintWriter( new FileWriter( revFile ) ); 180 out.println( currentRevision ); 181 out.flush(); 182 } 183 catch ( IOException e ) 184 { 185 throw e; 186 } 187 finally 188 { 189 if ( out != null ) 190 { 191 out.close(); 192 } 193 } 194 } 195 196 197 private void saveTags() throws Exception 198 { 199 File tagFile = new File( workingDirectory, TAG_FILE ); 200 201 if ( tagFile.exists() ) 202 { 203 tagFile.delete(); 204 } 205 206 FileOutputStream out = null; 207 208 try 209 { 210 out = new FileOutputStream( tagFile ); 211 212 Properties props = new Properties(); 213 214 for ( Tag tag : tags.values() ) 215 { 216 String key = String.valueOf( tag.getRevision() ); 217 218 if ( tag.getDescription() == null ) 219 { 220 props.setProperty( key, "null" ); 221 } 222 else 223 { 224 props.setProperty( key, tag.getDescription() ); 225 } 226 } 227 228 props.store( out, null ); 229 out.flush(); 230 } 231 catch ( IOException e ) 232 { 233 throw e; 234 } 235 finally 236 { 237 if ( out != null ) 238 { 239 //noinspection EmptyCatchBlock 240 try 241 { 242 out.close(); 243 } 244 catch ( IOException e ) 245 { 246 } 247 } 248 } 249 } 250 251 252 private void loadTags() throws Exception 253 { 254 File revFile = new File( workingDirectory, REV_FILE ); 255 256 if ( revFile.exists() ) 257 { 258 Properties props = new Properties(); 259 FileInputStream in = null; 260 261 try 262 { 263 in = new FileInputStream( revFile ); 264 props.load( in ); 265 ArrayList<Long> revList = new ArrayList<Long>(); 266 267 for ( Object key : props.keySet() ) 268 { 269 revList.add( Long.valueOf( ( String ) key ) ); 270 } 271 272 Collections.sort( revList ); 273 Tag tag = null; 274 275 // @todo need some serious syncrhoization here on tags 276 tags.clear(); 277 278 for ( Long lkey : revList ) 279 { 280 String rev = String.valueOf( lkey ); 281 String desc = props.getProperty( rev ); 282 283 if ( desc != null && desc.equals( "null" ) ) 284 { 285 tag = new Tag( lkey, null ); 286 } 287 else 288 { 289 tag = new Tag( lkey, desc ); 290 } 291 292 tags.put( lkey, tag ); 293 } 294 295 latest = tag; 296 } 297 catch ( IOException e ) 298 { 299 throw e; 300 } 301 finally 302 { 303 if ( in != null ) 304 { 305 //noinspection EmptyCatchBlock 306 try 307 { 308 in.close(); 309 } 310 catch ( IOException e ) 311 { 312 } 313 } 314 } 315 } 316 } 317 318 319 private void loadChangeLog() throws Exception 320 { 321 File file = new File( workingDirectory, CHANGELOG_FILE ); 322 323 if ( file.exists() ) 324 { 325 ObjectInputStream in = null; 326 327 try 328 { 329 in = new ObjectInputStream( new FileInputStream( file ) ); 330 int size = in.readInt(); 331 332 ArrayList<ChangeLogEvent> changeLogEvents = new ArrayList<ChangeLogEvent>( size ); 333 334 for ( int i = 0; i < size; i++ ) 335 { 336 ChangeLogEvent event = ( ChangeLogEvent ) in.readObject(); 337 changeLogEvents.add( event ); 338 } 339 340 // @todo man o man we need some synchronization later after getting this to work 341 this.events.clear(); 342 this.events.addAll( changeLogEvents ); 343 } 344 catch ( Exception e ) 345 { 346 throw e; 347 } 348 finally 349 { 350 if ( in != null ) 351 { 352 //noinspection EmptyCatchBlock 353 try 354 { 355 in.close(); 356 } 357 catch ( IOException e ) 358 { 359 } 360 } 361 } 362 } 363 } 364 365 366 private void saveChangeLog() throws Exception 367 { 368 File file = new File( workingDirectory, CHANGELOG_FILE ); 369 370 if ( file.exists() ) 371 { 372 file.delete(); 373 } 374 375 try 376 { 377 file.createNewFile(); 378 } 379 catch ( IOException e ) 380 { 381 throw e; 382 } 383 384 ObjectOutputStream out = null; 385 386 try 387 { 388 out = new ObjectOutputStream( new FileOutputStream( file ) ); 389 390 out.writeInt( events.size() ); 391 392 for ( ChangeLogEvent event : events ) 393 { 394 out.writeObject( event ); 395 } 396 397 out.flush(); 398 } 399 catch ( Exception e ) 400 { 401 throw e; 402 } 403 finally 404 { 405 if ( out != null ) 406 { 407 //noinspection EmptyCatchBlock 408 try 409 { 410 out.close(); 411 } 412 catch ( IOException e ) 413 { 414 } 415 } 416 } 417 } 418 419 420 public void sync() throws Exception 421 { 422 saveRevision(); 423 saveTags(); 424 saveChangeLog(); 425 } 426 427 428 /** 429 * Save logs, tags and revision on disk, and clean everything in memory 430 */ 431 public void destroy() throws Exception 432 { 433 saveRevision(); 434 saveTags(); 435 saveChangeLog(); 436 } 437 438 439 public long getCurrentRevision() 440 { 441 return currentRevision; 442 } 443 444 445 /** 446 * {@inheritDoc} 447 */ 448 public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, LdifEntry reverse ) throws Exception 449 { 450 currentRevision++; 451 ChangeLogEvent event = new ChangeLogEvent( currentRevision, DateUtils.getGeneralizedTime(), 452 principal, forward, reverse ); 453 events.add( event ); 454 return event; 455 } 456 457 458 /** 459 * {@inheritDoc} 460 */ 461 public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, List<LdifEntry> reverses ) throws Exception 462 { 463 currentRevision++; 464 ChangeLogEvent event = new ChangeLogEvent( currentRevision, DateUtils.getGeneralizedTime(), 465 principal, forward, reverses ); 466 events.add( event ); 467 return event; 468 } 469 470 471 public ChangeLogEvent lookup( long revision ) throws Exception 472 { 473 if ( revision < 0 ) 474 { 475 throw new IllegalArgumentException( I18n.err( I18n.ERR_239 ) ); 476 } 477 478 if ( revision > getCurrentRevision() ) 479 { 480 throw new IllegalArgumentException( I18n.err( I18n.ERR_240 ) ); 481 } 482 483 return events.get( ( int ) revision ); 484 } 485 486 487 public Cursor<ChangeLogEvent> find() throws Exception 488 { 489 return new ListCursor<ChangeLogEvent>( events ); 490 } 491 492 493 public Cursor<ChangeLogEvent> findBefore( long revision ) throws Exception 494 { 495 return new ListCursor<ChangeLogEvent>( events, ( int ) revision ); 496 } 497 498 499 public Cursor<ChangeLogEvent> findAfter( long revision ) throws Exception 500 { 501 return new ListCursor<ChangeLogEvent>( ( int ) revision, events ); 502 } 503 504 505 public Cursor<ChangeLogEvent> find( long startRevision, long endRevision ) throws Exception 506 { 507 return new ListCursor<ChangeLogEvent>( ( int ) startRevision, events, ( int ) ( endRevision + 1 ) ); 508 } 509 510 511 public Tag getLatest() throws Exception 512 { 513 return latest; 514 } 515 516 517 /** 518 * @see TaggableChangeLogStore#removeTag(long) 519 */ 520 public Tag removeTag( long revision ) throws Exception 521 { 522 return tags.remove( revision ); 523 } 524 525 526 /** 527 * @see TaggableChangeLogStore#tag(long, String) 528 */ 529 public Tag tag( long revision, String descrition ) throws Exception 530 { 531 if ( tags.containsKey( revision ) ) 532 { 533 return tags.get( revision ); 534 } 535 536 latest = new Tag( revision, descrition ); 537 tags.put( revision, latest ); 538 return latest; 539 } 540 541 542 /** 543 * @see Object#toString() 544 */ 545 public String toString() 546 { 547 StringBuilder sb = new StringBuilder(); 548 549 sb.append( "MemoryChangeLog\n" ); 550 sb.append( "latest tag : " ).append( latest ).append( '\n' ); 551 552 if ( events != null ) 553 { 554 sb.append( "Nb of events : " ).append( events.size() ).append( '\n' ); 555 556 int i = 0; 557 558 for ( ChangeLogEvent event:events ) 559 { 560 sb.append( "event[" ).append( i++ ).append( "] : " ); 561 sb.append( "\n---------------------------------------\n" ); 562 sb.append( event ); 563 sb.append( "\n---------------------------------------\n" ); 564 } 565 } 566 567 568 return sb.toString(); 569 } 570 }