/*
* 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 tachyon.client;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import tachyon.Constants;
import tachyon.UnderFileSystem;
import tachyon.util.CommonUtils;
/**
* <code>FileOutStream</code> implementation of TachyonFile. It can only be gotten by
* calling the methods in <code>tachyon.client.TachyonFile</code>, but can not be initialized by
* the client code.
*/
public class FileOutStream extends OutStream {
private final Logger LOG = Logger.getLogger(Constants.LOGGER_TYPE);
private final long BLOCK_CAPACITY;
private BlockOutStream mCurrentBlockOutStream;
private long mCurrentBlockId;
private long mCurrentBlockLeftByte;
private List<BlockOutStream> mPreviousBlockOutStreams;
private long mCachedBytes;
private OutputStream mCheckpointOutputStream = null;
private String mUnderFsFile = null;
private boolean mClosed = false;
private boolean mCancel = false;
/**
* @param file
* the output file
* @param opType
* the OutStream's write type
* @param ufsConf
* the under file system configuration
* @throws IOException
*/
FileOutStream(TachyonFile file, WriteType opType, Object ufsConf) throws IOException {
super(file, opType);
BLOCK_CAPACITY = file.getBlockSizeByte();
// TODO Support and test append.
mCurrentBlockOutStream = null;
mCurrentBlockId = -1;
mCurrentBlockLeftByte = 0;
mPreviousBlockOutStreams = new ArrayList<BlockOutStream>();
mCachedBytes = 0;
if (WRITE_TYPE.isThrough()) {
mUnderFsFile = CommonUtils.concat(TFS.createAndGetUserUnderfsTempFolder(), FILE.FID);
UnderFileSystem underfsClient = UnderFileSystem.get(mUnderFsFile, ufsConf);
if (BLOCK_CAPACITY > Integer.MAX_VALUE) {
throw new IOException("BLOCK_CAPCAITY (" + BLOCK_CAPACITY + ") can not bigger than "
+ Integer.MAX_VALUE);
}
mCheckpointOutputStream = underfsClient.create(mUnderFsFile, (int) BLOCK_CAPACITY);
}
}
@Override
public void cancel() throws IOException {
mCancel = true;
close();
}
@Override
public void close() throws IOException {
if (!mClosed) {
if (mCurrentBlockOutStream != null) {
mPreviousBlockOutStreams.add(mCurrentBlockOutStream);
}
Boolean canComplete = false;
if (WRITE_TYPE.isThrough()) {
if (mCancel) {
mCheckpointOutputStream.close();
UnderFileSystem underFsClient = UnderFileSystem.get(mUnderFsFile);
underFsClient.delete(mUnderFsFile, false);
} else {
mCheckpointOutputStream.flush();
mCheckpointOutputStream.close();
TFS.addCheckpoint(FILE.FID);
canComplete = true;
}
}
if (WRITE_TYPE.isCache()) {
try {
if (mCancel) {
for (BlockOutStream bos : mPreviousBlockOutStreams) {
bos.cancel();
}
} else {
for (BlockOutStream bos : mPreviousBlockOutStreams) {
bos.close();
}
canComplete = true;
}
} catch (IOException ioe) {
if (WRITE_TYPE.isMustCache()) {
LOG.error(ioe.getMessage());
throw new IOException("Fail to cache: " + WRITE_TYPE);
} else {
LOG.warn("Fail to cache for: " + ioe.getMessage());
}
}
}
if (canComplete) {
if (WRITE_TYPE.isAsync()) {
TFS.asyncCheckpoint(FILE.FID);
}
TFS.completeFile(FILE.FID);
}
}
mClosed = true;
}
@Override
public void flush() throws IOException {
// TODO We only flush the checkpoint output stream. Flush for RAMFS block streams.
if (WRITE_TYPE.isThrough()) {
mCheckpointOutputStream.flush();
}
}
private void getNextBlock() throws IOException {
if (mCurrentBlockId != -1) {
if (mCurrentBlockLeftByte != 0) {
throw new IOException("The current block still has space left, no need to get new block");
}
mPreviousBlockOutStreams.add(mCurrentBlockOutStream);
}
if (WRITE_TYPE.isCache()) {
mCurrentBlockId = TFS.getBlockIdBasedOnOffset(FILE.FID, mCachedBytes);
mCurrentBlockLeftByte = BLOCK_CAPACITY;
mCurrentBlockOutStream =
new BlockOutStream(FILE, WRITE_TYPE, (int) (mCachedBytes / BLOCK_CAPACITY));
}
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
|| ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
if (WRITE_TYPE.isCache()) {
try {
int tLen = len;
int tOff = off;
while (tLen > 0) {
if (mCurrentBlockLeftByte == 0) {
getNextBlock();
} else if (mCurrentBlockLeftByte < 0 || mCurrentBlockOutStream == null) {
throw new IOException("mCurrentBlockLeftByte " + mCurrentBlockLeftByte + " "
+ mCurrentBlockOutStream);
}
if (mCurrentBlockLeftByte >= tLen) {
mCurrentBlockOutStream.write(b, tOff, tLen);
mCurrentBlockLeftByte -= tLen;
mCachedBytes += tLen;
tOff += tLen;
tLen = 0;
} else {
mCurrentBlockOutStream.write(b, tOff, (int) mCurrentBlockLeftByte);
tOff += mCurrentBlockLeftByte;
tLen -= mCurrentBlockLeftByte;
mCachedBytes += mCurrentBlockLeftByte;
mCurrentBlockLeftByte = 0;
}
}
} catch (IOException ioe) {
if (WRITE_TYPE.isMustCache()) {
LOG.error(ioe.getMessage());
throw new IOException("Fail to cache: " + WRITE_TYPE);
} else {
LOG.warn("Fail to cache for: " + ioe.getMessage());
}
}
}
if (WRITE_TYPE.isThrough()) {
mCheckpointOutputStream.write(b, off, len);
}
}
@Override
public void write(int b) throws IOException {
if (WRITE_TYPE.isCache()) {
try {
if (mCurrentBlockId == -1 || mCurrentBlockLeftByte == 0) {
getNextBlock();
}
// TODO Cache the exception here.
mCurrentBlockOutStream.write(b);
mCurrentBlockLeftByte --;
mCachedBytes ++;
} catch (IOException ioe) {
if (WRITE_TYPE.isMustCache()) {
LOG.error(ioe.getMessage());
throw new IOException("Fail to cache: " + WRITE_TYPE);
} else {
LOG.warn("Fail to cache for: " + ioe.getMessage());
}
}
}
if (WRITE_TYPE.isThrough()) {
mCheckpointOutputStream.write(b);
}
}
}