org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.spi.commons.value;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.QValueFactory;
import org.apache.jackrabbit.util.TransientFileFactory;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import java.io.ByteArrayInputStream;
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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
/**
* QValueFactoryImpl
...
*/
public class QValueFactoryImpl extends AbstractQValueFactory {
private static final QValueFactory INSTANCE = new QValueFactoryImpl();
protected QValueFactoryImpl() {
}
public static QValueFactory getInstance() {
return INSTANCE;
}
/**
* @see QValueFactory#create(byte[])
*/
public QValue create(byte[] value) {
if (value == null) {
throw new IllegalArgumentException("Cannot create QValue from null value.");
}
return new BinaryQValue(value);
}
/**
* @see QValueFactory#create(InputStream)
*/
public QValue create(InputStream value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Cannot create QValue from null value.");
}
return new BinaryQValue(value);
}
/**
* @see QValueFactory#create(File)
*/
public QValue create(File value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Cannot create QValue from null value.");
}
return new BinaryQValue(value);
}
//--------------------------------------------------------< Inner Class >---
/**
* BinaryQValue
represents a binary Value
which is
* backed by a resource or byte[]. Unlike BinaryValue
it has no
* state, i.e. the getStream()
method always returns a fresh
* InputStream
instance.
*/
private static class BinaryQValue extends AbstractQValue implements Serializable {
/**
* A dummy value for calling the constructor of AbstractQValue
*/
private static final Object DUMMY_VALUE = new Serializable() {
private static final long serialVersionUID = 2849470089518940117L;
};
/**
* empty array
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**
* max size for keeping tmp data in memory
*/
private static final int MAX_BUFFER_SIZE = 0x10000;
/**
* underlying file
*/
private transient File file;
/**
* flag indicating if this instance represents a temporary value
* whose dynamically allocated resources can be explicitly freed on
* {@link #discard()}.
*/
private transient boolean temp;
/**
* Buffer for small-sized data
*/
private byte[] buffer = BinaryQValue.EMPTY_BYTE_ARRAY;
/**
* Creates a new BinaryQValue
instance from an
* InputStream
. The contents of the stream is spooled
* to a temporary file or to a byte buffer if its size is smaller than
* {@link #MAX_BUFFER_SIZE}.
*
* The new instance represents a temporary value whose dynamically
* allocated resources will be freed explicitly on {@link #discard()}.
*
* @param in stream to be represented as a BinaryQValue
instance
* @throws IOException if an error occurs while reading from the stream or
* writing to the temporary file
*/
private BinaryQValue(InputStream in) throws IOException {
this(in, true);
}
/**
* Creates a new BinaryQValue
instance from an
* InputStream
. The contents of the stream is spooled
* to a temporary file or to a byte buffer if its size is smaller than
* {@link #MAX_BUFFER_SIZE}.
*
* The temp
parameter governs whether dynamically allocated
* resources will be freed explicitly on {@link #discard()}. Note that any
* dynamically allocated resources (temp file/buffer) will be freed
* implicitly once this instance has been gc'ed.
*
* @param in stream to be represented as a BinaryQValue
instance
* @param temp flag indicating whether this instance represents a
* temporary value whose resources can be explicitly freed
* on {@link #discard()}.
* @throws IOException if an error occurs while reading from the stream or
* writing to the temporary file
*/
private BinaryQValue(InputStream in, boolean temp) throws IOException {
super(DUMMY_VALUE, PropertyType.BINARY);
byte[] spoolBuffer = new byte[0x2000];
int read;
int len = 0;
OutputStream out = null;
File spoolFile = null;
try {
while ((read = in.read(spoolBuffer)) > 0) {
if (out != null) {
// spool to temp file
out.write(spoolBuffer, 0, read);
len += read;
} else if (len + read > BinaryQValue.MAX_BUFFER_SIZE) {
// threshold for keeping data in memory exceeded;
// create temp file and spool buffer contents
TransientFileFactory fileFactory = TransientFileFactory.getInstance();
spoolFile = fileFactory.createTransientFile("bin", null, null);
out = new FileOutputStream(spoolFile);
out.write(buffer, 0, len);
out.write(spoolBuffer, 0, read);
buffer = null;
len += read;
} else {
// reallocate new buffer and spool old buffer contents
byte[] newBuffer = new byte[len + read];
System.arraycopy(buffer, 0, newBuffer, 0, len);
System.arraycopy(spoolBuffer, 0, newBuffer, len, read);
buffer = newBuffer;
len += read;
}
}
} finally {
in.close();
if (out != null) {
out.close();
}
}
// init vars
file = spoolFile;
this.temp = temp;
// buffer is EMPTY_BYTE_ARRAY (default value)
}
/**
* Creates a new BinaryQValue
instance from a
* byte[]
array.
*
* @param bytes byte array to be represented as a BinaryQValue
* instance
*/
private BinaryQValue(byte[] bytes) {
super(DUMMY_VALUE, PropertyType.BINARY);
buffer = bytes;
file = null;
// this instance is not backed by a temporarily allocated buffer
temp = false;
}
/**
* Creates a new BinaryQValue
instance from a File
.
*
* @param file file to be represented as a BinaryQValue
instance
* @throws IOException if the file can not be read
*/
private BinaryQValue(File file) throws IOException {
super(DUMMY_VALUE, PropertyType.BINARY);
String path = file.getCanonicalPath();
if (!file.isFile()) {
throw new IOException(path + ": the specified file does not exist");
}
if (!file.canRead()) {
throw new IOException(path + ": the specified file can not be read");
}
// this instance is backed by a 'real' file
this.file = file;
// this instance is not backed by temporarily allocated resource/buffer
temp = false;
// buffer is EMPTY_BYTE_ARRAY (default value)
}
//---------------------------------------------------------< QValue >---
/**
* Returns the length of this BinaryQValue
.
*
* @return The length, in bytes, of this BinaryQValue
,
* or -1L if the length can't be determined.
* @see QValue#getLength()
*/
@Override
public long getLength() {
if (file != null) {
// this instance is backed by a 'real' file
if (file.exists()) {
return file.length();
} else {
return -1;
}
} else {
// this instance is backed by an in-memory buffer
return buffer.length;
}
}
/**
* @see QValue#getStream()
*/
public InputStream getStream() throws RepositoryException {
// always return a 'fresh' stream
if (file != null) {
// this instance is backed by a 'real' file
try {
return new FileInputStream(file);
} catch (FileNotFoundException fnfe) {
throw new RepositoryException("file backing binary value not found",
fnfe);
}
} else {
return new ByteArrayInputStream(buffer);
}
}
/**
* @see QValue#getName()
*/
@Override
public Name getName() throws RepositoryException {
throw new UnsupportedOperationException();
}
/**
* @see QValue#getPath()
*/
@Override
public Path getPath() throws RepositoryException {
throw new UnsupportedOperationException();
}
/**
* Frees temporarily allocated resources such as temporary file, buffer, etc.
* If this BinaryQValue
is backed by a persistent resource
* calling this method will have no effect.
* @see QValue#discard()
*/
@Override
public void discard() {
if (!temp) {
// do nothing if this instance is not backed by temporarily
// allocated resource/buffer
return;
}
if (file != null) {
// this instance is backed by a temp file
file.delete();
} else if (buffer != null) {
// this instance is backed by an in-memory buffer
buffer = EMPTY_BYTE_ARRAY;
}
}
//-----------------------------------------------< java.lang.Object >---
/**
* Returns a string representation of this BinaryQValue
* instance. The string representation of a resource backed value is
* the path of the underlying resource. If this instance is backed by an
* in-memory buffer the generic object string representation of the byte
* array will be used instead.
*
* @return A string representation of this BinaryQValue
instance.
*/
@Override
public String toString() {
if (file != null) {
// this instance is backed by a 'real' file
return file.toString();
} else {
// this instance is backed by an in-memory buffer
return buffer.toString();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof BinaryQValue) {
BinaryQValue other = (BinaryQValue) obj;
return ((file == null ? other.file == null : file.equals(other.file))
&& Arrays.equals(buffer, other.buffer));
}
return false;
}
/**
* Returns zero to satisfy the Object equals/hashCode contract.
* This class is mutable and not meant to be used as a hash key.
*
* @return always zero
* @see Object#hashCode()
*/
@Override
public int hashCode() {
return 0;
}
//---------------------------------------------------< Serializable >---
private void writeObject(ObjectOutputStream out)
throws IOException {
out.defaultWriteObject();
// write hasFile marker
out.writeBoolean(file != null);
// then write file if necessary
if (file != null) {
byte[] buffer = new byte[4096];
int bytes;
InputStream stream = new FileInputStream(file);
while ((bytes = stream.read(buffer)) >= 0) {
// Write a segment of the input stream
if (bytes > 0) {
// just to ensure that no 0 is written
out.writeInt(bytes);
out.write(buffer, 0, bytes);
}
}
// Write the end of stream marker
out.writeInt(0);
// close stream
stream.close();
}
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
boolean hasFile = in.readBoolean();
if (hasFile) {
file = File.createTempFile("binary-qvalue", "bin");
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[4096];
for (int bytes = in.readInt(); bytes > 0; bytes = in.readInt()) {
if (buffer.length < bytes) {
buffer = new byte[bytes];
}
in.readFully(buffer, 0, bytes);
out.write(buffer, 0, bytes);
}
out.close();
}
// deserialized value is always temp
temp = true;
}
}
}