/* * 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.SequentialEntry; import c5db.interfaces.log.SequentialEntryCodec; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import static c5db.log.LogPersistenceService.BytePersistence; import static c5db.log.LogPersistenceService.PersistenceNavigator; /** * Sequential log that encodes and decodes its entries to bytes, persisting them to a BytePersistence. */ public class EncodedSequentialLog<E extends SequentialEntry> implements SequentialLog<E> { private final BytePersistence persistence; private final SequentialEntryCodec<E> codec; private final PersistenceNavigator persistenceNavigator; public EncodedSequentialLog(BytePersistence persistence, SequentialEntryCodec<E> codec, PersistenceNavigator persistenceNavigator) { this.persistence = persistence; this.codec = codec; this.persistenceNavigator = persistenceNavigator; } @Override public void append(List<E> entries) throws IOException { for (E entry : entries) { persistenceNavigator.notifyLogging(entry.getSeqNum(), persistence.size()); persistence.append(codec.encode(entry)); } } @Override public List<E> subSequence(long start, long end) throws IOException, LogEntryNotFound, LogEntryNotInSequence { final List<E> readEntries = new ArrayList<>(); try (InputStream reader = persistenceNavigator.getStreamAtSeqNum(start)) { long seqNum; do { E entry = codec.decode(reader); readEntries.add(entry); seqNum = entry.getSeqNum(); } while (seqNum < end - 1); } catch (EOFException e) { throw new LogEntryNotFound("EOF reached before finding all requested entries: seqNum range [" + start + ", " + end + ")"); } ensureAscendingWithNoGaps(readEntries); return readEntries; } @Override public boolean isEmpty() throws IOException { return persistence.isEmpty(); } @Override public E getLastEntry() throws IOException { if (isEmpty()) { return null; } try (InputStream inputStream = persistenceNavigator.getStreamAtLastEntry()) { return codec.decode(inputStream); } } @Override public SequentialEntryIterator<E> iterator() throws IOException { return new EncodedSequentialEntryIterator<>(persistenceNavigator, codec); } @Override public void truncate(long seqNum) throws IOException, LogEntryNotFound { long truncationPos = persistenceNavigator.getAddressOfEntry(seqNum); persistence.truncate(truncationPos); persistenceNavigator.notifyTruncation(seqNum); } @Override public void sync() throws IOException { persistence.sync(); } @Override public void close() throws IOException { persistence.close(); } private void ensureAscendingWithNoGaps(List<E> entries) throws LogEntryNotInSequence { final int size = entries.size(); if (size > 0) { for (int i = 1; i < size; i++) { if (entries.get(i).getSeqNum() != entries.get(i - 1).getSeqNum() + 1) { throw new LogEntryNotInSequence(); } } } } }