/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.server.cache; import com.caucho.db.block.Block; import com.caucho.db.block.BlockStore; import com.caucho.db.xa.RawTransaction; import com.caucho.db.xa.StoreTransaction; import com.caucho.util.L10N; import com.caucho.vfs.OutputStreamWithBuffer; import com.caucho.vfs.TempCharBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents an inode to a temporary file. */ public class TempFileInode { private static final L10N L = new L10N(TempFileInode.class); private static final Logger log = Logger.getLogger(TempFileInode.class.getName()); private final BlockStore _store; private AtomicInteger _useCount = new AtomicInteger(1); private ArrayList<Long> _blockList = new ArrayList<Long>(); private long []_blockArray; private long _length; TempFileInode(BlockStore store) { _store = store; } public long getLength() { return _length; } /** * Allocates access to the inode. */ public boolean allocate() { int count; while ((count = _useCount.get()) > 0) { if (_useCount.compareAndSet(count, count + 1)) return true; } return false; } /** * Opens a stream to write to the temp file */ public OutputStream openOutputStream() { return new TempFileOutputStream(); } /** * Opens a stream to read from the temp file */ public InputStream openInputStream() { return new TempFileInputStream(); } public Writer openWriter() { return new TempFileWriter(); } /** * Writes the inode value to a stream. */ public void writeToStream(OutputStreamWithBuffer os) throws IOException { writeToStream(os, 0, _length); } /** * Writes the inode value to a stream. */ public void writeToStream(OutputStreamWithBuffer os, long offset, long length) throws IOException { if (_length < length) length = _length; byte []buffer = os.getBuffer(); int writeLength = buffer.length; int writeOffset = os.getBufferOffset(); long []blockArray = _blockArray; while (length > 0) { int sublen = writeLength - writeOffset; if (sublen == 0) { buffer = os.nextBuffer(writeOffset); writeOffset = os.getBufferOffset(); sublen = writeLength - writeOffset; } if (length < sublen) sublen = (int) length; long blockId = blockArray[(int) (offset / BlockStore.BLOCK_SIZE)]; int blockOffset = (int) (offset % BlockStore.BLOCK_SIZE); if (BlockStore.BLOCK_SIZE - blockOffset < sublen) sublen = BlockStore.BLOCK_SIZE - blockOffset; int len = _store.readBlock(blockId, blockOffset, buffer, writeOffset, sublen); if (len <= 0) { break; } writeOffset += len; offset += len; length -= len; } os.setBufferOffset(writeOffset); /* if (_useCount < 2) System.out.println("USE_COUNT: " + _useCount); */ if (_useCount.get() <= 0) throw new IllegalStateException(L.l("Unexpected close of cache inode")); } /** * Writes the inode value to a stream. */ public void writeToWriter(Writer out) throws IOException { TempCharBuffer charBuffer = TempCharBuffer.allocate(); char []buffer = charBuffer.getBuffer(); long offset = 0; long length = _length; while (length > 0) { long blockId = _blockArray[(int) (offset / BlockStore.BLOCK_SIZE)]; int blockOffset = (int) (offset % BlockStore.BLOCK_SIZE); int sublen = (BlockStore.BLOCK_SIZE - blockOffset) / 2; if (buffer.length < sublen) sublen = buffer.length; if (length < 2 * sublen) sublen = (int) (length / 2); int len = _store.readBlock(blockId, blockOffset, buffer, 0, sublen); if (len <= 0) break; out.write(buffer, 0, len); offset += 2 * len; length -= 2 * len; } TempCharBuffer.free(charBuffer); if (_useCount.get() <= 0) throw new IllegalStateException(L.l("Unexpected close of cache inode")); } /** * Allocates access to the inode. */ public void free() { int useCount = _useCount.decrementAndGet(); if (useCount == 0) { remove(); } else if (useCount < 0) { //System.out.println("BAD: " + useCount); throw new IllegalStateException("illegal use count: " + useCount); } } private void remove() { ArrayList<Long> blockList; long []blockArray; synchronized (this) { blockList = _blockList; _blockList = null; blockArray = _blockArray; _blockArray = null; } if (blockArray != null) { if (_useCount.get() > 0) Thread.dumpStack(); for (long block : blockArray) { try { _store.deallocateBlock(block); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } } if (_useCount.get() > 0) Thread.dumpStack(); } else if (blockList != null) { //System.out.println("FRAGMENT-LIST: " + fragmentList); for (long block : blockList) { try { _store.deallocateBlock(block); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } } } } class TempFileOutputStream extends OutputStream { private final StoreTransaction _xa = RawTransaction.create(); private final byte []_tempBuffer = new byte[8]; @Override public void write(int ch) throws IOException { _tempBuffer[0] = (byte) ch; write(_tempBuffer, 0, 1); } @Override public void write(byte []buffer, int offset, int length) throws IOException { while (length > 0) { while (_blockList.size() <= _length / BlockStore.BLOCK_SIZE) { Block block = _store.allocateBlock(); _blockList.add(block.getBlockId()); block.free(); } int blockOffset = (int) (_length % BlockStore.BLOCK_SIZE); long blockAddress = _blockList.get(_blockList.size() - 1); int sublen = BlockStore.BLOCK_SIZE - blockOffset; if (length < sublen) sublen = length; _length += sublen; Block block = _store.writeBlock(blockAddress, blockOffset, buffer, offset, sublen); _xa.addUpdateBlock(block); length -= sublen; offset += sublen; } } @Override public void flush() { } @Override public void close() { if (_blockList == null) return; _blockArray = new long[_blockList.size()]; for (int i = 0; i < _blockList.size(); i++) { _blockArray[i] = _blockList.get(i); } } } class TempFileInputStream extends InputStream { private final byte []_tempBuffer = new byte[1]; private int _offset; public int read() throws IOException { int len = read(_tempBuffer, 0, 1); if (len > 0) return _tempBuffer[0] & 0xff; else return -1; } /** * Writes the inode value to a stream. */ public int read(byte []buffer, int offset, int length) throws IOException { if (_length - _offset < length) length = (int) (_length - _offset); long []blockArray = _blockArray; int readLength = 0; while (length > 0) { long blockId = blockArray[(int) (_offset / BlockStore.BLOCK_SIZE)]; int blockOffset = (int) (_offset % BlockStore.BLOCK_SIZE); int sublen = length; if (BlockStore.BLOCK_SIZE - blockOffset < sublen) sublen = BlockStore.BLOCK_SIZE - blockOffset; int len = _store.readBlock(blockId, blockOffset, buffer, offset, sublen); if (len <= 0) { break; } offset += len; _offset += len; length -= len; readLength += len; } if (readLength <= 0) return -1; else return readLength; } public void close() { } } class TempFileWriter extends Writer { private final StoreTransaction _xa = RawTransaction.create(); private final char []_tempBuffer = new char[8]; public void write(char ch) throws IOException { _tempBuffer[0] = ch; write(_tempBuffer, 0, 1); } public void write(char []buffer, int offset, int length) throws IOException { while (length > 0) { while (_blockList.size() <= _length / BlockStore.BLOCK_SIZE) { Block block = _store.allocateBlock(); _blockList.add(block.getBlockId()); block.free(); } int blockOffset = (int) (_length % BlockStore.BLOCK_SIZE); long blockId = _blockList.get(_blockList.size() - 1); int sublen = (BlockStore.BLOCK_SIZE - blockOffset) / 2; if (length < sublen) sublen = length; _length += 2 * sublen; Block block = _store.writeBlock(blockId, blockOffset, buffer, offset, sublen); _xa.addUpdateBlock(block); length -= sublen; offset += sublen; } } public void flush() { } public void close() { synchronized (this) { if (_blockList == null) return; _blockArray = new long[_blockList.size()]; for (int i = 0; i < _blockList.size(); i++) { _blockArray[i] = _blockList.get(i); } _blockList = null; } } } }