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    }