1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.directory.server.core.changelog;
20
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.io.OutputStream;
29 import java.io.PrintWriter;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.Files;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38
39 import org.apache.directory.api.ldap.model.cursor.Cursor;
40 import org.apache.directory.api.ldap.model.cursor.ListCursor;
41 import org.apache.directory.api.ldap.model.exception.LdapException;
42 import org.apache.directory.api.ldap.model.ldif.LdifEntry;
43 import org.apache.directory.api.util.DateUtils;
44 import org.apache.directory.api.util.TimeProvider;
45 import org.apache.directory.server.core.api.DirectoryService;
46 import org.apache.directory.server.core.api.LdapPrincipal;
47 import org.apache.directory.server.core.api.changelog.ChangeLogEvent;
48 import org.apache.directory.server.core.api.changelog.ChangeLogEventSerializer;
49 import org.apache.directory.server.core.api.changelog.Tag;
50 import org.apache.directory.server.core.api.changelog.TaggableChangeLogStore;
51 import org.apache.directory.server.i18n.I18n;
52
53
54
55
56
57
58
59 public class MemoryChangeLogStore implements TaggableChangeLogStore
60 {
61
62 private static final String REV_FILE = "revision";
63 private static final String TAG_FILE = "tags";
64 private static final String CHANGELOG_FILE = "changelog.dat";
65
66
67 private long currentRevision;
68
69
70 private Tag latest;
71
72
73 private final Map<Long, Tag> tags = new HashMap<>( 100 );
74
75 private final List<ChangeLogEvent> events = new ArrayList<>();
76 private File workingDirectory;
77
78
79 private DirectoryService directoryService;
80
81 private TimeProvider timeProvider = TimeProvider.DEFAULT;
82
83
84
85
86 @Override
87 public Tag tag( long revision )
88 {
89 if ( tags.containsKey( revision ) )
90 {
91 return tags.get( revision );
92 }
93
94 latest = new Tag( revision, null );
95 tags.put( revision, latest );
96
97 return latest;
98 }
99
100
101
102
103
104 @Override
105 public Tag tag()
106 {
107 if ( ( latest != null ) && ( latest.getRevision() == currentRevision ) )
108 {
109 return latest;
110 }
111
112 latest = new Tag( currentRevision, null );
113 tags.put( currentRevision, latest );
114 return latest;
115 }
116
117
118
119
120
121 @Override
122 public Tag tag( String description )
123 {
124 if ( ( latest != null ) && ( latest.getRevision() == currentRevision ) )
125 {
126 return latest;
127 }
128
129 latest = new Tag( currentRevision, description );
130 tags.put( currentRevision, latest );
131 return latest;
132 }
133
134
135
136
137
138 @Override
139 public void init( DirectoryService service ) throws LdapException
140 {
141 workingDirectory = service.getInstanceLayout().getLogDirectory();
142 this.directoryService = service;
143 this.timeProvider = service.getTimeProvider();
144
145 try
146 {
147 loadRevision();
148 loadTags();
149 loadChangeLog();
150 }
151 catch ( IOException ioe )
152 {
153 throw new LdapException( ioe.getMessage(), ioe );
154 }
155 }
156
157
158
159 private void loadRevision() throws IOException
160 {
161 File revFile = new File( workingDirectory, REV_FILE );
162
163 if ( revFile.exists() )
164 {
165 try ( BufferedReader reader = Files.newBufferedReader( revFile.toPath(), StandardCharsets.UTF_8 ) )
166 {
167 String line = reader.readLine();
168 currentRevision = Long.parseLong( line );
169 }
170 }
171 }
172
173
174 private void saveRevision() throws IOException
175 {
176 File revFile = new File( workingDirectory, REV_FILE );
177
178 if ( revFile.exists() && !revFile.delete() )
179 {
180 throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, revFile.getAbsolutePath() ) );
181 }
182
183
184 try ( PrintWriter out = new PrintWriter( Files.newBufferedWriter( revFile.toPath(), StandardCharsets.UTF_8 ) ) )
185 {
186 out.println( currentRevision );
187 out.flush();
188 }
189 }
190
191
192
193 private void saveTags() throws IOException
194 {
195 File tagFile = new File( workingDirectory, TAG_FILE );
196
197 if ( tagFile.exists() && !tagFile.delete() )
198 {
199 throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, tagFile.getAbsolutePath() ) );
200 }
201
202 OutputStream out = null;
203
204 try
205 {
206 out = Files.newOutputStream( tagFile.toPath() );
207
208 Properties props = new Properties();
209
210 for ( Tag tag : tags.values() )
211 {
212 String key = String.valueOf( tag.getRevision() );
213
214 if ( tag.getDescription() == null )
215 {
216 props.setProperty( key, "null" );
217 }
218 else
219 {
220 props.setProperty( key, tag.getDescription() );
221 }
222 }
223
224 props.store( out, null );
225 out.flush();
226 }
227 finally
228 {
229 if ( out != null )
230 {
231 out.close();
232 }
233 }
234 }
235
236
237
238 private void loadTags() throws IOException
239 {
240 File revFile = new File( workingDirectory, REV_FILE );
241
242 if ( revFile.exists() )
243 {
244 Properties props = new Properties();
245 InputStream in = null;
246
247 try
248 {
249 in = Files.newInputStream( revFile.toPath() );
250 props.load( in );
251 ArrayList<Long> revList = new ArrayList<>();
252
253 for ( Object key : props.keySet() )
254 {
255 revList.add( Long.valueOf( ( String ) key ) );
256 }
257
258 Collections.sort( revList );
259 Tag tag = null;
260
261
262 tags.clear();
263
264 for ( Long lkey : revList )
265 {
266 String rev = String.valueOf( lkey );
267 String desc = props.getProperty( rev );
268
269 if ( ( desc != null ) && desc.equals( "null" ) )
270 {
271 tag = new Tag( lkey, null );
272 }
273 else
274 {
275 tag = new Tag( lkey, desc );
276 }
277
278 tags.put( lkey, tag );
279 }
280
281 latest = tag;
282 }
283 finally
284 {
285 if ( in != null )
286 {
287 in.close();
288 }
289 }
290 }
291 }
292
293
294
295 private void loadChangeLog() throws IOException
296 {
297 File file = new File( workingDirectory, CHANGELOG_FILE );
298
299 if ( file.exists() )
300 {
301 try ( ObjectInputStream in = new ObjectInputStream( Files.newInputStream( file.toPath() ) ) )
302 {
303 int size = in.readInt();
304
305 ArrayList<ChangeLogEvent> changeLogEvents = new ArrayList<>( size );
306
307 for ( int i = 0; i < size; i++ )
308 {
309 ChangeLogEvent event = ChangeLogEventSerializer.deserialize( directoryService.getSchemaManager(),
310 in );
311 event.getCommitterPrincipal().setSchemaManager( directoryService.getSchemaManager() );
312 changeLogEvents.add( event );
313 }
314
315
316 this.events.clear();
317 this.events.addAll( changeLogEvents );
318 }
319 }
320 }
321
322
323
324 private void saveChangeLog() throws IOException
325 {
326 File file = new File( workingDirectory, CHANGELOG_FILE );
327
328 if ( file.exists() && !file.delete() )
329 {
330 throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, file.getAbsolutePath() ) );
331 }
332
333 file.createNewFile();
334
335 try ( ObjectOutputStream out = new ObjectOutputStream( Files.newOutputStream( file.toPath() ) ) )
336 {
337 out.writeInt( events.size() );
338
339 for ( ChangeLogEvent event : events )
340 {
341 ChangeLogEventSerializer.serialize( event, out );
342 }
343
344 out.flush();
345 }
346 }
347
348
349
350
351
352 @Override
353 public void sync() throws LdapException
354 {
355 try
356 {
357 saveRevision();
358 saveTags();
359 saveChangeLog();
360 }
361 catch ( IOException ioe )
362 {
363 throw new LdapException( ioe.getMessage(), ioe );
364 }
365 }
366
367
368
369
370
371 @Override
372 public void destroy() throws LdapException
373 {
374 try
375 {
376 saveRevision();
377 saveTags();
378 saveChangeLog();
379 }
380 catch ( IOException ioe )
381 {
382 throw new LdapException( ioe.getMessage(), ioe );
383 }
384 }
385
386
387
388
389
390 @Override
391 public long getCurrentRevision()
392 {
393 return currentRevision;
394 }
395
396
397
398
399
400 @Override
401 public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, LdifEntry reverse )
402 {
403 currentRevision++;
404 ChangeLogEventore/api/changelog/ChangeLogEvent.html#ChangeLogEvent">ChangeLogEvent event = new ChangeLogEvent( currentRevision,
405 DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ),
406 principal, forward, reverse );
407 events.add( event );
408
409 return event;
410 }
411
412
413
414
415
416 @Override
417 public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, List<LdifEntry> reverses )
418 {
419 currentRevision++;
420 ChangeLogEventore/api/changelog/ChangeLogEvent.html#ChangeLogEvent">ChangeLogEvent event = new ChangeLogEvent( currentRevision,
421 DateUtils.getGeneralizedTime( timeProvider ), principal, forward, reverses );
422 events.add( event );
423
424 return event;
425 }
426
427
428
429
430
431 @Override
432 public ChangeLogEvent lookup( long revision )
433 {
434 if ( revision < 0 )
435 {
436 throw new IllegalArgumentException( I18n.err( I18n.ERR_239 ) );
437 }
438
439 if ( revision > getCurrentRevision() )
440 {
441 throw new IllegalArgumentException( I18n.err( I18n.ERR_240 ) );
442 }
443
444 return events.get( ( int ) revision );
445 }
446
447
448
449
450
451 @Override
452 public Cursor<ChangeLogEvent> find()
453 {
454 return new ListCursor<>( events );
455 }
456
457
458
459
460
461 @Override
462 public Cursor<ChangeLogEvent> findBefore( long revision )
463 {
464 return new ListCursor<>( events, ( int ) revision );
465 }
466
467
468
469
470
471 @Override
472 public Cursor<ChangeLogEvent> findAfter( long revision )
473 {
474 return new ListCursor<>( ( int ) revision, events );
475 }
476
477
478
479
480
481 @Override
482 public Cursor<ChangeLogEvent> find( long startRevision, long endRevision )
483 {
484 return new ListCursor<>( ( int ) startRevision, events, ( int ) ( endRevision + 1 ) );
485 }
486
487
488
489
490
491 @Override
492 public Tag getLatest()
493 {
494 return latest;
495 }
496
497
498
499
500
501 @Override
502 public Tag removeTag( long revision )
503 {
504 return tags.remove( revision );
505 }
506
507
508
509
510
511 @Override
512 public Tag tag( long revision, String descrition )
513 {
514 if ( tags.containsKey( revision ) )
515 {
516 return tags.get( revision );
517 }
518
519 latest = new Tag( revision, descrition );
520 tags.put( revision, latest );
521 return latest;
522 }
523
524
525
526
527
528 @Override
529 public String toString()
530 {
531 StringBuilder sb = new StringBuilder();
532
533 sb.append( "MemoryChangeLog\n" );
534 sb.append( "latest tag : " ).append( latest ).append( '\n' );
535
536 sb.append( "Nb of events : " ).append( events.size() ).append( '\n' );
537
538 int i = 0;
539
540 for ( ChangeLogEvent event : events )
541 {
542 sb.append( "event[" ).append( i++ ).append( "] : " );
543 sb.append( "\n---------------------------------------\n" );
544 sb.append( event );
545 sb.append( "\n---------------------------------------\n" );
546 }
547
548 return sb.toString();
549 }
550 }