/*
* Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: DatabaseRawStore.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.cmf.dam.contentstores;
import com.uwyn.rife.cmf.dam.contentstores.exceptions.*;
import com.uwyn.rife.database.*;
import com.uwyn.rife.database.queries.*;
import com.uwyn.rife.cmf.Content;
import com.uwyn.rife.cmf.ContentInfo;
import com.uwyn.rife.cmf.MimeType;
import com.uwyn.rife.cmf.dam.ContentDataUser;
import com.uwyn.rife.cmf.dam.ContentStore;
import com.uwyn.rife.cmf.dam.exceptions.ContentManagerException;
import com.uwyn.rife.cmf.format.Formatter;
import com.uwyn.rife.cmf.format.exceptions.FormatException;
import com.uwyn.rife.cmf.transform.ContentTransformer;
import com.uwyn.rife.config.RifeConfig;
import com.uwyn.rife.database.exceptions.DatabaseException;
import com.uwyn.rife.engine.ElementSupport;
import com.uwyn.rife.tools.Convert;
import com.uwyn.rife.tools.ExceptionUtils;
import com.uwyn.rife.tools.FileUtils;
import com.uwyn.rife.tools.exceptions.InnerClassException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
public abstract class DatabaseRawStore extends DbQueryManager implements ContentStore
{
private ArrayList<MimeType> mMimeTypes = new ArrayList<MimeType>();
public DatabaseRawStore(Datasource datasource)
{
super(datasource);
addMimeType(MimeType.RAW);
}
protected void addMimeType(MimeType mimeType)
{
mMimeTypes.add(mimeType);
}
public Collection<MimeType> getSupportedMimeTypes()
{
return mMimeTypes;
}
public String getContentType(ContentInfo contentInfo)
{
MimeType mimeType = MimeType.getMimeType(contentInfo.getMimeType());
if (!getSupportedMimeTypes().contains(mimeType))
{
return null;
}
Map<String, String> attributes = contentInfo.getAttributes();
if (attributes != null)
{
if (attributes.containsKey("content-type"))
{
return attributes.get("content-type");
}
}
if (contentInfo.hasName())
{
return RifeConfig.Mime.getMimeType(FileUtils.getExtension(contentInfo.getName()));
}
return null;
}
public Formatter getFormatter(MimeType mimeType, boolean fragment)
{
if (!getSupportedMimeTypes().contains(mimeType))
{
return null;
}
return mimeType.getFormatter();
}
public String getContentForHtml(int id, ContentInfo info, ElementSupport element, String serveContentExitName)
throws ContentManagerException
{
return "";
}
protected boolean _install(CreateTable createTableContentInfo, CreateTable createTableContentChunk)
throws ContentManagerException
{
assert createTableContentInfo != null;
assert createTableContentChunk != null;
try
{
executeUpdate(createTableContentInfo);
executeUpdate(createTableContentChunk);
}
catch (DatabaseException e)
{
throw new InstallContentStoreErrorException(e);
}
return true;
}
protected boolean _remove(DropTable dropTableContentInfo, DropTable dropTableContentChunk)
throws ContentManagerException
{
assert dropTableContentInfo != null;
try
{
executeUpdate(dropTableContentChunk);
executeUpdate(dropTableContentInfo);
}
catch (DatabaseException e)
{
throw new RemoveContentStoreErrorException(e);
}
return true;
}
protected boolean _deleteContentData(final Delete deleteContentInfo, final Delete deleteContentChunk, final int id)
throws ContentManagerException
{
if (id < 0) throw new IllegalArgumentException("id must be positive");
assert deleteContentInfo != null;
assert deleteContentChunk != null;
Boolean result = null;
try
{
result = inTransaction(new DbTransactionUser() {
public Boolean useTransaction() throws InnerClassException
{
if (0 == executeUpdate(deleteContentChunk, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id);
}
}))
{
return false;
}
return 0 != executeUpdate(deleteContentInfo, new DbPreparedStatementHandler()
{
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id);
}
});
}
});
}
catch (DatabaseException e)
{
throw new DeleteContentDataErrorException(id, e);
}
return result != null && result.booleanValue();
}
protected int _getSize(Select retrieveSize, final int id)
throws ContentManagerException
{
if (id < 0) throw new IllegalArgumentException("id must be positive");
assert retrieveSize != null;
try
{
return executeGetFirstInt(retrieveSize, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id);
}
});
}
catch (DatabaseException e)
{
throw new RetrieveSizeErrorException(id, e);
}
}
protected boolean _hasContentData(Select hasContentData, final int id)
throws ContentManagerException
{
if (id < 0) throw new IllegalArgumentException("id must be positive");
assert hasContentData != null;
try
{
return executeHasResultRows(hasContentData, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id);
}
});
}
catch (DatabaseException e)
{
throw new HasContentDataErrorException(id, e);
}
}
protected String getContentSizeColumnName()
{
return "size";
}
protected boolean _storeContentData(final Insert storeContentInfo, final Insert storeContentChunk, final int id, Content content, ContentTransformer transformer)
throws ContentManagerException
{
if (id < 0) throw new IllegalArgumentException("id must be positive");
if (content != null &&
content.getData() != null &&
!(content.getData() instanceof InputStream) &&
!(content.getData() instanceof byte[])) throw new IllegalArgumentException("the content data must be of type InputStream or byte[]");
assert storeContentInfo != null;
assert storeContentChunk != null;
final InputStream typed_data;
if (null == content ||
null == content.getData())
{
typed_data = null;
}
else
{
if (content.getData() instanceof byte[])
{
Content cloned_content = content.clone();
cloned_content.setData(new ByteArrayInputStream((byte[])content.getData()));
cloned_content.setCachedLoadedData(null);
content = cloned_content;
}
Formatter formatter = null;
if (!Convert.toBoolean(content.getAttribute("unformatted"), false))
{
formatter = getFormatter(content.getMimeType(), content.isFragment());
}
if (formatter != null)
{
try
{
typed_data = (InputStream)formatter.format(content, transformer);
}
catch (FormatException e)
{
throw new StoreContentDataErrorException(id, e);
}
}
else
{
typed_data = (InputStream)content.getData();
}
}
// store the data
try
{
Boolean success = inTransaction(new DbTransactionUser() {
public Object useTransaction()
throws InnerClassException
{
try
{
final int size = storeChunks(storeContentChunk, id, typed_data);
if (size < 0)
{
rollback();
}
if (executeUpdate(storeContentInfo, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id)
.setInt(getContentSizeColumnName(), size);
}
}) <= 0)
{
rollback();
}
}
catch (IOException e)
{
throwException(e);
}
return true;
}
});
return null != success && success.booleanValue();
}
catch (InnerClassException e)
{
throw new StoreContentDataErrorException(id, e.getCause());
}
catch (DatabaseException e)
{
throw new StoreContentDataErrorException(id, e);
}
}
protected int storeChunks(Insert storeContentChunk, final int id, InputStream data)
throws IOException
{
class Scope {
int size = 0;
int length = -1;
int ordinal = 0;
byte[] buffer = null;
}
final Scope s = new Scope();
if (data != null)
{
s.buffer = new byte[65535];
while ((s.length = data.read(s.buffer)) != -1)
{
s.size += s.length;
if (executeUpdate(storeContentChunk, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id)
.setInt("ordinal", s.ordinal)
.setBinaryStream("chunk", new ByteArrayInputStream(s.buffer), s.length);
}
}) <= 0)
{
return -1;
}
s.ordinal++;
}
}
return s.size;
}
protected int storeChunksNoStream(Insert storeContentChunk, final int id, InputStream data)
throws IOException
{
class Scope {
int size = 0;
int length = -1;
int ordinal = 0;
byte[] buffer = null;
byte[] buffer_swp = null;
}
final Scope s = new Scope();
if (data != null)
{
s.buffer = new byte[65535];
while ((s.length = data.read(s.buffer)) != -1)
{
s.size += s.length;
if (s.length < s.buffer.length)
{
byte[] new_buffer = new byte[s.length];
System.arraycopy(s.buffer, 0, new_buffer, 0, s.length);
s.buffer_swp = s.buffer;
s.buffer = new_buffer;
}
if (executeUpdate(storeContentChunk, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id)
.setInt("ordinal", s.ordinal)
.setBytes("chunk", s.buffer);
}
}) <= 0)
{
return -1;
}
if (s.buffer_swp != null)
{
s.buffer = s.buffer_swp;
s.buffer_swp = null;
}
s.ordinal++;
}
}
return s.size;
}
protected <ResultType> ResultType _useContentData(Select retrieveContentChunks, final int id, ContentDataUser user)
throws ContentManagerException
{
if (id < 0) throw new IllegalArgumentException("id must be positive");
if (null == user) throw new IllegalArgumentException("user can't be null");
assert retrieveContentChunks != null;
try
{
InputStream data = RawContentStream.getInstance(this, retrieveContentChunks, id);
try
{
return (ResultType)user.useContentData(data);
}
finally
{
if (data != null)
{
try
{
data.close();
}
catch (IOException e)
{
throw new UseContentDataErrorException(id, e);
}
}
}
}
catch (DatabaseException e)
{
throw new UseContentDataErrorException(id, e);
}
}
protected DbPreparedStatement getStreamPreparedStatement(Query query, DbConnection connection)
{
DbPreparedStatement statement = connection.getPreparedStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
statement.setFetchDirection(ResultSet.FETCH_FORWARD);
statement.setFetchSize(1);
return statement;
}
protected void _serveContentData(final Select retrieveContentChunks, final ElementSupport element, final int id)
throws ContentManagerException
{
if (null == element) throw new IllegalArgumentException("element can't be null");
if (id < 0)
{
element.defer();
return;
}
assert retrieveContentChunks != null;
// set the content length header
final int size = getSize(id);
if (size < 0)
{
element.defer();
return;
}
element.setContentLength(size);
try
{
Boolean success = executeQuery(retrieveContentChunks, new DbPreparedStatementHandler() {
public DbPreparedStatement getPreparedStatement(Query query, DbConnection connection)
{
return getStreamPreparedStatement(query, connection);
}
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt("contentId", id);
}
public Object concludeResults(DbResultSet resultset)
throws SQLException
{
if (!resultset.next())
{
return false;
}
// output the content
OutputStream os = element.getOutputStream();
try
{
serveChunks(resultset, os, size);
os.flush();
}
catch (IOException e)
{
// don't do anything, the client has probably disconnected
}
return true;
}
});
if (null == success || !success.booleanValue())
{
element.defer();
}
}
catch (DatabaseException e)
{
Logger.getLogger("com.uwyn.rife.cmf").severe(ExceptionUtils.getExceptionStackTrace(e));
element.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
protected void serveChunks(DbResultSet resultset, OutputStream os, int size)
throws SQLException, IOException
{
byte[] buffer = new byte[512];
do
{
InputStream is = resultset.getBinaryStream("chunk");
BufferedInputStream buffered_raw_is = new BufferedInputStream(is, 512);
int buffer_size = 0;
try
{
while ((buffer_size = buffered_raw_is.read(buffer)) != -1)
{
os.write(buffer, 0, buffer_size);
}
}
catch (IOException e)
{
// don't do anything, the client has probably disconnected
}
}
while (resultset.next());
}
}