/*
* 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.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import org.apache.log4j.Logger;
import tachyon.Constants;
import tachyon.util.CommonUtils;
/**
* <code>BlockOutStream</code> implementation of TachyonFile. This class is not client facing.
*/
public class BlockOutStream extends OutStream {
private final Logger LOG = Logger.getLogger(Constants.LOGGER_TYPE);
private final int BLOCK_INDEX;
private final long BLOCK_CAPACITY_BYTE;
private final long BLOCK_ID;
private final long BLOCK_OFFSET;
private final boolean PIN;
private long mInFileBytes = 0;
private long mWrittenBytes = 0;
private String mLocalFilePath = null;
private RandomAccessFile mLocalFile = null;
private FileChannel mLocalFileChannel = null;
private ByteBuffer mBuffer = ByteBuffer.allocate(0);
private boolean mCanWrite = false;
private boolean mClosed = false;
private boolean mCancel = false;
/**
* @param file
* the file the block belongs to
* @param opType
* the OutStream's write type
* @param blockIndex
* the index of the block in the file
* @throws IOException
*/
BlockOutStream(TachyonFile file, WriteType opType, int blockIndex) throws IOException {
super(file, opType);
if (!opType.isCache()) {
throw new IOException("BlockOutStream only support WriteType.CACHE");
}
BLOCK_INDEX = blockIndex;
BLOCK_CAPACITY_BYTE = FILE.getBlockSizeByte();
BLOCK_ID = FILE.getBlockId(BLOCK_INDEX);
BLOCK_OFFSET = BLOCK_CAPACITY_BYTE * blockIndex;
PIN = FILE.needPin();
mCanWrite = true;
if (!TFS.hasLocalWorker()) {
mCanWrite = false;
String msg = "The machine does not have any local worker.";
throw new IOException(msg);
}
File localFolder = TFS.createAndGetUserTempFolder();
if (localFolder == null) {
mCanWrite = false;
String msg = "Failed to create temp user folder for tachyon client.";
throw new IOException(msg);
}
mLocalFilePath = CommonUtils.concat(localFolder.getPath(), BLOCK_ID);
mLocalFile = new RandomAccessFile(mLocalFilePath, "rw");
mLocalFileChannel = mLocalFile.getChannel();
// change the permission of the temporary file in order that the worker can move it.
CommonUtils.changeLocalFileToFullPermission(mLocalFilePath);
// use the sticky bit, only the client and the worker can write to the block
CommonUtils.setLocalFileStickyBit(mLocalFilePath);
LOG.info(mLocalFilePath + " was created!");
mBuffer = ByteBuffer.allocate(USER_CONF.FILE_BUFFER_BYTES + 4);
}
private synchronized void appendCurrentBuffer(byte[] buf, int offset, int length)
throws IOException {
if (!TFS.requestSpace(length)) {
mCanWrite = false;
String msg =
"Local tachyon worker does not have enough " + "space (" + length
+ ") or no worker for " + FILE.FID + " " + BLOCK_ID;
if (PIN) {
TFS.outOfMemoryForPinFile(FILE.FID);
throw new IOException(msg);
}
throw new IOException(msg);
}
MappedByteBuffer out = mLocalFileChannel.map(MapMode.READ_WRITE, mInFileBytes, length);
out.put(buf, offset, length);
mInFileBytes += length;
}
@Override
public void cancel() throws IOException {
mCancel = true;
close();
}
/**
* @return true if the stream can write and is not closed, otherwise false
*/
public boolean canWrite() {
return !mClosed && mCanWrite;
}
@Override
public void close() throws IOException {
if (!mClosed) {
if (!mCancel && mBuffer.position() > 0) {
appendCurrentBuffer(mBuffer.array(), 0, mBuffer.position());
}
if (mLocalFileChannel != null) {
mLocalFileChannel.close();
mLocalFile.close();
}
if (mCancel) {
TFS.releaseSpace(mWrittenBytes - mBuffer.position());
new File(mLocalFilePath).delete();
LOG.info("Canceled output of block " + BLOCK_ID + ", deleted local file " + mLocalFilePath);
} else {
TFS.cacheBlock(BLOCK_ID);
}
}
mClosed = true;
}
@Override
public void flush() throws IOException {
// Since this only writes to memory, this flush is not outside visible.
}
/**
* @return the block id of the block
*/
public long getBlockId() {
return BLOCK_ID;
}
/**
* @return the block offset in the file.
*/
public long getBlockOffset() {
return BLOCK_OFFSET;
}
/**
* @return the remaining space of the block, in bytes
*/
public long getRemainingSpaceByte() {
return BLOCK_CAPACITY_BYTE - mWrittenBytes;
}
@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(String.format("Buffer length (%d), offset(%d), len(%d)",
b.length, off, len));
}
if (!mCanWrite) {
throw new IOException("Can not write cache.");
}
if (mWrittenBytes + len > BLOCK_CAPACITY_BYTE) {
throw new IOException("Out of capacity.");
}
if (mBuffer.position() + len >= USER_CONF.FILE_BUFFER_BYTES) {
if (mBuffer.position() > 0) {
appendCurrentBuffer(mBuffer.array(), 0, mBuffer.position());
mBuffer.clear();
}
if (len > 0) {
appendCurrentBuffer(b, off, len);
}
} else {
mBuffer.put(b, off, len);
}
mWrittenBytes += len;
}
@Override
public void write(int b) throws IOException {
if (!mCanWrite) {
throw new IOException("Can not write cache.");
}
if (mWrittenBytes + 1 > BLOCK_CAPACITY_BYTE) {
throw new IOException("Out of capacity.");
}
if (mBuffer.position() >= USER_CONF.FILE_BUFFER_BYTES) {
appendCurrentBuffer(mBuffer.array(), 0, mBuffer.position());
mBuffer.clear();
}
mBuffer.put((byte) (b & 0xFF));
mWrittenBytes ++;
}
}