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 }