/*
* @(#)$Id: codetemplate_xbird.xml 943 2006-09-13 07:03:37Z yui $
*
* Copyright 2006-2008 Makoto YUI
*
* 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.
*
* Contributors:
* Makoto YUI - initial implementation
*/
package xbird.storage.io;
import java.io.Externalizable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ByteChannel;
import java.nio.channels.FileChannel;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.file.DefaultFileRegion;
import org.apache.mina.core.file.FileRegion;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import xbird.server.services.RemotePagingService;
import xbird.server.services.RemotePagingService.RequestMessage;
import xbird.storage.io.VarSegments.IDescriptor;
import xbird.util.io.IOUtils;
import xbird.util.net.NetUtils;
import xbird.util.net.PoolableSocketChannelFactory;
import xbird.util.nio.NIOUtils;
import xbird.util.pool.ConcurrentKeyedStackObjectPool;
import xbird.util.pool.ObjectPool;
import xbird.util.pool.StackObjectPool;
import xbird.util.primitive.Primitives;
import xbird.util.string.StringUtils;
/**
*
* <DIV lang="en"></DIV>
* <DIV lang="ja"></DIV>
*
* @author Makoto YUI (yuin405+xbird@gmail.com)
*/
public final class RemoteVarSegments implements Segments, Externalizable {
private static final long serialVersionUID = -2804462847438664966L;
private static final Log LOG = LogFactory.getLog(RemoteVarSegments.class);
private static final int DEFAULT_BUF_CAPACITY = 2 * 1024 * 1024; // 2M bytes
private/* final */InetSocketAddress _sockAddr;
private/* final */String _filePath;
private/* final */ObjectPool<ByteBuffer> _rbufPool;
private static final ConcurrentKeyedStackObjectPool<SocketAddress, ByteChannel> _sockPool;
static {
boolean datagram = "NIODATAGRAM".equals(RemotePagingService.CONN_TYPE);
PoolableSocketChannelFactory factory = new PoolableSocketChannelFactory(datagram, true);
factory.configure(RemotePagingService.SWEEP_INTERVAL, RemotePagingService.TIME_TO_LIVE, RemotePagingService.SO_RCVBUF_SIZE);
_sockPool = new ConcurrentKeyedStackObjectPool<SocketAddress, ByteChannel>("RemoteVarSegments", factory);
}
public RemoteVarSegments() {
this._rbufPool = createPool();
}
public RemoteVarSegments(int port, String filePath, boolean alloc) {
this._sockAddr = new InetSocketAddress(NetUtils.getLocalHost(), port);
this._filePath = filePath;
this._rbufPool = alloc ? createPool() : null;
}
private static ObjectPool<ByteBuffer> createPool() {
return new StackObjectPool<ByteBuffer>(1) {
protected ByteBuffer createObject() {
return ByteBuffer.allocate(DEFAULT_BUF_CAPACITY);
}
};
}
public byte[] read(long idx) throws IOException {
final ByteChannel channel;
try {
channel = openConnection(_sockAddr);
} catch (IOException ioe) {
throw new IllegalStateException("failed opening a socket", ioe);
}
final byte[] dst;
final ByteBuffer rbuf = _rbufPool.borrowObject();
try {
sendRequest(channel, idx);
dst = recvResponse(channel, rbuf);
} catch (IOException ioe) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(ioe);
} catch (Throwable e) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(e);
} finally {
_rbufPool.returnObject(rbuf);
_sockPool.returnObject(_sockAddr, channel);
}
return dst;
}
public byte[][] readv(final long[] idxs) throws IOException {
final ByteChannel channel;
try {
channel = openConnection(_sockAddr);
} catch (IOException ioe) {
throw new IllegalStateException("failed opening a socket", ioe);
}
final int size = idxs.length;
final byte[][] dst = new byte[size][];
final ByteBuffer rbuf = _rbufPool.borrowObject();
try {
sendRequest(channel, idxs);
for(int i = 0; i < size; i++) {
dst[i] = recvResponse(channel, rbuf);
}
} catch (IOException ioe) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(ioe);
} catch (Throwable e) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(e);
} finally {
_rbufPool.returnObject(rbuf);
_sockPool.returnObject(_sockAddr, channel);
}
return dst;
}
private static ByteChannel openConnection(final SocketAddress sockAddr) throws IOException {
return _sockPool.borrowObject(sockAddr);
}
private void sendRequest(final ByteChannel channel, final long... idxs) throws IOException {
byte[] bFilePath = StringUtils.getBytes(_filePath);
final int size = idxs.length;
int buflen = bFilePath.length + 16 + (Primitives.LONG_BYTES * size);//+ 4 + 4 + 4 + 4 + 8n;
if(buflen > RemotePagingService.MAX_COMMAND_BUFLEN) {
throw new IllegalStateException("command size exceeds limit in MAX_COMMAND_BUFLEN("
+ RemotePagingService.MAX_COMMAND_BUFLEN + "): " + buflen + " bytes");
}
ByteBuffer oprBuf = ByteBuffer.allocate(buflen);
oprBuf.putInt(buflen - 4);
oprBuf.putInt(RemotePagingService.COMMAND_TRACK_READ);
oprBuf.putInt(bFilePath.length); // #1
oprBuf.put(bFilePath); // #2
oprBuf.putInt(size); // #3
for(int i = 0; i < size; i++) {
oprBuf.putLong(idxs[i]); // #4
}
oprBuf.flip();
NIOUtils.writeFully(channel, oprBuf);
}
public static final class TrackReadRequestMessage extends RequestMessage {
final String filePath;
final long[] idxs;
private TrackReadRequestMessage(IoBuffer in) {
super(RemotePagingService.COMMAND_TRACK_READ);
int filePathLen = in.getInt(); // #1
byte[] bFilePath = new byte[filePathLen];
in.get(bFilePath);
this.filePath = StringUtils.toString(bFilePath, 0, filePathLen); // #2
final int size = in.getInt(); // #3
long[] idxs = new long[size];
for(int i = 0; i < size; i++) {
idxs[i] = in.getLong(); // #4
}
this.idxs = idxs;
}
public static TrackReadRequestMessage decode(IoBuffer in) {
return new TrackReadRequestMessage(in);
}
}
public static void handleResponse(final TrackReadRequestMessage request, final ProtocolEncoderOutput out, final ConcurrentMap<String, FileChannel> fdCacheMap, final ConcurrentMap<String, IDescriptor> directoryCache)
throws IOException {
final String filePath = request.filePath;
final long[] idxs = request.idxs;
final int size = idxs.length;
// look-up directory
final File dataFile = new File(filePath);
final long[] offsets = new long[size];
IDescriptor directory = directoryCache.get(filePath);
try {
if(directory == null) {
directory = VarSegments.initDescriptor(dataFile);
directoryCache.put(filePath, directory);
}
for(int i = 0; i < size; i++) {
offsets[i] = directory.getRecordAddr(idxs[i]);
}
} catch (IOException e) {
LOG.error(e);
throw e;
}
FileChannel fileChannel = fdCacheMap.get(filePath);
if(fileChannel == null) {
if(!dataFile.exists()) {
throw new IllegalStateException("file not exists: " + filePath);
}
final RandomAccessFile raf;
try {
raf = new RandomAccessFile(dataFile, "r");
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
fileChannel = raf.getChannel();
fdCacheMap.put(filePath, fileChannel);
}
for(int i = 0; i < size; i++) {
final long offset = offsets[i];
// get data length
final ByteBuffer tmpBuf = ByteBuffer.allocate(4);
try {
fileChannel.read(tmpBuf, offset);
} catch (IOException e) {
LOG.error(e);
throw e;
}
tmpBuf.flip();
final int length = tmpBuf.getInt();
tmpBuf.rewind();
IoBuffer ioBuf = IoBuffer.wrap(tmpBuf);
out.write(ioBuf);
// attempt zero-copy sendfile
long position = offset + 4;
long count = length;
FileRegion fileRegion = new DefaultFileRegion(fileChannel, position, count);
out.write(fileRegion);
}
}
private byte[] recvResponse(final ByteChannel channel, final ByteBuffer rcvBuf)
throws IOException {
ByteBuffer tmpBuf = ByteBuffer.allocate(4);
NIOUtils.readFully(channel, tmpBuf, 4);
tmpBuf.flip();
int datalen = tmpBuf.getInt();
final ByteBuffer buf = truncateBuffer(rcvBuf, datalen);
NIOUtils.readFully(channel, buf, datalen);
buf.flip();
if(ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
buf.order(ByteOrder.BIG_ENDIAN);
}
final byte[] b = new byte[datalen];
buf.get(b);
if(buf != rcvBuf) {
_rbufPool.returnObject(buf);
}
return b;
}
public void close() throws IOException {
this._rbufPool = null;
//_rbufPool.clear();
}
public File getFile() {
throw new UnsupportedOperationException();
}
public long write(long idx, byte[] b) throws IOException {
throw new UnsupportedOperationException();
}
public void flush(boolean close) throws IOException {
throw new UnsupportedOperationException();
}
private ByteBuffer truncateBuffer(final ByteBuffer buf, final int size) {
if(size > buf.capacity()) {
_rbufPool.returnObject(buf);
return ByteBuffer.allocate(size); // TODO REVIEWME
} else {
buf.clear();
buf.limit(size);
return buf;
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
String host = IOUtils.readString(in);
int port = in.readInt();
this._sockAddr = new InetSocketAddress(host, port);
this._filePath = IOUtils.readString(in);
}
public void writeExternal(ObjectOutput out) throws IOException {
IOUtils.writeString(_sockAddr.getHostName(), out);
out.writeInt(_sockAddr.getPort());
IOUtils.writeString(_filePath, out);
close();
}
public static RemoteVarSegments read(ObjectInput in) throws IOException, ClassNotFoundException {
RemoteVarSegments seg = new RemoteVarSegments();
seg.readExternal(in);
return seg;
}
}