blob: c272eaa0773fc751efe163212825c3440f2a52ca [file] [log] [blame]
/*
* 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.ode.daohib.bpel.hobj;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Types;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.usertype.UserType;
/**
* Custom Hibernate datatype that compresses (GZip) byte arrays
* to increase performance and save disk space.
*/
public class GZipDataType implements UserType {
private static final Logger log = LoggerFactory.getLogger(GZipDataType.class);
public static final int[] SQL_TYPES = new int[] { Types.BLOB };
public static final Class RETURNED_CLASS = new byte[0].getClass();
/** For backward compatibility with non-zipped data, prefix the gzip stream with a magic sequence */
public static final byte[] GZIP_PREFIX = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x03, 0x02, 0x01, 0x00 };
// Compression statistics
private static long _totalBytesBefore = 0;
private static long _totalBytesAfter = 0;
private static volatile long _lastLogTime = 0;
private static final Object STATS_LOCK = new Object();
private static volatile boolean _compressionEnabled = System.getProperty("org.apache.ode.daohib.bpel.hobj.GZipDataType.enabled", "true").equalsIgnoreCase("true");
/** Reconstruct an object from the cacheable representation */
public Object assemble(Serializable cached, Object owner) {
// serializable representation is same
return cached;
}
/** Transform the object into its cacheable representation */
public Serializable disassemble(Object value) {
// as-is
return (Serializable) value;
}
/** Return a deep copy of the persistent state */
public Object deepCopy(Object value) {
if (value == null) return null;
return ((byte[]) value).clone();
}
/** Compare two instances of the class mapped by this type for persistence "equality". */
public boolean equals(Object x, Object y) {
byte[] buf1 = (byte[]) x;
byte[] buf2 = (byte[]) y;
if (buf1 == buf2) return true;
if (buf1 == null && buf2 != null) return false;
if (buf1 != null && buf2 == null) return false;
if (buf1.length != buf2.length) return false;
for (int i=0; i<buf1.length; i++) {
if (buf1[i] != buf2[i]) return false;
}
return true;
}
/** Get a hashcode for the instance, consistent with persistence "equality" */
public int hashCode(Object x) {
if (x == null) return 0;
byte[] buf = (byte[]) x;
int hash = 0;
for (int i=0; i<buf.length; i++) {
hash += buf[i];
}
return hash;
}
/** Are objects of this type mutable? */
public boolean isMutable() {
return false;
}
/** Retrieve an instance of the mapped class from a JDBC resultset. */
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
if (names.length != 1) throw new IllegalStateException("Expected a single column name instead of "+names.length);
byte[] buf = rs.getBytes(names[0]);
if (buf == null) {
return null;
}
if (buf.length >= GZIP_PREFIX.length) {
boolean gzip = true;
for (int i=0; i<GZIP_PREFIX.length; i++) {
if (buf[i] != GZIP_PREFIX[i]) {
gzip = false;
break;
}
}
if (gzip) {
buf = gunzip(new ByteArrayInputStream(buf, GZIP_PREFIX.length, buf.length-GZIP_PREFIX.length));
}
}
return buf;
}
/** Write an instance of the mapped class to a prepared statement. */
public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
byte[] buf = (byte[]) value;
if (buf != null) {
synchronized (STATS_LOCK) {
if (_totalBytesBefore > Integer.MAX_VALUE) {
// prevent overflow - renormalize to percent value
_totalBytesAfter = _totalBytesAfter*100/_totalBytesBefore;
_totalBytesBefore = 100;
}
_totalBytesBefore += buf.length;
}
// only try to zip if we have more than 100 bytes
if (buf != null && buf.length > 100 && _compressionEnabled) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(buf.length);
for (int i=0; i<GZIP_PREFIX.length; i++) {
baos.write(GZIP_PREFIX[i]);
}
gzip((byte[]) value, baos);
byte[] zipped = baos.toByteArray();
// only use zipped representation if we gain 2% or more
if (zipped.length*100/buf.length < 99) {
buf = zipped;
}
}
synchronized (STATS_LOCK) {
_totalBytesAfter += buf.length;
}
if (log.isDebugEnabled()) {
long now = System.currentTimeMillis();
if (_lastLogTime+5000 < now) {
log.debug("Average compression ratio: "+ (_totalBytesAfter*100/_totalBytesBefore)+"%");
_lastLogTime = now;
}
}
}
st.setBytes(index, buf);
}
/** During merge, replace the existing (target) value in the entity we are
* merging to with a new (original) value from the detached entity we are merging.
*/
public Object replace(Object original, Object target, Object owner) {
return original;
}
/** The class returned by nullSafeGet(). */
public Class returnedClass() {
return RETURNED_CLASS;
}
/** Return the SQL type codes for the columns mapped by this type. */
public int[] sqlTypes() {
return SQL_TYPES;
}
/**
* Compress (using gzip algorithm) a byte array into an output stream.
*/
public static void gzip(byte[] content, OutputStream out) {
try {
GZIPOutputStream zip = new GZIPOutputStream(out);
zip.write(content, 0, content.length);
zip.finish();
zip.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Decompress (using gzip algorithm) a byte array.
*/
public static byte[] gunzip(InputStream input) {
try {
GZIPInputStream unzip = new GZIPInputStream(input);
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
byte[] buf = new byte[4096];
int len;
while ((len = unzip.read(buf)) > 0) {
baos.write(buf, 0, len);
}
unzip.close();
return baos.toByteArray();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}