001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2014 ForgeRock AS.
016 */
017
018package org.forgerock.openig.io;
019
020import java.io.File;
021import java.io.IOException;
022
023/**
024 * A buffer that fist uses memory, then a temporary file for data storage. Initially, a
025 * {@link MemoryBuffer} is used; when the memory buffer limit is exceeded it promotes to
026 * the use of a {@link FileBuffer}.
027 */
028public class TemporaryBuffer implements Buffer {
029
030    /** File used for temporary storage. */
031    private File file = null;
032
033    /** The directory where temporary files are created. */
034    private File directory;
035
036    /** The length limit of the file buffer. */
037    private int fileLimit;
038
039    /** The buffer currently in use. */
040    private Buffer buffer;
041
042    /**
043     * Constructs a new temporary buffer.
044     *
045     * @param initialLength the initial length of memory buffer byte array.
046     * @param memoryLimit the length limit of the memory buffer.
047     * @param fileLimit the length limit of the file buffer.
048     * @param directory the directory where temporary files are created, or {@code null} to use the system-dependent
049     * default temporary directory.
050     */
051    public TemporaryBuffer(int initialLength, int memoryLimit, int fileLimit, File directory) {
052        buffer = new MemoryBuffer(initialLength, memoryLimit);
053        this.fileLimit = fileLimit;
054        this.directory = directory;
055    }
056
057    @Override
058    public int read(int pos, byte[] b, int off, int len) throws IOException {
059        notClosed();
060        return buffer.read(pos, b, off, len);
061    }
062
063    @Override
064    public void append(byte[] b, int off, int len) throws IOException, OverflowException {
065        notClosed();
066        try {
067            buffer.append(b, off, len);
068        } catch (OverflowException oe) {
069            // may throw OverflowException to indicate no promotion possible
070            promote();
071            // recursively retry after promotion
072            append(b, off, len);
073        }
074    }
075
076    @Override
077    public int length() throws IOException {
078        notClosed();
079        return buffer.length();
080    }
081
082    @Override
083    public void close() throws IOException {
084        if (buffer != null) {
085            try {
086                buffer.close();
087            } finally {
088                buffer = null;
089                if (file != null) {
090                    file.delete();
091                }
092                file = null;
093            }
094        }
095    }
096
097    /**
098     * Throws an {@link IOException} if the buffer is closed.
099     */
100    private void notClosed() throws IOException {
101        if (buffer == null) {
102            throw new IOException("buffer is closed");
103        }
104    }
105
106    private void promote() throws IOException, OverflowException {
107        if (buffer instanceof MemoryBuffer) {
108            MemoryBuffer membuf = (MemoryBuffer) buffer;
109            file = File.createTempFile("buf", null, directory);
110            buffer = new FileBuffer(file, fileLimit);
111            // accesses byte array directly
112            buffer.append(membuf.data, 0, membuf.length());
113            membuf.close();
114        } else {
115            // no further promotion possible
116            throw new OverflowException();
117        }
118    }
119}