/*
* Copyright Terracotta, Inc.
*
* Licensed 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.ehcache.transactions.xa.internal.journal;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.transactions.xa.internal.TransactionId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* A persistent, but not durable {@link Journal} implementation.
* This implementation will persist saved states during close and restore them during open. If close is not called,
* all saved states are lost.
*
* @author Ludovic Orban
*/
public class PersistentJournal<K> extends TransientJournal<K> {
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentJournal.class);
private static final String JOURNAL_FILENAME = "journal.data";
protected static class SerializableEntry<K> implements Serializable {
final XAState state;
final boolean heuristic;
final Collection<byte[]> serializedKeys;
protected SerializableEntry(Entry<K> entry, Serializer<K> keySerializer) {
this.state = entry.state;
this.heuristic = entry.heuristic;
this.serializedKeys = new ArrayList<byte[]>();
for (K key : entry.keys) {
ByteBuffer byteBuffer = keySerializer.serialize(key);
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
this.serializedKeys.add(bytes);
}
}
protected Collection<K> deserializeKeys(Serializer<K> keySerializer) throws ClassNotFoundException {
Collection<K> result = new ArrayList<K>();
for (byte[] serializedKey : serializedKeys) {
K key = keySerializer.read(ByteBuffer.wrap(serializedKey));
result.add(key);
}
return result;
}
}
private final File directory;
private final Serializer<K> keySerializer;
public PersistentJournal(File directory, Serializer<K> keySerializer) {
if (directory == null) {
throw new NullPointerException("directory must not be null");
}
if (keySerializer == null) {
throw new NullPointerException("keySerializer must not be null");
}
this.directory = directory;
this.keySerializer = keySerializer;
}
@Override
public void open() throws IOException {
File file = new File(directory, JOURNAL_FILENAME);
if (file.isFile()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
try {
boolean valid = ois.readBoolean();
states.clear();
if (valid) {
@SuppressWarnings("unchecked")
Map<TransactionId, SerializableEntry<K>> readStates = (Map<TransactionId, SerializableEntry<K>>) ois.readObject();
for (Map.Entry<TransactionId, SerializableEntry<K>> entry : readStates.entrySet()) {
SerializableEntry<K> value = entry.getValue();
states.put(entry.getKey(), new Entry<K>(value.state, value.heuristic, value.deserializeKeys(keySerializer)));
}
}
} catch (IOException ioe) {
LOGGER.warn("Cannot read XA journal, truncating it", ioe);
} catch (ClassNotFoundException cnfe) {
LOGGER.warn("Cannot deserialize XA journal contents, truncating it", cnfe);
} finally {
ois.close();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
try {
oos.writeObject(false);
} finally {
oos.close();
}
}
}
}
@Override
public void close() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(directory, JOURNAL_FILENAME)));
try {
oos.writeBoolean(true);
Map<TransactionId, SerializableEntry<K>> toSerialize = new HashMap<TransactionId, SerializableEntry<K>>();
for (Map.Entry<TransactionId, Entry<K>> entry : states.entrySet()) {
TransactionId key = entry.getKey();
Entry<K> value = entry.getValue();
toSerialize.put(key, new SerializableEntry<K>(value, keySerializer));
}
oos.writeObject(toSerialize);
states.clear();
} finally {
oos.close();
}
}
}