/*
* Copyright (c) 2004-2016 TADA AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Thomas Hallgren
* Chapman Flack
*/
package org.postgresql.pljava.jdbc;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.charset.CharsetDecoder;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLDataException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLNonTransientException;
import java.sql.SQLInput;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import org.postgresql.pljava.internal.Backend;
/**
* The SQLInputToChunk uses JNI to read from memory that has been allocated by
* the PostgreSQL backend. A user should never make an attempt to create an
* instance of this class. Only internal JNI routines can do that. An instance
* is propagated in a call from the internal JNI layer to the Java layer will
* only survive during that single call. The handle of the instance will be
* invalidated when the call returns and subsequent use of the instance will
* yield a SQLException with the message "Stream is closed".
*
* @author Thomas Hallgren
*/
public class SQLInputFromChunk implements SQLInput
{
private ByteBuffer m_bb;
private static ByteOrder scalarOrder;
private static ByteOrder mirrorOrder;
public SQLInputFromChunk(ByteBuffer bb, boolean isJavaBasedScalar)
throws SQLException
{
m_bb = bb;
if ( isJavaBasedScalar )
{
if ( null == scalarOrder )
scalarOrder = getOrder(true);
m_bb.order(scalarOrder);
}
else
{
if ( null == mirrorOrder )
mirrorOrder = getOrder(false);
m_bb.order(mirrorOrder);
}
}
private ByteOrder getOrder(boolean isJavaBasedScalar) throws SQLException
{
ByteOrder result;
String key = "org.postgresql.pljava.udt.byteorder."
+ ( isJavaBasedScalar ? "scalar" : "mirror" ) + ".p2j";
String val = System.getProperty(key);
if ( "big_endian".equals(val) )
result = ByteOrder.BIG_ENDIAN;
else if ( "little_endian".equals(val) )
result = ByteOrder.LITTLE_ENDIAN;
else if ( "native".equals(val) )
result = ByteOrder.nativeOrder();
else
throw new SQLNonTransientException(
"System property " + key +
" must be big_endian, little_endian, or native", "F0000");
return result;
}
@Override
public Array readArray() throws SQLException
{
throw unsupportedOperationException("readArray");
}
@Override
public InputStream readAsciiStream() throws SQLException
{
throw unsupportedOperationException("readAsciiStream");
}
@Override
public BigDecimal readBigDecimal() throws SQLException
{
return new BigDecimal(this.readString());
}
@Override
public InputStream readBinaryStream() throws SQLException
{
return new ByteArrayInputStream(this.readBytes());
}
@Override
public Blob readBlob() throws SQLException
{
throw unsupportedOperationException("readBlob");
}
@Override
public boolean readBoolean() throws SQLException
{
try
{
return 0 != m_bb.get();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public byte readByte() throws SQLException
{
try
{
return m_bb.get();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public byte[] readBytes() throws SQLException
{
try
{
int len = m_bb.getShort() & 0xffff;
byte[] buffer = new byte[len];
m_bb.get(buffer);
return buffer;
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public Reader readCharacterStream() throws SQLException
{
return new StringReader(this.readString());
}
@Override
public Clob readClob() throws SQLException
{
throw unsupportedOperationException("readClob");
}
@Override
public Date readDate() throws SQLException
{
try
{
return new Date(m_bb.getLong());
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public double readDouble() throws SQLException
{
try
{
return m_bb.getDouble();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public float readFloat() throws SQLException
{
try
{
return m_bb.getFloat();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public int readInt() throws SQLException
{
try
{
return m_bb.getInt();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public long readLong() throws SQLException
{
try
{
return m_bb.getLong();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public Object readObject() throws SQLException
{
throw unsupportedOperationException("readObject");
}
@Override
public Ref readRef() throws SQLException
{
throw unsupportedOperationException("readRef");
}
@Override
public short readShort() throws SQLException
{
try
{
return m_bb.getShort();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public String readString() throws SQLException
{
try
{
int len = m_bb.getShort() & 0xffff;
ByteBuffer bytes = (ByteBuffer)m_bb.slice().limit(len);
m_bb.position(m_bb.position() + len);
return UTF_8.newDecoder().decode(bytes).toString();
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public Time readTime() throws SQLException
{
try
{
return new Time(m_bb.getLong());
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public Timestamp readTimestamp() throws SQLException
{
try
{
return new Timestamp(m_bb.getLong());
}
catch ( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public URL readURL() throws SQLException
{
try
{
return new URL(this.readString());
}
catch( Exception e )
{
throw badRepresentation(e);
}
}
@Override
public boolean wasNull() throws SQLException
{
return false;
}
void close()
{
m_bb = null;
}
private SQLException badRepresentation(Throwable e)
{
if ( e instanceof NullPointerException )
return new SQLNonTransientException(
"attempted read from SQLInput after closing it", "55000", e);
return new SQLDataException(
"Could not read binary representation of user-defined type",
"22P03", e);
}
private SQLException unsupportedOperationException(String op)
{
return new SQLFeatureNotSupportedException(
this.getClass() + "." + op + "() not implemented yet.", "0A000");
}
// ************************************************************
// Non-implementation of JDBC 4 methods.
// ************************************************************
@Override
public RowId readRowId()
throws SQLException
{
throw unsupportedOperationException("readRowId");
}
@Override
public SQLXML readSQLXML()
throws SQLException
{
throw unsupportedOperationException("readSQLXML");
}
@Override
public String readNString()
throws SQLException
{
throw unsupportedOperationException("readNString");
}
@Override
public NClob readNClob()
throws SQLException
{
throw unsupportedOperationException("readNClob");
}
}