All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dooapp.gaedo.prevalence.space.basic.DefaultSpacePersister Maven / Gradle / Ivy

Go to download

A gaedo-inspired prevalence layer and the associated service, for making the most gaedo-efficient use of that layer

There is a newer version: 1.0.16
Show newest version
package com.dooapp.gaedo.prevalence.space.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.dooapp.gaedo.prevalence.space.Command;
import com.dooapp.gaedo.prevalence.space.ExecutionSpace;
import com.dooapp.gaedo.prevalence.space.StorageSpace;

/**
 * Default implementation of space persister
 * 
 * @author ndx
 * 
 */
public class DefaultSpacePersister implements SpacePersister {
	private static final String SPACE_NAME = "space.serialized";

	private static final Logger logger = Logger
			.getLogger(DefaultSpacePersister.class.getName());
	
	private class StorageSpaceSynchronizer implements Runnable {

		private StorageSpace storageSpace;

		public StorageSpaceSynchronizer(StorageSpace storageSpace) {
			this.storageSpace = storageSpace;
		}

		@Override
		public void run() {
			save(storageSpace);
		}
		
	}

	/**
	 * Some config informations that will be stored
	 */
	private DefaultSpacePersisterConfig config = new DefaultSpacePersisterConfig();

	/**
	 * Directory where all commands should be logged and where space will be
	 * persisted
	 */
	private File storageDirectory;

	public DefaultSpacePersister() {
		this(new File(".", DefaultSpacePersister.class.getSimpleName()));
	}

	public DefaultSpacePersister(File storageDirectory) {
		super();
		setStorageDirectory(storageDirectory);
	}

	/**
	 * Formatter used to generate file name from command index
	 */
	private static transient DecimalFormat format = new DecimalFormat("0");

	/**
	 * Logs command to a file named from commandIndex. Notice only Commands
	 * implementing the Serializable interface will be logged, as the other ones
	 * don't need to be logged.
	 * 
	 * @param toLog
	 *            command to log
	 */
	@Override
	public  void logCommand(
			Command toLog) {
		if (toLog instanceof Serializable) {
			// This is a kind of double effect synchronized locking, as all code
			// from this class should only be called by a specific executor
			// service
			synchronized (this) {
				writeCommand(config.getCommandIndexAndIncrement(), toLog);
			}
		}
	}

	protected  void writeCommand(
			long commandIndex, Command command) {
		File toRead = buildCommandLogFileName(commandIndex);
		ObjectOutputStream objectStream = null;
		try {
			objectStream = new ObjectOutputStream(new FileOutputStream(toRead));
			objectStream.writeObject(command);
		} catch (IOException e) {
			throw new ConsistencyException(e);
		} finally {
			if (objectStream != null) {
				try {
					objectStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Read a given command
	 * 
	 * @param 
	 * @param 
	 * @param commandIndex
	 * @return
	 */
	protected  Command readCommand(
			long commandIndex) {
		File toRead = buildCommandLogFileName(commandIndex);
		ObjectInputStream objectStream = null;
		try {
			objectStream = new ObjectInputStream(new FileInputStream(toRead));

			return (Command) objectStream.readObject();
		} catch (IOException e) {
			throw new ConsistencyException(e);
		} catch (ClassNotFoundException e) {
			throw new ConsistencyException(e);
		} finally {
			if (objectStream != null) {
				try {
					objectStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Load a collection of command from a collection of command log files.
	 * 
	 * @return
	 */
	 Collection> loadCommands(
			long low, long high) {
		Collection> returned = new LinkedList>();
		for (long index = low; index < high; index++) {
			if (buildCommandLogFileName(index).exists()) {
				// Weird generics bug, no ? Will solve it later
				returned.add((Command) readCommand(index));
			} else {
				// If command does not exists, stop here, as next commands won't
				// be appliable
				break;
			}
		}
		return returned;
	}

	public File getStorageDirectory() {
		return storageDirectory;
	}

	/**
	 * Sets {@link #storageDirectory}. notice that if argument is not a
	 * directory, its parent will be used instead.
	 * 
	 * @param storageDirectory
	 */
	public void setStorageDirectory(File storageDirectory) {
		if (!storageDirectory.exists()) {
			storageDirectory.mkdirs();
		}
		if (!storageDirectory.isDirectory()) {
			storageDirectory = storageDirectory.getParentFile();
		}
		this.storageDirectory = storageDirectory;
	}

	/**
	 * Get a command log File object from the {@link #commandIndex} and
	 * {@link #storageDirectory}
	 * 
	 * @return {@link #storageDirectory}+"/command_"+{@link #commandIndex}
	 *         +".log"
	 */
	File buildCommandLogFileName(long commandIndex) {
		return new File(getStorageDirectory(), format.format(commandIndex)
				+ ".commandLog");
	}

	public long getCommandIndex() {
		return config.getCommandIndex();
	}

	/**
	 * Get file name holding all space content
	 * 
	 * @return
	 */
	File getSpaceFile() {
		return new File(getStorageDirectory(), SPACE_NAME);
	}

	/**
	 * When trying to restore, we first try to load file given by
	 * {@link #getSpaceFile()}, create an object input stream on it, and read it
	 * content.
	 * 
	 * @param storageSpace
	 *            default storage space is none was memorized
	 * @return used storage space
	 */
	@Override
	public  StorageSpace restore(
			StorageSpace storageSpace) {
		StorageSpace returned = storageSpace;
		File spaceFile = getSpaceFile();
		if (spaceFile.exists()) {
			ObjectInputStream stream = null;
			try {
				stream = new ObjectInputStream(
						new FileInputStream(spaceFile));
				// First object to read is space persister config
				config = (DefaultSpacePersisterConfig) stream.readObject();
				// Then we have to read full space data
				returned = (StorageSpace) stream
						.readObject();
			} catch (Exception e) {
				logger.log(Level.INFO, "space was not readable", e);
			} finally {
				try {
					stream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		// Now, load and apply all remaining commands
		Collection> toApply = loadCommands(
				config.getSpacePersistedAt(), Long.MAX_VALUE);
		config.setCommandIndex(config.getSpacePersistedAt()+toApply.size());
		for (Command command : toApply) {
			command.execute(returned);
		}
		// Finally, re-persist all to have a compressed and consistent space
		save(returned);
		return returned;
	}

	/**
	 * Save storage space and all required data
	 */
	public  void save(StorageSpace storageSpace) {
		// Now remove all command logs
		for(long index=config.getSpacePersistedAt(); index<=config.getCommandIndex(); index++) {
			buildCommandLogFileName(index).delete();
			config.setSpacePersistedAt(index);
			updateSpaceFile(storageSpace);
		}
	}

	/**
	 * Update space file with new values of both config and storageSapce
	 * @param 
	 * @param storageSpace
	 */
	private  void updateSpaceFile(StorageSpace storageSpace) {
		File spaceFile = getSpaceFile();
		ObjectOutputStream persister = null;
		try {
			persister = new ObjectOutputStream(new FileOutputStream(spaceFile));
			persister.writeObject(config);
			persister.writeObject(storageSpace);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				persister.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Each second, the storage space will be resynchronized on disk
	 */
	@Override
	public  void install(
			ScheduledExecutorService executor, StorageSpace storageSpace) {
		executor.scheduleWithFixedDelay(new StorageSpaceSynchronizer(storageSpace), 1, 1, TimeUnit.SECONDS);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy