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

net.fwbrasil.activate.storage.prevalent.PrevalentJournal.scala Maven / Gradle / Ivy

The newest version!
package net.fwbrasil.activate.storage.prevalent

import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.serialization.Serializer
import java.nio.ByteOrder
import java.io.FileOutputStream
import java.io.ObjectOutputStream
import java.io.ObjectInputStream
import java.io.FileInputStream
import scala.collection.JavaConversions._
import net.fwbrasil.activate.storage.memory.BasePrevalentTransaction
import net.fwbrasil.activate.storage.memory.BasePrevalentStorageSystem
import java.io.ObjectStreamClass

class PrevalentJournal(directory: File, serializer: Serializer, fileSize: Int, bufferPoolSize: Int) {

    private val fileSeparator = System.getProperty("file.separator")

    directory.mkdirs

    private var currentBuffer: ByteBuffer = null
    private var currentBufferIdx = 0

    private val bufferPool = new DirectBufferPool(fileSize, bufferPoolSize)

    Runtime.getRuntime.addShutdownHook(
        new Thread {
            override def run = {
                cleanCurrentBuffer
                bufferPool.destroy
            }
        })

    private def clear = {
        cleanCurrentBuffer
        currentBufferIdx = 0
    }

    private def cleanCurrentBuffer =
        if (currentBuffer != null) {
            byteBufferCleaner.cleanDirect(currentBuffer)
            currentBuffer = null
        }

    def takeSnapshot(system: BasePrevalentStorageSystem) = {
        val snapshotFile = new File(directory, fileName(nextFileIndex, ".snapshot"))
        require(snapshotFile.createNewFile)
        val stream = new ObjectOutputStream(new FileOutputStream(snapshotFile))
        stream.writeObject(system)
        stream.close
    }

    def add(transaction: BasePrevalentTransaction): Unit = {
        val temp = bufferPool.pop
        try {
            serializeToTempBuffer(temp, transaction)
            val totalSize = temp.remaining
            val writeBuffer = createWriteBuffer(totalSize)
            writeBuffer.put(temp)
        } finally {
            temp.clear
            bufferPool.push(temp)
        }
    }

    def recover(implicit ctx: ActivateContext) = {
        clear
        val (nextFileId, system) = recoverSnapshot
        ctx.hidrateEntities(system.entities)
        val buffers = directoryBuffers(nextFileId)
        for (buffer <- buffers) yield {
            recoverTransactions(system, buffer)
            cleanCurrentBuffer
            currentBuffer = buffer
        }
        if (buffers.isEmpty) {
            currentBuffer = createBuffer(nextFileId)
            currentBufferIdx = nextFileId + buffers.size
        } else
            currentBufferIdx = nextFileId + buffers.size - 1
        system
    }

    private def recoverSnapshot(implicit ctx: ActivateContext) =
        snapshotFiles.lastOption.map { file =>
            val stream = new ObjectInputStream(new FileInputStream(file)) {
                override def resolveClass(desc: ObjectStreamClass): Class[_] =
                    try Class.forName(desc.getName, false, classOf[PrevalentJournal].getClassLoader)
                    catch {
                        case e: ClassNotFoundException =>
                            super.resolveClass(desc)
                    }
            }
            val system = stream.readObject.asInstanceOf[BasePrevalentStorageSystem]

            val idx = fileNameToIndex(file)
            (idx + 1, system)
        }.getOrElse((0, new BasePrevalentStorageSystem))

    private def serializeToTempBuffer(temp: ByteBuffer, transaction: BasePrevalentTransaction) = {
        PrevalentTransactionSerializer.write(transaction)(temp)
        val totalSize = temp.position
        temp.limit(temp.position)
        temp.rewind
        temp
    }

    private def directoryBuffers(startIdx: Int) = {
        val files = journalFiles.filter(file => fileNameToIndex(file) >= startIdx)
        verifyJournalFiles(files, startIdx)
        files.map(f => fileToBuffer(f, f.length))
    }

    private def createBuffer(fileIdx: Int) =
        fileToBuffer(new File(newFilePath(fileIdx)), fileSize)

    private def fileName(idx: Int, sufix: String = ".journal") =
        idx.formatted("%020d") + sufix

    private def fileToBuffer(file: File, size: Long) =
        new RandomAccessFile(file, "rw").getChannel
            .map(FileChannel.MapMode.READ_WRITE, 0, size)
            .order(ByteOrder.nativeOrder)

    private def createWriteBuffer(totalSize: Int) =
        synchronized {
            val buffer = createNewBufferIfNecessary(totalSize)
            try buffer.duplicate
            finally buffer.position(buffer.position + totalSize)
        }

    private def newFilePath(fileIdx: Int) =
        directory + fileSeparator + fileName(fileIdx)

    private def recoverTransactions(
        system: BasePrevalentStorageSystem,
        buffer: ByteBuffer)(implicit ctx: ActivateContext) = {
        var transactionOption = PrevalentTransactionSerializer.read(buffer, ctx)
        while (transactionOption.isDefined) {
            transactionOption.get.execute(system)
            transactionOption = PrevalentTransactionSerializer.read(buffer, ctx)
        }
    }

    private def readTransactionBytes(buffer: ByteBuffer) = {
        val transactionSize = buffer.getInt
        val bytes = new Array[Byte](transactionSize)
        buffer.get(bytes)
        bytes
    }

    private def createNewBufferIfNecessary(totalSize: Int) = {
        if (currentBuffer.remaining < totalSize) {
            currentBuffer = createBuffer(nextFileIndex)
        }
        currentBuffer
    }

    private def nextFileIndex = {
        currentBufferIdx += 1
        currentBufferIdx
    }

    private def fileNameToIndex(file: File) =
        file.getName
            .replace(".journal", "")
            .replace(".snapshot", "")
            .toInt

    private def journalFiles =
        files.filter(_.getName.endsWith(".journal"))

    private def snapshotFiles =
        files.filter(_.getName.endsWith(".snapshot"))

    private def files =
        directory
            .listFiles
            .toList
            .sortBy(_.getName)

    private def verifyJournalFiles(files: List[File], startIdx: Int) =
        for (idx <- startIdx until files.size)
            if (fileNameToIndex(files(idx)) != idx)
                throw new IllegalStateException(s"Can't find ${fileName(idx)}")

}
    




© 2015 - 2024 Weber Informatics LLC | Privacy Policy