Code:
/*
* Copyright 2005 the original author or authors.
*
* Licensed 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 ...;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.orm.hibernate3.support.AbstractLobType;
import org.hibernate.HibernateException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.transaction.TransactionManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.Types;
import java.io.*;
/**
* <p>Hibernate UserType implementation for files that get mapped to BLOBs. Retrieves the LobHandler to use from
* LocalSessionFactoryBean at config time.</p>
* <p>Can also be defined in generic Hibernate mappings, as DefaultLobCreator will work with most JDBC-compliant
* database drivers. In this case, the field type does not have to be BLOB: For databases like MySQL and MS SQL
* Server, any large enough binary type will work.</p>
* <p>This UserType creates a temporary file using {@link #createTemporaryFile(java.io.File, java.sql.ResultSet, String[], Object)}
* method. By default the file name is generated using the value returned from {@link Object#hashCode()} on the owner.
* This can be modified by extending this class and re-implementing the
* {@link #createTemporaryFile(java.io.File, java.sql.ResultSet, String[], Object)} method.</p>
*
* <p>Based on org.springframework.orm.hibernate3.support.BlobByteArrayType by Juergen Hoeller.</p>
*
* @author Dan Washusen
* @version $Id: FileType.java,v 1.1 2005/03/24 23:37:55 dan Exp $
*/
public class FileType
extends AbstractLobType {
private static final Log log = LogFactory.getLog(FileType.class);
/**
* Constructor used by Hibernate: fetches config-time LobHandler and
* config-time JTA TransactionManager from LocalSessionFactoryBean.
*
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeLobHandler
* @see org.springframework.orm.hibernate3.LocalSessionFactoryBean#getConfigTimeTransactionManager
*/
public FileType() {
super();
}
/**
* Constructor used for testing: takes an explicit LobHandler
* and an explicit JTA TransactionManager (can be null).
*/
protected FileType(LobHandler lobHandler, TransactionManager jtaTransactionManager) {
super(lobHandler, jtaTransactionManager);
}
/**
* Creates a temporary file using the {@link #createTemporaryFile(java.io.File, java.sql.ResultSet, String[], Object)}
* method and then writes the contents of the blob to the the temporary file.
* @param resultSet a JDBC result set
* @param columns the column names
* @param owner the containing entity
* @param lobHandler the LobHandler to use
* @todo This class will run into problems in a threaded environment, two threads creating the same file...
* @todo This could be a lot more efficent when writing the temporary file
*/
protected Object nullSafeGetInternal(ResultSet resultSet, String[] columns, Object owner, LobHandler lobHandler)
throws SQLException, IOException, HibernateException {
// we only handle one column, so panic if it isn't so
if (columns == null || columns.length != 1)
throw new HibernateException("Only one column name can be used for the " + getClass() + " user type");
// check the temporary directory
File tempDirectory = checkTempDirectory(resultSet, columns, owner);
// delegate to the createTemporaryFile method to create file
File file = createTemporaryFile(tempDirectory, resultSet, columns, owner);
// write the contents of the file to disk
BufferedInputStream inputStream = new BufferedInputStream(lobHandler.getBlobAsBinaryStream(resultSet, columns[0]));
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
// we really should read and write more than one byte at a time...
int foo = -1;
while ((foo = inputStream.read()) != -1) {
outputStream.write(foo);
}
outputStream.flush();
outputStream.close();
inputStream.close();
return file;
}
/**
* Checks for the existance of the temporary directory specifed by the java.io.tmpdir system property, attempting
* to create the directory if it doesn't exist.
* @param resultSet a JDBC result set
* @param columns the column names
* @param owner the containing entity
* @return the temporary directory
* @throws IOException
*/
protected File checkTempDirectory(ResultSet resultSet, String[] columns, Object owner)
throws IOException {
File tempDirectory = new File(System.getProperty("java.io.tmpdir"));
log.debug("Using temporary directory " + tempDirectory.getAbsolutePath());
if (!tempDirectory.exists()) {
log.info("Creating temporary directory " + tempDirectory.getAbsolutePath());
tempDirectory.createNewFile();
}
return tempDirectory;
}
/**
* Creates a temporary file to write the data to. By default the method generates a file name based on
* the hash code of the owner using the tempDirectory parameter as the parent.
* If the file exists it will be deleted. The file is then marked for deletion when the JVM exits.
* <p>If you wish to change the default behavior, this is the method to override.</p.
* @param tempDirectory The directory to create the temporary files
* @param resultSet a JDBC result set
* @param columns the column names
* @param owner the containing entity
* @return The tempoary file
* @throws HibernateException if the temporary file exists and can't be deleted
*/
protected File createTemporaryFile(File tempDirectory, ResultSet resultSet, String[] columns, Object owner) {
String filename = String.valueOf(owner.hashCode());
File file = new File(tempDirectory, filename);
log.debug("Creating temporary file at " + file.getAbsolutePath());
// delete the file if it already exists
if (file.exists()) {
log.debug(file.getAbsolutePath() + " is in the way, removing...");
if (!file.delete()) {
throw new HibernateException("File " + file.getAbsolutePath() + " was in the way and couldn't be deleted");
}
}
// as the file is only a temporary representation of a database column
// we are going to mark the file for deleting when the JVM exists
file.deleteOnExit();
return file;
}
/**
* Creates a buffered {@link FileInputStream} from the file provided as the value parameter.
* @param statement the PreparedStatement to set on
* @param index the statement parameter index
* @param value the file
* @param lobCreator the LobCreator to use
* @throws SQLException if thrown by JDBC methods
* @throws FileNotFoundException If the file specified by the value parameter could not be found
* @throws HibernateException if the file is bigger than {@link Integer.MAX_VALUE}
*/
protected void nullSafeSetInternal(PreparedStatement statement, int index, Object value, LobCreator lobCreator)
throws SQLException, HibernateException, FileNotFoundException {
// check the file length, it can't be greater than Integer.MAX_VALUE because we are going to cast the length
// down to an int alter
if (((File) value).length() >= Integer.MAX_VALUE)
throw new HibernateException("File size exceeds " + Integer.MAX_VALUE + " in length.");
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream((File) value));
lobCreator.setBlobAsBinaryStream(statement, index, inputStream, (int) ((File) value).length());
}
public int[] sqlTypes() {
return new int[]{Types.BLOB};
}
public Class returnedClass() {
return File.class;
}
public boolean isMutable() {
return true;
}
}