| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.directory.server.core.changelog; |
| |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import org.apache.directory.api.ldap.model.cursor.Cursor; |
| import org.apache.directory.api.ldap.model.cursor.ListCursor; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.ldif.LdifEntry; |
| import org.apache.directory.api.util.DateUtils; |
| import org.apache.directory.api.util.TimeProvider; |
| import org.apache.directory.server.core.api.DirectoryService; |
| import org.apache.directory.server.core.api.LdapPrincipal; |
| import org.apache.directory.server.core.api.changelog.ChangeLogEvent; |
| import org.apache.directory.server.core.api.changelog.ChangeLogEventSerializer; |
| import org.apache.directory.server.core.api.changelog.Tag; |
| import org.apache.directory.server.core.api.changelog.TaggableChangeLogStore; |
| import org.apache.directory.server.i18n.I18n; |
| |
| |
| /** |
| * A change log store that keeps it's information in memory. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class MemoryChangeLogStore implements TaggableChangeLogStore |
| { |
| |
| private static final String REV_FILE = "revision"; |
| private static final String TAG_FILE = "tags"; |
| private static final String CHANGELOG_FILE = "changelog.dat"; |
| |
| /** An incremental number giving the current revision */ |
| private long currentRevision; |
| |
| /** The latest tag */ |
| private Tag latest; |
| |
| /** A Map of tags and revisions */ |
| private final Map<Long, Tag> tags = new HashMap<>( 100 ); |
| |
| private final List<ChangeLogEvent> events = new ArrayList<>(); |
| private File workingDirectory; |
| |
| /** The DirectoryService */ |
| private DirectoryService directoryService; |
| |
| private TimeProvider timeProvider = TimeProvider.DEFAULT; |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag tag( long revision ) |
| { |
| if ( tags.containsKey( revision ) ) |
| { |
| return tags.get( revision ); |
| } |
| |
| latest = new Tag( revision, null ); |
| tags.put( revision, latest ); |
| |
| return latest; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag tag() |
| { |
| if ( ( latest != null ) && ( latest.getRevision() == currentRevision ) ) |
| { |
| return latest; |
| } |
| |
| latest = new Tag( currentRevision, null ); |
| tags.put( currentRevision, latest ); |
| return latest; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag tag( String description ) |
| { |
| if ( ( latest != null ) && ( latest.getRevision() == currentRevision ) ) |
| { |
| return latest; |
| } |
| |
| latest = new Tag( currentRevision, description ); |
| tags.put( currentRevision, latest ); |
| return latest; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void init( DirectoryService service ) throws LdapException |
| { |
| workingDirectory = service.getInstanceLayout().getLogDirectory(); |
| this.directoryService = service; |
| this.timeProvider = service.getTimeProvider(); |
| |
| try |
| { |
| loadRevision(); |
| loadTags(); |
| loadChangeLog(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapException( ioe.getMessage(), ioe ); |
| } |
| } |
| |
| |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| private void loadRevision() throws IOException |
| { |
| File revFile = new File( workingDirectory, REV_FILE ); |
| |
| if ( revFile.exists() ) |
| { |
| try ( BufferedReader reader = Files.newBufferedReader( revFile.toPath(), StandardCharsets.UTF_8 ) ) |
| { |
| String line = reader.readLine(); |
| currentRevision = Long.parseLong( line ); |
| } |
| } |
| } |
| |
| |
| private void saveRevision() throws IOException |
| { |
| File revFile = new File( workingDirectory, REV_FILE ); |
| |
| if ( revFile.exists() && !revFile.delete() ) |
| { |
| throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, revFile.getAbsolutePath() ) ); |
| } |
| |
| |
| try ( PrintWriter out = new PrintWriter( Files.newBufferedWriter( revFile.toPath(), StandardCharsets.UTF_8 ) ) ) |
| { |
| out.println( currentRevision ); |
| out.flush(); |
| } |
| } |
| |
| |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| private void saveTags() throws IOException |
| { |
| File tagFile = new File( workingDirectory, TAG_FILE ); |
| |
| if ( tagFile.exists() && !tagFile.delete() ) |
| { |
| throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, tagFile.getAbsolutePath() ) ); |
| } |
| |
| OutputStream out = null; |
| |
| try |
| { |
| out = Files.newOutputStream( tagFile.toPath() ); |
| |
| Properties props = new Properties(); |
| |
| for ( Tag tag : tags.values() ) |
| { |
| String key = String.valueOf( tag.getRevision() ); |
| |
| if ( tag.getDescription() == null ) |
| { |
| props.setProperty( key, "null" ); |
| } |
| else |
| { |
| props.setProperty( key, tag.getDescription() ); |
| } |
| } |
| |
| props.store( out, null ); |
| out.flush(); |
| } |
| finally |
| { |
| if ( out != null ) |
| { |
| out.close(); |
| } |
| } |
| } |
| |
| |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| private void loadTags() throws IOException |
| { |
| File revFile = new File( workingDirectory, REV_FILE ); |
| |
| if ( revFile.exists() ) |
| { |
| Properties props = new Properties(); |
| InputStream in = null; |
| |
| try |
| { |
| in = Files.newInputStream( revFile.toPath() ); |
| props.load( in ); |
| ArrayList<Long> revList = new ArrayList<>(); |
| |
| for ( Object key : props.keySet() ) |
| { |
| revList.add( Long.valueOf( ( String ) key ) ); |
| } |
| |
| Collections.sort( revList ); |
| Tag tag = null; |
| |
| // @todo need some serious syncrhoization here on tags |
| tags.clear(); |
| |
| for ( Long lkey : revList ) |
| { |
| String rev = String.valueOf( lkey ); |
| String desc = props.getProperty( rev ); |
| |
| if ( ( desc != null ) && desc.equals( "null" ) ) |
| { |
| tag = new Tag( lkey, null ); |
| } |
| else |
| { |
| tag = new Tag( lkey, desc ); |
| } |
| |
| tags.put( lkey, tag ); |
| } |
| |
| latest = tag; |
| } |
| finally |
| { |
| if ( in != null ) |
| { |
| in.close(); |
| } |
| } |
| } |
| } |
| |
| |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| private void loadChangeLog() throws IOException |
| { |
| File file = new File( workingDirectory, CHANGELOG_FILE ); |
| |
| if ( file.exists() ) |
| { |
| try ( ObjectInputStream in = new ObjectInputStream( Files.newInputStream( file.toPath() ) ) ) |
| { |
| int size = in.readInt(); |
| |
| ArrayList<ChangeLogEvent> changeLogEvents = new ArrayList<>( size ); |
| |
| for ( int i = 0; i < size; i++ ) |
| { |
| ChangeLogEvent event = ChangeLogEventSerializer.deserialize( directoryService.getSchemaManager(), |
| in ); |
| event.getCommitterPrincipal().setSchemaManager( directoryService.getSchemaManager() ); |
| changeLogEvents.add( event ); |
| } |
| |
| // @todo man o man we need some synchronization later after getting this to work |
| this.events.clear(); |
| this.events.addAll( changeLogEvents ); |
| } |
| } |
| } |
| |
| |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| private void saveChangeLog() throws IOException |
| { |
| File file = new File( workingDirectory, CHANGELOG_FILE ); |
| |
| if ( file.exists() && !file.delete() ) |
| { |
| throw new IOException( I18n.err( I18n.ERR_726_FILE_UNDELETABLE, file.getAbsolutePath() ) ); |
| } |
| |
| file.createNewFile(); |
| |
| try ( ObjectOutputStream out = new ObjectOutputStream( Files.newOutputStream( file.toPath() ) ) ) |
| { |
| out.writeInt( events.size() ); |
| |
| for ( ChangeLogEvent event : events ) |
| { |
| ChangeLogEventSerializer.serialize( event, out ); |
| } |
| |
| out.flush(); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void sync() throws LdapException |
| { |
| try |
| { |
| saveRevision(); |
| saveTags(); |
| saveChangeLog(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapException( ioe.getMessage(), ioe ); |
| } |
| } |
| |
| |
| /** |
| * Save logs, tags and revision on disk, and clean everything in memory |
| */ |
| @Override |
| public void destroy() throws LdapException |
| { |
| try |
| { |
| saveRevision(); |
| saveTags(); |
| saveChangeLog(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapException( ioe.getMessage(), ioe ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public long getCurrentRevision() |
| { |
| return currentRevision; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, LdifEntry reverse ) |
| { |
| currentRevision++; |
| ChangeLogEvent event = new ChangeLogEvent( currentRevision, |
| DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ), |
| principal, forward, reverse ); |
| events.add( event ); |
| |
| return event; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ChangeLogEvent log( LdapPrincipal principal, LdifEntry forward, List<LdifEntry> reverses ) |
| { |
| currentRevision++; |
| ChangeLogEvent event = new ChangeLogEvent( currentRevision, |
| DateUtils.getGeneralizedTime( timeProvider ), principal, forward, reverses ); |
| events.add( event ); |
| |
| return event; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ChangeLogEvent lookup( long revision ) |
| { |
| if ( revision < 0 ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_239 ) ); |
| } |
| |
| if ( revision > getCurrentRevision() ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_240 ) ); |
| } |
| |
| return events.get( ( int ) revision ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Cursor<ChangeLogEvent> find() |
| { |
| return new ListCursor<>( events ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Cursor<ChangeLogEvent> findBefore( long revision ) |
| { |
| return new ListCursor<>( events, ( int ) revision ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Cursor<ChangeLogEvent> findAfter( long revision ) |
| { |
| return new ListCursor<>( ( int ) revision, events ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Cursor<ChangeLogEvent> find( long startRevision, long endRevision ) |
| { |
| return new ListCursor<>( ( int ) startRevision, events, ( int ) ( endRevision + 1 ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag getLatest() |
| { |
| return latest; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag removeTag( long revision ) |
| { |
| return tags.remove( revision ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Tag tag( long revision, String descrition ) |
| { |
| if ( tags.containsKey( revision ) ) |
| { |
| return tags.get( revision ); |
| } |
| |
| latest = new Tag( revision, descrition ); |
| tags.put( revision, latest ); |
| return latest; |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| @Override |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "MemoryChangeLog\n" ); |
| sb.append( "latest tag : " ).append( latest ).append( '\n' ); |
| |
| sb.append( "Nb of events : " ).append( events.size() ).append( '\n' ); |
| |
| int i = 0; |
| |
| for ( ChangeLogEvent event : events ) |
| { |
| sb.append( "event[" ).append( i++ ).append( "] : " ); |
| sb.append( "\n---------------------------------------\n" ); |
| sb.append( event ); |
| sb.append( "\n---------------------------------------\n" ); |
| } |
| |
| return sb.toString(); |
| } |
| } |