xbird.util.nio.RemoteMemoryMappedFile.java Source code

Java tutorial

Introduction

Here is the source code for xbird.util.nio.RemoteMemoryMappedFile.java

Source

/*
 * @(#)$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;
    }

}