/*
* Copyright 2014 WANdisco
*
* WANdisco 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 c5db.log;
import c5db.interfaces.log.SequentialEntryCodec;
import c5db.util.CheckedSupplier;
import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import static c5db.log.SequentialLog.LogEntryNotFound;
/**
* Service to handle persistence for different quorums' logs. Each quorum's log is
* represented by a list of persisted data stores. Apart from administrative actions,
* such as deleting old and unneeded log records, stores may only be added to or
* removed from the end of the list. At any given time, the "current" data store
* is the most recent one, at the end of the list. Data stores are accessed and
* manipulated via returned BytePersistence instances.
*/
public interface LogPersistenceService<P extends LogPersistenceService.BytePersistence> {
/**
* Return a persistence instance referring to the latest (most recent) data
* store persisted for the given quorum. If there is none, return null.
*
* @param quorumId ID of the quorum.
* @return A new, open BytePersistence pointing to the latest log data store
* for the given quorum, or null if there is none.
* @throws IOException
*/
@Nullable
P getCurrent(String quorumId) throws IOException;
/**
* Create a new, empty data store, and return an object representing it. The store
* is temporary; if not appended to the log, it will not persist.
*
* @param quorumId ID of the quorum.
* @return A new, open persistence instance.
* @throws IOException
*/
@NotNull
P create(String quorumId) throws IOException;
/**
* Atomically add the given persistence to the log for the given quorum. After this
* method call, getCurrentPersistence will return an instance referring to the same
* underlying data store as the given instance (but not necessarily the same instance).
*
* @param quorumId Quorum ID.
* @param persistence The persistence to append to the log, making it current.
* @throws IOException
*/
void append(String quorumId, @NotNull P persistence) throws IOException;
/**
* Atomically remove and delete the data store underlying the "current" persistence
* for the given quorum, in effect truncating its contents from the log record, and
* rendering any instances referring to this data store invalid.
*
* @param quorumId Quorum ID.
* @throws IOException
*/
void truncate(String quorumId) throws IOException;
/**
* Return an list of the data stores for this quorum, in order from most recent
* to least recent. This method may perform IO, to determine which stores are
* present. Rather than the list directly containing persistence objects, it contains
* Suppliers that return them when invoked; that way additional IO is only performed
* if needed.
* <p>
* The first element (index 0) returned will be a Supplier that returns the element
* returned by getCurrent. The user is responsible for closing any BytePersistence
* objects returned by the Suppliers.
*
* @return A new ImmutableList.
*/
ImmutableList<CheckedSupplier<P, IOException>> getList(String quorumId) throws IOException;
/**
* Represents a single store of persisted log data; a file-like abstraction.
*/
interface BytePersistence extends AutoCloseable {
/**
* Determine if the store is empty.
*
* @return True if empty, otherwise false.
* @throws IOException
*/
boolean isEmpty() throws IOException;
/**
* Get the size in bytes of the data, equal to the position/address of
* next byte to be appended.
*
* @return Size in bytes.
* @throws IOException
*/
long size() throws IOException;
/**
* Append data.
*
* @param buffers Data to append.
* IOException if the persistence is closed, or if the underlying object is inaccessible.
*/
void append(ByteBuffer[] buffers) throws IOException;
/**
* Get a new reader of the data. Each reader is independent of any other,
* and the caller takes responsibility for releasing associated resources.
*
* @return A new reader instance.
* @throws IOException
*/
PersistenceReader getReader() throws IOException;
/**
* Truncate data from the end, to a certain size.
*
* @param size New size of the data, equal to the position/address of the next
* byte to be appended after the truncation. After calling this method,
* the size() method will return this value of size.
* @throws IOException if the persistence is closed, or if the underlying object is inaccessible.
*/
void truncate(long size) throws IOException;
/**
* Sync previous operations to the underlying medium.
*
* @throws IOException if the persistence is closed, or if the underlying object is inaccessible.
*/
void sync() throws IOException;
/**
* Release held resources.
*
* @throws IOException
*/
void close() throws IOException;
}
/**
* Seekable reader of a BytePersistence that keeps track of its own position within
* the data.
*/
interface PersistenceReader extends ReadableByteChannel, AutoCloseable {
long position() throws IOException;
void position(long newPos) throws IOException;
}
/**
* Service in charge of finding entries in a persistence by their sequence number, rather
* than by their byte position/address. To facilitate fast navigation, it may build an index
* of already-written entries and their locations.
*/
interface PersistenceNavigator {
/**
* Updates the navigator with information about an entry to be written, so that it can
* optionally incorporate that information into an internal index.
*
* @param seqNum Sequence number of an entry being written.
* @param byteAddress Byte address of the start of the entry within the persistence.
* @throws IOException
*/
void notifyLogging(long seqNum, long byteAddress) throws IOException;
/**
* Updates the navigator with information about the location of an entry within the
* persistence, guaranteeing it will be added to the internal index.
*
* @param seqNum Sequence number the entry.
* @param byteAddress Byte address of the start of the entry within the persistence.
* @throws IOException
*/
void addToIndex(long seqNum, long byteAddress) throws IOException;
/**
* Updates the navigator that a truncation is being performed on the log, which may
* necessitate updating the navigator's internal index.
*
* @param seqNum Sequence number of the start of the truncation; i.e., entries greater than
* or equal to this sequence number are being deleted.
* @throws IOException
*/
void notifyTruncation(long seqNum) throws IOException;
/**
* Find the position or address of an entry within the persistence, using its sequence number.
*
* @param seqNum Sequence number of entry to find.
* @return Byte position or address.
* @throws IOException, LogEntryNotFound
*/
long getAddressOfEntry(long seqNum) throws IOException, LogEntryNotFound;
/**
* Return an input stream ready to read from the persistence starting at the beginning of the
* entry with the specified sequence number.
*
* @param fromSeqNum Sequence number to read from.
* @return A new input stream; the caller takes responsibility for closing it.
* @throws IOException, LogEntryNotFound
*/
InputStream getStreamAtSeqNum(long fromSeqNum) throws IOException, LogEntryNotFound;
/**
* Return an input stream ready to read from the persistence starting at the beginning of the
* first entry in the persistence. If there are no entries, the stream will be positioned at
* the end of the persistence.
*
* @return A new input stream; the caller takes responsibility for closing it.
* @throws IOException
*/
InputStream getStreamAtFirstEntry() throws IOException;
/**
* Return an input stream ready to read from the persistence starting at the beginning of the
* last entry in the persistence. Throws an unchecked exception if the log is empty.
*
* @return A new input stream; the caller takes responsibility for closing it.
* @throws IOException
*/
InputStream getStreamAtLastEntry() throws IOException;
}
interface PersistenceNavigatorFactory {
PersistenceNavigator create(BytePersistence persistence, SequentialEntryCodec<?> encoding, long offset);
}
}