/*
* Copyright (c) 1998-2008 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 Rodrigo Westrupp
*/
package com.caucho.quercus.lib.db;
import com.caucho.quercus.annotation.NotNull;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.annotation.ReturnNullAsFalse;
import com.caucho.quercus.annotation.Name;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.LongValue;
import com.caucho.quercus.env.StringValue;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.WriteStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Quercus Oracle OCI-Lob object oriented API.
*/
public class OracleOciLob {
private static final Logger log = Logger.getLogger(OracleOciLob.class.getName());
private static final L10N L = new L10N(OracleOciLob.class);
// The large object
private Object _lob;
// Cache the connection. See writeTemporary()
private Oracle _conn;
// Current position (seek)
private long _currentPointer;
// OCI Lob type: OCI_D_FILE, OCI_D_LOB or OCI_D_ROWID
private int _type;
// This could be refactored in two classes: OracleOciBlob and OracleOciClob
private OutputStream _outputStream;
private Writer _writer;
// Cache classes and methods for oracle.sql.BLOB and oracle.sql.CLOB
private static Class<?> classOracleBLOB;
private static Class<?> classOracleCLOB;
private static Method createTemporaryBLOB;
private static Method createTemporaryCLOB;
private static int BLOB_DURATION_SESSION;
private static int CLOB_DURATION_SESSION;
static {
try {
classOracleBLOB = Class.forName("oracle.sql.BLOB");
classOracleCLOB = Class.forName("oracle.sql.CLOB");
createTemporaryBLOB = classOracleBLOB.getDeclaredMethod("createTemporary",
new Class[] {Connection.class,
Boolean.TYPE,
Integer.TYPE});
createTemporaryCLOB = classOracleCLOB.getDeclaredMethod("createTemporary",
new Class[] {Connection.class,
Boolean.TYPE,
Integer.TYPE});
BLOB_DURATION_SESSION = classOracleBLOB.getDeclaredField("DURATION_SESSION").getInt(null);
CLOB_DURATION_SESSION = classOracleCLOB.getDeclaredField("DURATION_SESSION").getInt(null);
} catch (Exception e) {
log.log(Level.FINER, L.l("Unable to load LOB classes or methods for oracle.sql.BLOB and oracle.sql.CLOB."));
}
}
/**
* Constructor for OracleOciLob
*
* @param type one of the following types:
*
* OCI_D_FILE - a FILE descriptor
*
* OCI_D_LOB - a LOB descriptor
*
* OCI_D_ROWID - a ROWID descriptor
*/
OracleOciLob(Oracle conn, int type)
{
_conn = conn;
_lob = null;
_currentPointer = 0;
_type = type;
_outputStream = null;
_writer = null;
}
/**
* Appends data from the large object to another large object
*/
public boolean append(Env env,
OracleOciLob lobFrom)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
return appendInternalBlob(env, lobFrom);
} else if (_lob instanceof Clob) {
return appendInternalClob(env, lobFrom);
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Closes LOB descriptor
*/
public boolean close(Env env)
{
try {
_currentPointer = 0;
if (_outputStream != null) {
_outputStream.close();
_outputStream = null;
}
if (_writer != null) {
_writer.close();
_writer = null;
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Tests for end-of-file on a large object's descriptor
*/
public boolean eof(Env env)
{
try {
long length = -1;
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
length = blob.length();
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
length = clob.length();
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
if (_currentPointer == length) {
return true;
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return false;
}
/**
* Erases a specified portion of the internal LOB data
*
* @return the actual number of characters/bytes erased or
* FALSE in case of error.
*/
@ReturnNullAsFalse
public LongValue erase(Env env,
@Optional("0") long offset,
@Optional("-1") long length)
{
try {
if (offset < 0) {
offset = 0;
}
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
if (_outputStream != null)
_outputStream.close();
_outputStream = blob.setBinaryStream(offset);
long blobLength = blob.length();
if ((length < 0) || (offset + length > blobLength)) {
length = blobLength - offset;
}
long remaining = length;
byte zeroBuffer[] = new byte[128];
while (remaining >= 128) {
_outputStream.write(zeroBuffer, 0, 128);
remaining -= 128;
}
if (remaining > 0) {
_outputStream.write(zeroBuffer, 0, (int) remaining);
}
_outputStream.close();
_outputStream = null;
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
if (_writer != null)
_writer.close();
_writer = clob.setCharacterStream(offset);
long clobLength = clob.length();
if ((length < 0) || (offset + length > clobLength)) {
length = clobLength - offset;
}
long remaining = length;
char spaceBuffer[] = new char[128];
while (remaining >= 128) {
_writer.write(spaceBuffer, 0, 128);
remaining -= 128;
}
if (remaining > 0) {
_writer.write(spaceBuffer, 0, (int) remaining);
}
_writer.close();
_writer = null;
}
_currentPointer = offset + length;
break;
case OracleModule.OCI_D_ROWID:
break;
}
if (length > 0) {
return LongValue.create(length);
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return null;
}
/**
* Exports LOB's contents to a file
*/
public boolean export(Env env,
Path file,
@Optional("0") long start,
@Optional("-1") long length)
{
try {
WriteStream writeStream = file.openWrite();
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
InputStream is = blob.getBinaryStream();
is.skip(start);
writeStream.writeStream(is);
is.close();
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
Reader reader = clob.getCharacterStream();
reader.skip(start);
writeStream.writeStream(reader);
reader.close();
} else {
writeStream.close();
return false;
}
writeStream.close();
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Flushes/writes buffer of the LOB to the server
*/
public boolean flush(Env env,
@Optional("-1") int flag)
{
try {
if (_outputStream != null) {
_outputStream.flush();
}
if (_writer != null) {
_writer.flush();
}
if (flag == OracleModule.OCI_LOB_BUFFER_FREE) {
close(env);
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Frees resources associated with the LOB descriptor
*/
public boolean free(Env env)
{
try {
_lob = null;
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Returns current state of buffering for the large object
*/
public boolean getBuffering(Env env)
{
// XXX: we assume buffering is always turned on.
return true;
}
/**
* Imports file data to the LOB
*/
@Name("import")
public boolean q_import(Env env, Path file)
{
try {
ReadStream readStream = file.openRead();
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
blob.truncate(0);
if (_outputStream != null)
_outputStream.close();
_outputStream = blob.setBinaryStream(0);
long nbytes;
byte buffer[] = new byte[128];
while ((nbytes = readStream.read(buffer, 0, 128)) > 0) {
_outputStream.write(buffer, 0, (int) nbytes);
_currentPointer += nbytes;
}
_outputStream.close();
_outputStream = null;
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
clob.truncate(0);
if (_writer != null)
_writer.close();
_writer = clob.setCharacterStream(0);
long nchars;
char buffer[] = new char[128];
while ((nchars = readStream.read(buffer, 0, 128)) > 0) {
_writer.write(buffer, 0, (int) nchars);
_currentPointer += nchars;
}
_writer.close();
_writer = null;
} else {
readStream.close();
return false;
}
readStream.close();
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Returns large object's contents
*/
@ReturnNullAsFalse
public Object load(Env env)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
return readInternalBlob(env, -1);
} else if (_lob instanceof Clob) {
return readInternalClob(env, -1);
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return null;
}
/**
* Reads part of the large object
*/
@ReturnNullAsFalse
public Object read(Env env,
long length)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
return readInternalBlob(env, length);
} else if (_lob instanceof Clob) {
return readInternalClob(env, length);
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return null;
}
/**
* Moves the internal pointer to the beginning of the large object
*/
public boolean rewind(Env env)
{
return seek(env, 0, OracleModule.OCI_SEEK_SET);
}
/**
* Saves data to the large object
*/
public boolean save(Env env,
@NotNull String data,
@Optional("0") long offset)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
if (_outputStream != null) {
_outputStream.close();
}
_outputStream = blob.setBinaryStream(offset);
_outputStream.write(data.getBytes());
_outputStream.close();
_outputStream = null;
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
if (_writer != null) {
_writer.close();
}
_writer = clob.setCharacterStream(offset);
_writer.write(data);
_writer.close(); // php/4408
_writer = null;
}
_currentPointer = offset + data.length();
break;
case OracleModule.OCI_D_ROWID:
break;
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Alias of import()
*/
public boolean saveFile(Env env,
Path file)
{
return q_import(env, file);
}
/**
* Sets the internal pointer of the large object
*/
public boolean seek(Env env,
long offset,
@Optional("-1") int whence)
{
try {
switch (whence) {
case OracleModule.OCI_SEEK_SET:
_currentPointer = offset;
break;
case OracleModule.OCI_SEEK_END:
long length = 0;
if (_lob instanceof Blob) {
length = ((Blob) _lob).length();
} else if (_lob instanceof Clob) {
length = ((Clob) _lob).length();
} else {
L.l("Unable to determine large object's length trying to seek with OCI_SEEK_END");
return false;
}
_currentPointer = length + offset;
break;
default: // OCI_SEEK_CUR
_currentPointer += offset;
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Changes current state of buffering for the large object
*/
public boolean setBuffering(Env env,
boolean onOff)
{
// XXX: we assume buffering is always turned on.
return true;
}
/**
* Sets the underlying LOB
*/
protected void setLob(Object lob) {
_lob = lob;
}
/**
* Returns size of large object
*/
@ReturnNullAsFalse
public LongValue size(Env env)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
return LongValue.create(blob.length());
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
return LongValue.create(clob.length());
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return null;
}
/**
* Returns current position of internal pointer of large object
*/
@ReturnNullAsFalse
public LongValue tell(Env env)
{
try {
return LongValue.create(_currentPointer);
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return null;
}
}
public String toString() {
String typeName = "UNKNOWN";
switch (_type) {
case OracleModule.OCI_D_FILE:
typeName = "OCI_D_FILE";
break;
case OracleModule.OCI_D_LOB:
typeName = "OCI_D_LOB";
break;
case OracleModule.OCI_D_ROWID:
typeName = "OCI_D_ROWID";
break;
}
return "OracleOciLob("+typeName+")";
}
/**
* Truncates large object
*/
public boolean truncate(Env env,
@Optional("0") long length)
{
try {
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
blob.truncate(length);
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
clob.truncate(length);
}
break;
case OracleModule.OCI_D_ROWID:
break;
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Writes data to the large object
*/
@ReturnNullAsFalse
public LongValue write(Env env,
String data,
@Optional("-1") long length)
{
try {
long dataLength = data.length();
if ((length < 0) || (length > dataLength)) {
length = dataLength;
}
switch (_type) {
case OracleModule.OCI_D_FILE:
break;
case OracleModule.OCI_D_LOB:
if (_lob instanceof Blob) {
Blob blob = (Blob) _lob;
if (_outputStream == null) {
_outputStream = blob.setBinaryStream(0);
}
_outputStream.write(data.getBytes());
} else if (_lob instanceof Clob) {
Clob clob = (Clob) _lob;
if (_writer == null)
_writer = clob.setCharacterStream(0);
_writer.write(data);
}
_currentPointer += length;
break;
case OracleModule.OCI_D_ROWID:
break;
}
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
}
return null;
}
/**
* Writes temporary large object
*/
public boolean writeTemporary(Env env,
String data,
@Optional("-1") int lobType)
{
try {
if (_type != OracleModule.OCI_D_LOB) {
L.l("Unable to write a temporary LOB into a non-lob object");
return false;
}
if (lobType == OracleModule.OCI_TEMP_BLOB) {
_lob = createTemporaryBLOB.invoke(classOracleBLOB,
new Object[] {_conn,
true,
BLOB_DURATION_SESSION});
} else {
_lob = createTemporaryCLOB.invoke(classOracleCLOB,
new Object[] {_conn,
true,
CLOB_DURATION_SESSION});
}
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
/**
* Alias of export()
*/
public boolean writeToFile(Env env,
Path file,
@Optional("0") long start,
@Optional("-1") long length)
{
return export(env, file, start, length);
}
private boolean appendInternalBlob(Env env,
OracleOciLob lobFrom)
{
try {
Blob blob = (Blob) _lob;
long blobLength = blob.length();
if (_currentPointer != blobLength) {
if (_outputStream != null) {
_outputStream.close();
}
_outputStream = blob.setBinaryStream(blobLength);
_currentPointer = blobLength;
}
Blob blobFrom = (Blob) lobFrom;
InputStream is = blobFrom.getBinaryStream();
long nbytes;
byte buffer[] = new byte[128];
while ((nbytes = is.read(buffer)) > 0) {
_outputStream.write(buffer, 0, (int) nbytes);
_currentPointer += nbytes;
}
is.close();
// Keep this output stream open to be reused.
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
private boolean appendInternalClob(Env env,
OracleOciLob lobFrom)
{
try {
Clob clob = (Clob) _lob;
long clobLength = clob.length();
if (_currentPointer != clobLength) {
if (_writer != null)
_writer.close();
_writer = clob.setCharacterStream(clobLength);
_currentPointer = clobLength;
}
Clob clobFrom = (Clob) lobFrom;
Reader reader = clobFrom.getCharacterStream();
long nchars;
char buffer[] = new char[128];
while ((nchars = reader.read(buffer)) > 0) {
_writer.write(buffer, 0, (int) nchars);
_currentPointer += nchars;
}
reader.close();
// Keep this writer open to be reused.
return true;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return false;
}
}
private StringValue readInternalBlob(Env env, long length)
{
try {
StringValue bb = env.createBinaryBuilder();
Blob blob = (Blob) _lob;
InputStream is = blob.getBinaryStream();
is.skip(_currentPointer);
if (length < 0)
length = Integer.MAX_VALUE;
bb.appendReadAll(is, length);
is.close();
return bb;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return null;
}
}
private StringValue readInternalClob(Env env,
long length)
{
try {
StringValue sb = env.createUnicodeBuilder();
Clob clob = (Clob) _lob;
Reader reader = clob.getCharacterStream();
reader.skip(_currentPointer);
if (length < 0)
length = Integer.MAX_VALUE;
sb.append(reader, length);
reader.close();
return sb;
} catch (Exception ex) {
log.log(Level.FINE, ex.toString(), ex);
return null;
}
}
}