/*
* @(#)$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.util.nio;
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.IntBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
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.util.cache.ILongCache;
import xbird.util.io.IOUtils;
import xbird.util.net.NetUtils;
import xbird.util.net.PoolableSocketChannelFactory;
import xbird.util.pool.ConcurrentKeyedStackObjectPool;
import xbird.util.string.StringUtils;
import xbird.util.struct.LongRange;
import xbird.xquery.dm.coder.SerializationContext;
import xbird.xquery.dm.dtm.MemoryMappedDocumentTable;
/**
*
* <DIV lang="en">
* This class is not thread-safe as an intended behavior.
* </DIV>
* <DIV lang="ja"></DIV>
*
* @author Makoto YUI (yuin405+xbird@gmail.com)
*/
public final class RemoteMemoryMappedFile implements IMemoryMappedFile, Externalizable {
private static final long serialVersionUID = 4842717419671591515L;
private static final Log LOG = LogFactory.getLog("xbird.remotepaging");
private/* final */InetSocketAddress _sockAddr;
private/* final */String _filePath;
private/* final */int _pageSize;
private/* final */boolean _bigEndian;
private/* final */long _maxBulkFetchSize;
private transient String _fileIdentifier = null;
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>("RemoteMemoryMappedFile", factory);
}
private transient/* final */ByteBuffer _rcvbuf;
private SerializationContext _serContext = null;
public RemoteMemoryMappedFile() {}
public RemoteMemoryMappedFile(int port, String filePath, int pageSize, boolean alloc, boolean bigEndian) {
this._sockAddr = new InetSocketAddress(NetUtils.getLocalHost(), port);
this._filePath = filePath;
this._pageSize = pageSize;
this._bigEndian = bigEndian;
this._maxBulkFetchSize = pageSize * (MemoryMappedDocumentTable.CACHED_PAGES / 4);
this._rcvbuf = alloc ? ByteBuffer.allocateDirect(pageSize) : null;
}
public String getFileIdentifier() {
return _fileIdentifier;
}
public void setSerializationContext(SerializationContext serContext) {
this._serContext = serContext;
}
public CloseableMappedByteBuffer allocateBuffer(long pageOffset) {
throw new UnsupportedOperationException();
}
public int[] transferBuffer(final long pageOffset, final int aryLength) {
final int[] dst;
final ByteChannel channel;
try {
channel = openConnection(_sockAddr);
} catch (IOException ioe) {
throw new IllegalStateException("failed opening a socket", ioe);
}
try {
sendRequest(channel, pageOffset, pageOffset, aryLength);
dst = recvResponse(channel, _rcvbuf, aryLength);
} catch (IOException ioe) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(ioe);
} catch (Throwable e) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(e);
} finally {
_sockPool.returnObject(_sockAddr, channel);
}
return dst;
}
public int[] transferBuffers(long startPageOffset, int aryLength, ILongCache<int[]> _pool) {
LongRange range = _serContext.ranges().getRangeOf(startPageOffset);
final int pageSize = _pageSize;
long endPageOffset = (range == null) ? startPageOffset + pageSize : range.getEnd();
final long lastPageOffset = restrictEndOffset(startPageOffset, endPageOffset, aryLength, pageSize);
assert (lastPageOffset > startPageOffset) : "Illegal condition.. start:" + startPageOffset
+ " < end:" + lastPageOffset;
final ByteChannel channel;
try {
channel = openConnection(_sockAddr);
} catch (IOException ioe) {
throw new IllegalStateException("failed opening a socket", ioe);
}
if(LOG.isDebugEnabled()) {
LOG.debug("Send a request to " + _sockAddr + " to transfer " + pageSize + " bytes of '"
+ _filePath + "' from the offset " + startPageOffset);
}
final int[] dst;
try {
sendRequest(channel, startPageOffset, lastPageOffset, aryLength);
dst = recvResponse(channel, _rcvbuf, aryLength);
if(LOG.isDebugEnabled()) {
LOG.debug("Received pages starting from the offset " + startPageOffset);
}
int[] tmp;
long startPageId = (startPageOffset + pageSize) / pageSize;
long endPageId = lastPageOffset / pageSize;
for(long i = startPageId; i < endPageId; i++) {
tmp = recvResponse(channel, _rcvbuf, aryLength);
_pool.put(i, tmp);
}
} catch (IOException ioe) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(ioe);
} catch (Throwable e) {
IOUtils.closeQuietly(channel);
throw new IllegalStateException(e);
} finally {
_sockPool.returnObject(_sockAddr, channel);
}
return dst;
}
private static ByteChannel openConnection(final SocketAddress sockAddr) throws IOException {
return _sockPool.borrowObject(sockAddr);
}
private long restrictEndOffset(final long startOffset, final long endOffset, final int aryLength, final int pageSize) {
final long diff = endOffset - startOffset;
if(diff > _maxBulkFetchSize) {
return startOffset + _maxBulkFetchSize;
} else {
return endOffset;
}
}
private void sendRequest(final ByteChannel channel, final long startOffset, final long endOffset, final int aryLength)
throws IOException {
byte[] bFilePath = StringUtils.getBytes(_filePath);
int buflen = bFilePath.length + 29;//+ 4 + 4 + 4 + 8 + 8 + 1;
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_READ);
oprBuf.putInt(bFilePath.length); // #1
oprBuf.put(bFilePath); // #2
oprBuf.putLong(startOffset); // #3
oprBuf.putLong(endOffset); // #4
oprBuf.put((byte) (_bigEndian ? 1 : 0)); // # 5 is big endian?
oprBuf.flip();
NIOUtils.writeFully(channel, oprBuf);
}
public static final class ReadRequestMessage extends RequestMessage {
final String filePath;
final long startOffset;
final long endOffset;
final boolean isBigEndian;
private ReadRequestMessage(IoBuffer in) {
super(RemotePagingService.COMMAND_READ);
int filePathLen = in.getInt(); // #1
byte[] bFilePath = new byte[filePathLen];
in.get(bFilePath);
this.filePath = StringUtils.toString(bFilePath, 0, filePathLen); // #2
this.startOffset = in.getLong(); // #3
this.endOffset = in.getLong(); // #4
byte bigEndian = in.get(); // #5
this.isBigEndian = (bigEndian == 1) ? true : false;
}
public static ReadRequestMessage decode(IoBuffer in) {
return new ReadRequestMessage(in);
}
}
public static void handleResponse(final ReadRequestMessage request, final ProtocolEncoderOutput out, final ConcurrentMap<String, FileChannel> fdCacheMap) {
final String filePath = request.filePath;
FileChannel fileChannel = fdCacheMap.get(filePath);
if(fileChannel == null) {
File file = new File(filePath);
if(!file.exists()) {
throw new IllegalStateException("file not exists: " + filePath);
}
final RandomAccessFile raf;
try {
raf = new RandomAccessFile(file, "r");
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
fileChannel = raf.getChannel();
fdCacheMap.put(filePath, fileChannel);
}
long count = request.endOffset - request.startOffset;
long position = request.startOffset;
if(LOG.isDebugEnabled()) {
LOG.debug("Transfer " + count + " bytes of file '" + filePath + "' from the offset "
+ position);
}
FileRegion fileRegion = new DefaultFileRegion(fileChannel, position, count);
out.write(fileRegion);
}
private int[] recvResponse(final ReadableByteChannel channel, final ByteBuffer buf, final int dstlen)
throws IOException {
buf.clear();
// set endian optimized for this machine
final boolean isBufBigEndian = (buf.order() == ByteOrder.BIG_ENDIAN);
if(_bigEndian != isBufBigEndian) {
buf.order(_bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
}
NIOUtils.readFully(channel, buf, _pageSize);
buf.flip();
IntBuffer ibuf = buf.asIntBuffer();
int[] dst = new int[dstlen];
ibuf.get(dst);
return dst;
}
public void flush() {
throw new IllegalStateException();
}
public void close() throws IOException {}
public void ensureOpen() {}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this._bigEndian = in.readBoolean();
this._pageSize = in.readInt();
this._maxBulkFetchSize = _pageSize * (MemoryMappedDocumentTable.CACHED_PAGES / 4);
this._rcvbuf = ByteBuffer.allocateDirect(_pageSize);
String host = IOUtils.readString(in);
int port = in.readInt();
this._sockAddr = new InetSocketAddress(host, port);
this._filePath = IOUtils.readString(in);
this._fileIdentifier = generateFileIdentifier(_sockAddr, _filePath);
}
private static String generateFileIdentifier(final InetSocketAddress sockAddr, final String filePath) {
final StringBuilder buf = new StringBuilder();
buf.append(sockAddr.getHostName());
buf.append(':');
buf.append(sockAddr.getPort());
buf.append('/');
buf.append(filePath);
return buf.toString();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeBoolean(_bigEndian);
out.writeInt(_pageSize);
IOUtils.writeString(_sockAddr.getHostName(), out);
out.writeInt(_sockAddr.getPort());
IOUtils.writeString(_filePath, out);
close();
}
public static RemoteMemoryMappedFile read(ObjectInput in) throws IOException,
ClassNotFoundException {
RemoteMemoryMappedFile mmfile = new RemoteMemoryMappedFile();
mmfile.readExternal(in);
return mmfile;
}
public RemoteMemoryMappedFile externalize() {
return this;
}
}