All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scriptella.jdbc.Lobs Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2006-2012 The Scriptella Project Team.
 *
 * 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.
 */
package scriptella.jdbc;

import scriptella.util.IOUtils;
import scriptella.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;

/**
 * Factory for LOBs.
 *
 * @author Fyodor Kupolov
 * @version 1.0
 */
class Lobs {
    private Lobs() { //Singleton
    }

    /**
     * Create a new read-only BLOB for specified input stream.
     * 

The stream will be lazily read into memory or saved on disk depending on its length. * * @param is input stream to create blob. * @return read-only Blob instance. */ public static Blob newBlob(InputStream is) { return new ReadonlyBlob(is); } /** * Create a new read-only BLOB for specified input stream. *

The stream will be lazily read into memory or saved on disk depending on its length. * * @param is input stream to create blob. * @param length input stream length. * @return read-only Blob instance. */ public static Blob newBlob(InputStream is, long length) { return new ReadonlyBlob(is, length); } /** * Create a new read-only BLOB for specified URL. *

The URL content will be lazily fetched into memory or saved on disk depending on its length. * * @param url URL of the blob content. * @return read-only Blob instance. * @see #newBlob(java.io.InputStream) */ public static Blob newBlob(URL url) { return new UrlBlob(url); } /** * Create a new read-only CLOB for specified reader. *

The reader will be lazily read into memory or saved on disk depending on its length. * * @param reader reader to create CLOB. * @return read-only Clob instance. */ public static Clob newClob(Reader reader) { return new ReadonlyClob(reader); } /** * Create a new read-only CLOB for specified reader. *

The reader will be lazily read into memory or saved on disk depending on its length. * * @param reader reader to create CLOB. * @param length content length. * @return read-only Clob instance. */ public static Clob newClob(Reader reader, long length) { return new ReadonlyClob(reader, length); } /** * Base class for LOBs. *

Defines a common functionality and helper methods. */ static abstract class AbstractLob implements Closeable { static int LOB_MAX_MEM = 100 * 1024; //100Kb protected File tmpFile; protected long length = -1; protected T source; /** * For custom instantiation. */ protected AbstractLob() { } protected AbstractLob(T source) { if (source == null) { throw new IllegalArgumentException("Input source cannot be null"); } this.source = source; } protected AbstractLob(T source, long length) { if (source == null) { throw new IllegalArgumentException("Input source cannot be null"); } if (length < 0) { throw new IllegalArgumentException("Input source length cannot be negative"); } this.source = source; this.length = length; } public void close() { if (tmpFile != null) { tmpFile.delete(); tmpFile = null; } } public final void free() throws SQLException { close(); } /** * Read bytes/chars from source stream/reader. * * @param inmemory true if data should be read into a memory. * @return number of bytes/chars read. * @throws IOException */ protected abstract int read(boolean inmemory) throws IOException; /** * Flush the data stored in a memory to disk. *

This method is invoked 0 or 1 time. * * @throws IOException */ protected abstract void flushToDisk() throws IOException; /** * Invoked when initialization phase complete. *

Subclasses should close unused and opened resources. */ protected abstract void onInitComplete(); /** * Performs a LOB initialization in following steps: *

    *
  • Reads content into memory until the size exceeds {@link #LOB_MAX_MEM}. *
  • {@link #flushToDisk() Flushes} memory content to disk. *
  • Copy the left bytes to disk. *
*/ protected void init() { if (source == null && length >= 0) { return; } int n; try { for (length = 0; (n = read(true)) >= 0;) { length += n; if (length > LOB_MAX_MEM) { break; } } if (n >= 0) { flushToDisk(); for (; (n = read(false)) >= 0;) { length += n; } } } catch (IOException e) { throw new JdbcException("Cannot initialize temprorary file storage", e); } finally { IOUtils.closeSilently(source); source = null; onInitComplete(); } } /** * Returns true if this LOB is stored in memory. */ public boolean isInMemory() { return tmpFile == null; } /** * Creates a temprorary file. * * @return output stream for temprorary file created. * @throws IOException if I/O error occurs. */ protected OutputStream createTempFile() throws IOException { tmpFile = File.createTempFile("blob_", null); tmpFile.deleteOnExit(); return new FileOutputStream(tmpFile); } /** * Returns an input stream for temprorary file. */ protected InputStream getTempFileInputStream() { if (tmpFile == null) { throw new IllegalStateException("Internal error - temprorary file was not created"); } try { return new FileInputStream(tmpFile); } catch (FileNotFoundException e) { throw new JdbcException("Cannot open stream - temprorary file has been removed", e); } } /** * Returns the length of this LOB. */ public long length() { if (length < 0) { init(); } return length; } } /** * Represents a BLOB located at specified URL. */ static class UrlBlob extends ReadonlyBlob { private URL url; public UrlBlob(URL url) { if (url == null) { throw new IllegalArgumentException("URL cannot be null"); } this.url = url; } URL getUrl() { return url; } @Override protected void init() { if (length < 0) { try { final URLConnection c = url.openConnection(); source = c.getInputStream(); length = c.getContentLength(); if (length < 0) { //if length is undefined - fetch the url super.init(); } } catch (IOException e) { throw new JdbcException("Unable to read content for file " + url + ": " + e.getMessage(), e); } } } @Override public InputStream getBinaryStream() { InputStream src = source; if (src != null) { source = null; return src; } else { length = -1; init(); src = source; source = null; return (src == null) ? super.getBinaryStream() : src; } } } /** * Readonly implementation of {@link java.sql.Blob}. * * @author Fyodor Kupolov * @version 1.0 */ static class ReadonlyBlob extends AbstractLob implements Blob { private byte[] bytes; private byte[] buffer = new byte[8192]; private ByteArrayOutputStream memStream; private OutputStream diskStream; private static final byte[] EMPTY_BYTES = new byte[0]; /** * For custom instantion. */ protected ReadonlyBlob() { } public ReadonlyBlob(InputStream source) { super(source); } public ReadonlyBlob(InputStream source, long length) { super(source, length); } protected int read(boolean inmemory) throws IOException { int n = source.read(buffer); if (n > 0) { if (inmemory) { if (memStream == null) { memStream = new ByteArrayOutputStream(n); } memStream.write(buffer, 0, n); } else { diskStream.write(buffer, 0, n); } } return n; } protected void flushToDisk() throws IOException { diskStream = createTempFile(); memStream.writeTo(diskStream); memStream = null; } protected void onInitComplete() { if (diskStream != null) { //If large content IOUtils.closeSilently(diskStream); diskStream = null; } else { //otherwise in-memory content //don't forget to check memStream for null in case of empty input stream bytes = memStream == null ? EMPTY_BYTES : memStream.toByteArray(); memStream = null; } } public InputStream getBinaryStream() { init(); if (isInMemory()) { return new ByteArrayInputStream(bytes); } else { return getTempFileInputStream(); } } public void close() { super.close(); if (bytes != null) { bytes = null; } } public String toString() { try { return "BLOB: " + StringUtils.consoleFormat( new String(IOUtils.toByteArray(getBinaryStream(), 1024))); } catch (Exception e) { return "BLOB: " + e; } } //--------------- Unsupported methods public byte[] getBytes(long pos, int length) throws SQLException { throw new SQLException("Unsupported operation"); //Due to performance reasons } public long position(byte pattern[], long start) throws SQLException { throw new SQLException("Unsupported operation"); } public long position(Blob pattern, long start) throws SQLException { throw new SQLException("Unsupported operation"); } public int setBytes(long pos, byte[] bytes) throws SQLException { throw new SQLException("Unsupported operation"); } public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { throw new SQLException("Unsupported operation"); } public OutputStream setBinaryStream(long pos) throws SQLException { throw new SQLException("Unsupported operation"); } public void truncate(long len) throws SQLException { throw new SQLException("Unsupported operation"); } public InputStream getBinaryStream(long pos, long length) throws SQLException { throw new SQLException("Unsupported operation"); } } /** * Represents a read-only CLOB. */ static class ReadonlyClob extends AbstractLob implements Clob { private String string; private char[] buffer = new char[8192]; private StringBuilder mem; private Writer diskWriter; public ReadonlyClob(Reader source) { super(source); } public ReadonlyClob(Reader source, long length) { super(source, length); } protected int read(boolean inmemory) throws IOException { int n = source.read(buffer); if (n > 0) { if (inmemory) { if (mem == null) { mem = new StringBuilder(n); } mem.append(buffer, 0, n); } else { diskWriter.write(buffer, 0, n); } } return n; } protected void flushToDisk() throws IOException { diskWriter = new OutputStreamWriter(createTempFile(), "UTF-8"); diskWriter.append(mem); mem = null; } protected void onInitComplete() { if (diskWriter != null) { IOUtils.closeSilently(diskWriter); diskWriter = null; } else { //otherwise in-memory content //don't forget to check mem buffer for null in case of empty input reader string = mem == null ? "" : mem.toString(); mem = null; } } public Reader getCharacterStream() { init(); if (isInMemory()) { return new StringReader(string); } else { try { return new InputStreamReader(getTempFileInputStream(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); //should never happen } } } /** * For debug purposes. */ public String toString() { try { return "CLOB: " + StringUtils.consoleFormat(IOUtils.toString(getCharacterStream(), 1024)); } catch (Exception e) { return "CLOB: " + e; } } //--------------- Unsupported methods public String getSubString(long pos, int length) throws SQLException { throw new SQLException("Unsupported operation"); } public InputStream getAsciiStream() throws SQLException { throw new SQLException("Unsupported operation"); } public long position(String searchstr, long start) throws SQLException { throw new SQLException("Unsupported operation"); } public long position(Clob searchstr, long start) throws SQLException { throw new SQLException("Unsupported operation"); } public int setString(long pos, String str) throws SQLException { throw new SQLException("Unsupported operation"); } public int setString(long pos, String str, int offset, int len) throws SQLException { throw new SQLException("Unsupported operation"); } public OutputStream setAsciiStream(long pos) throws SQLException { throw new SQLException("Unsupported operation"); } public Writer setCharacterStream(long pos) throws SQLException { throw new SQLException("Unsupported operation"); } public void truncate(long len) throws SQLException { throw new SQLException("Unsupported operation"); } public Reader getCharacterStream(long pos, long length) throws SQLException { throw new SQLException("Unsupported operation"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy