/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.jackrabbit.core.journal;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.jackrabbit.core.data.db.ResettableTempFileInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default temporary record used for appending to some journal.
*/
public class AppendRecord extends AbstractRecord {
/**
* Logger.
*/
private static Logger log = LoggerFactory.getLogger(AppendRecord.class);
/**
* Default prefix for appended records in the file system.
*/
private static final String DEFAULT_PREFIX = "journal";
/**
* Default extension for appended records in the file system.
*/
private static final String DEFAULT_EXT = ".tmp";
/**
* Default size for in-memory records.
*/
private static final int DEFAULT_IN_MEMORY_SIZE = 1024;
/**
* Maximum size for in-memory records.
*/
private static final int MAXIMUM_IN_MEMORY_SIZE = 65536;
/**
* Journal where record is being appended.
*/
private final AbstractJournal journal;
/**
* Producer identifier.
*/
private final String producerId;
/**
* This record's revision.
*/
private long revision;
/**
* Underlying data output.
*/
private DataOutputStream dataOut;
/**
* Underlying byte output.
*/
private ByteArrayOutputStream byteOut;
/**
* Underlying file.
*/
private File file;
/**
* Underlying file output.
*/
private FileOutputStream fileOut;
/**
* Flag indicating whether the output is closed.
*/
private boolean outputClosed;
/**
* Create a new instance of this class.
*
* @param journal journal where record is being appended
* @param producerId producer identifier
*/
public AppendRecord(AbstractJournal journal, String producerId) {
super(journal.getResolver(), journal.getNamePathResolver());
this.journal = journal;
this.producerId = producerId;
this.revision = 0L;
byteOut = new ByteArrayOutputStream(DEFAULT_IN_MEMORY_SIZE);
dataOut = new DataOutputStream(byteOut);
}
/**
* {@inheritDoc}
*/
public String getJournalId() {
return journal.getId();
}
/**
* {@inheritDoc}
*/
public String getProducerId() {
return producerId;
}
/**
* {@inheritDoc}
*/
public long getRevision() {
return revision;
}
/**
* Set the revision this record represents.
*
* @param revision revision
*/
public void setRevision(long revision) {
this.revision = revision;
}
/**
* {@inheritDoc}
*/
public void writeByte(int n) throws JournalException {
checkOutput();
try {
dataOut.writeByte(n);
} catch (IOException e) {
String msg = "I/O error while writing byte.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void writeChar(char c) throws JournalException {
checkOutput();
try {
dataOut.writeChar(c);
} catch (IOException e) {
String msg = "I/O error while writing character.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void writeBoolean(boolean b) throws JournalException {
checkOutput();
try {
dataOut.writeBoolean(b);
} catch (IOException e) {
String msg = "I/O error while writing boolean.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void writeInt(int n) throws JournalException {
checkOutput();
try {
dataOut.writeInt(n);
} catch (IOException e) {
String msg = "I/O error while writing integer.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void writeLong(long n) throws JournalException {
checkOutput();
try {
dataOut.writeLong(n);
} catch (IOException e) {
String msg = "I/O error while writing long.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void writeString(String s) throws JournalException {
checkOutput();
try {
if (s == null) {
dataOut.writeBoolean(true);
} else {
dataOut.writeBoolean(false);
dataOut.writeUTF(s);
}
} catch (IOException e) {
String msg = "I/O error while writing string.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public void write(byte[] b) throws JournalException {
checkOutput();
try {
dataOut.write(b);
} catch (IOException e) {
String msg = "I/O error while writing a byte array.";
throw new JournalException(msg, e);
}
}
/**
* {@inheritDoc}
*/
public long update() throws JournalException {
boolean succeeded = false;
try {
int length = dataOut.size();
closeOutput();
InputStream in = openInput();
try {
journal.append(this, in, length);
succeeded = true;
return length;
} finally {
try {
in.close();
} catch (IOException e) {
String msg = "I/O error while closing stream.";
log.warn(msg, e);
}
}
} finally {
dispose();
journal.unlock(succeeded);
}
}
/**
* {@inheritDoc}
*/
public void cancelUpdate() {
if (!outputClosed) {
dispose();
journal.unlock(false);
}
}
/**
* Open input on record written.
*/
private InputStream openInput() throws JournalException {
if (file != null) {
try {
return new ResettableTempFileInputStream(file);
} catch (IOException e) {
String msg = "Unable to open file input on: " + file.getPath();
throw new JournalException(msg, e);
}
} else {
return new ByteArrayInputStream(byteOut.toByteArray());
}
}
/**
* Check output size and eventually switch to file output.
*
* @throws JournalException
*/
private void checkOutput() throws JournalException {
if (outputClosed) {
throw new IllegalStateException("Output closed.");
}
if (fileOut == null && byteOut.size() >= MAXIMUM_IN_MEMORY_SIZE) {
try {
file = File.createTempFile(DEFAULT_PREFIX, DEFAULT_EXT);
} catch (IOException e) {
String msg = "Unable to create temporary file.";
throw new JournalException(msg, e);
}
try {
fileOut = new FileOutputStream(file);
} catch (FileNotFoundException e) {
String msg = "Unable to open output stream on: " + file.getPath();
throw new JournalException(msg, e);
}
dataOut = new DataOutputStream(new BufferedOutputStream(fileOut));
try {
dataOut.write(byteOut.toByteArray());
} catch (IOException e) {
String msg = "Unable to write in-memory record to file.";
throw new JournalException(msg, e);
}
}
}
/**
* Close output, keeping the underlying file.
*
* @throws JournalException if an error occurs
*/
private void closeOutput() throws JournalException {
if (!outputClosed) {
try {
if (fileOut != null) {
dataOut.flush();
fileOut.getFD().sync();
dataOut.close();
}
} catch (IOException e) {
String msg = "I/O error while closing stream.";
throw new JournalException(msg, e);
} finally {
outputClosed = true;
}
}
}
/**
* Dispose this record, deleting the underlying file.
*/
private void dispose() {
if (!outputClosed) {
try {
dataOut.close();
} catch (IOException e) {
String msg = "I/O error while closing stream.";
log.warn(msg, e);
} finally {
outputClosed = true;
}
}
if (file != null) {
file.delete();
file = null;
}
}
/**
* Unsupported methods when appending.
*/
public byte readByte() throws JournalException {
throw unsupported();
}
public char readChar() throws JournalException {
throw unsupported();
}
public boolean readBoolean() throws JournalException {
throw unsupported();
}
public int readInt() throws JournalException {
throw unsupported();
}
public long readLong() throws JournalException {
throw unsupported();
}
public String readString() throws JournalException {
throw unsupported();
}
public void readFully(byte[] b) throws JournalException {
throw unsupported();
}
private JournalException unsupported() {
String msg = "Reading from an appended record is not supported.";
return new JournalException(msg);
}
}