/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.core.types;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.teiid.core.CorePlugin;
import org.teiid.core.types.InputStreamFactory.StorageMode;
import org.teiid.core.util.MultiArrayOutputStream;
/**
* A large value object which can be streamable in chunks of data each time
*
* <p>A reference stream id is tuple source id for a Streamble object where the
* object is in buffer manager, but the contents will never be written to disk;
* this is the ID that client needs to reference to get the chunk of data.
*/
public abstract class Streamable<T> implements Externalizable {
private static final Logger logger = Logger.getLogger(Streamable.class.getName());
private static final long serialVersionUID = -8252488562134729374L;
private static AtomicLong counter = new AtomicLong();
public static final String ENCODING = "UTF-8"; //$NON-NLS-1$
public static final Charset CHARSET = Charset.forName(ENCODING);
public static final int STREAMING_BATCH_SIZE_IN_BYTES = 102400; // 100K
private String referenceStreamId = String.valueOf(counter.getAndIncrement());
protected transient volatile T reference;
protected long length = -1;
public Streamable() {
}
public Streamable(T reference) {
if (reference == null) {
throw new IllegalArgumentException(CorePlugin.Util.getString("Streamable.isNUll")); //$NON-NLS-1$
}
this.reference = reference;
}
/**
* Returns the cached length. May be binary or character based.
* @return
*/
public long getLength() {
return length;
}
abstract long computeLength() throws SQLException;
public long length() throws SQLException {
if (length == -1) {
if (InputStreamFactory.getStorageMode(this.reference) == StorageMode.FREE) {
throw new SQLException("Already freed or streaming"); //$NON-NLS-1$
}
length = computeLength();
}
return length;
}
public T getReference() {
return reference;
}
public void setReference(T reference) {
this.reference = reference;
}
public String getReferenceStreamId() {
return this.referenceStreamId;
}
public void setReferenceStreamId(String id) {
this.referenceStreamId = id;
}
@Override
public String toString() {
if (reference == null) {
return getClass().getName() + " " + this.referenceStreamId; //$NON-NLS-1$
}
return reference.toString();
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
length = in.readLong();
this.referenceStreamId = (String)in.readObject();
if (referenceStreamId == null) {
//we expect the data inline
readReference(in);
}
}
protected abstract void readReference(ObjectInput in) throws IOException;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
try {
length();
} catch (SQLException e) {
}
out.writeLong(length);
boolean writeBuffer = false;
MultiArrayOutputStream baos = null;
if (referenceStreamId == null) {
//TODO: detect when this buffering is not necessary
if (length > Integer.MAX_VALUE) {
throw new AssertionError("Should not inline a lob of length " + length); //$NON-NLS-1$
}
if (length > 0) {
baos = new MultiArrayOutputStream((int)length);
} else {
baos = new MultiArrayOutputStream(256);
}
DataOutputStream dataOutput = new DataOutputStream(baos);
try {
writeReference(dataOutput);
dataOutput.close();
writeBuffer = true;
} catch (IOException e) {
logger.log(Level.WARNING, e.getMessage());
referenceStreamId = "error"; //$NON-NLS-1$
}
}
out.writeObject(referenceStreamId);
if (writeBuffer) {
baos.writeTo(out);
}
}
protected boolean isBinary() {
return true;
}
protected abstract void writeReference(DataOutput out) throws IOException;
}