org.modeshape.jcr.value.binary.DatabaseBinaryStore Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* 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 org.modeshape.jcr.value.binary;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.JcrI18n;
import org.modeshape.jcr.value.BinaryKey;
import org.modeshape.jcr.value.BinaryValue;
/**
* A {@link BinaryStore} implementation that uses a database for persisting binary values.
*
* This binary store implementation establishes a connection to the specified database and then attempts to determine which type
* of database is being used. ModeShape is aware of the following database types:
*
* mysql
* postgres
* derby
* hsql
* h2
* sqlite
* db2
* db2_390
* informix
* interbase
* firebird
* sqlserver
* access
* oracle
* sybase
*
* This binary store implementation then uses DDL and DML statements to create the table(s) if not already existing and to perform
* the various operations required of a binary store. ModeShape can use database-specific statements, although a default set of
* SQL-99 statements are used as a fallback.
*
*
* These statements are read from a property file named "binary_store_{type}_database.properties
", where where "
* {type}
" is one of the above-mentioned database type strings. These properties files are expected to be found on
* the classpath directly under "org/modeshape/jcr/database". If the corresponding file is not found on the classpath, then the "
* binary_store_default_database.properties
" file provided by ModeShape is used.
*
*
* ModeShape provides out-of-the-box database-specific files for several of the DBMSes that are popular within the open source
* community. The properties files for the other database types are not provided (though the ModeShape community will gladly
* incorporate them if you wish to make them available to us); in such cases, simply copy one of the provided properties files
* (e.g., "binary_store_default_database.properties
" is often a good start) and customize it for your particular
* DBMS, naming it according to the pattern described above and including it on the classpath.
*
*
* Note that this mechanism can also be used to override the statements that ModeShape does provide out-of-the-box. In such cases,
* be sure to place the file on the classpath before the ModeShape JARs so that your file will be discovered first.
*
*
* The JDBC driver used needs to be at least JDBC 1.4 (JDK 6) compliant, because
* {@link PreparedStatement#setBinaryStream(int parameterIndex, java.io.InputStream x)} is being used.
*
*/
@ThreadSafe
public class DatabaseBinaryStore extends AbstractBinaryStore {
private static final boolean ALIVE = true;
private static final boolean UNUSED = false;
/**
* JDBC params
*/
private final String driverClass;
private final String connectionURL;
private final String username;
private final String password;
private final String datasourceJNDILocation;
private DataSource dataSource;
/**
* A temporary fs-based store which stores binaries before they are persisted in the DB
*/
private final FileSystemBinaryStore cache;
/**
* JDBC utility for working with the database.
*/
private Database database;
/**
* Create new store.
*
* @param driverClass JDBC driver class name
* @param connectionURL database location
* @param username database user name
* @param password database password
*/
public DatabaseBinaryStore( String driverClass,
String connectionURL,
String username,
String password ) {
this.driverClass = driverClass;
this.connectionURL = connectionURL;
this.username = username;
this.password = password;
this.datasourceJNDILocation = null;
this.cache = TransientBinaryStore.get();
}
/**
* Create new store that uses the JDBC DataSource in the given JNDI location.
*
* @param datasourceJNDILocation the JNDI name of the JDBC Data Source that should be used, or null
*/
public DatabaseBinaryStore( String datasourceJNDILocation ) {
this.driverClass = null;
this.connectionURL = null;
this.username = null;
this.password = null;
this.datasourceJNDILocation = datasourceJNDILocation;
this.cache = TransientBinaryStore.get();
}
@Override
public void start() {
super.start();
if (!StringUtil.isBlank(datasourceJNDILocation)) {
lookupDataSource();
} else {
lookupDriver();
}
database();
}
protected Database database() {
if (this.database == null) {
Connection connection = newConnection();
try {
this.database = new Database(connection);
} catch (Throwable t) {
throw new RuntimeException(t);
} finally {
Database.tryToClose(connection);
}
}
return database;
}
@Override
public BinaryValue storeValue( InputStream stream, final boolean markAsUnused ) throws BinaryStoreException {
// store into temporary file system store and get SHA-1
final BinaryValue temp = cache.storeValue(stream, markAsUnused);
try {
return dbCall(new DBCallable() {
@Override
public BinaryValue execute( Connection connection ) throws Exception {
// prepare new binary key based on SHA-1
BinaryKey key = new BinaryKey(temp.getKey().toString());
// check for duplicate content
Database database = database();
if (database.contentExists(key, ALIVE, connection)) {
return new StoredBinaryValue(DatabaseBinaryStore.this, key, temp.getSize());
}
// check unused content
if (database.contentExists(key, UNUSED, connection)) {
if (!markAsUnused) {
database.restoreContent(connection, Arrays.asList(key));
}
} else {
// store the content
database.insertContent(key, temp.getStream(), temp.getSize(), connection);
if (markAsUnused) {
database.markUnused(Arrays.asList(key), connection);
}
}
return new StoredBinaryValue(DatabaseBinaryStore.this, key, temp.getSize());
}
});
} finally {
// remove content from temp store
cache.markAsUnused(temp.getKey());
}
}
@Override
public InputStream getInputStream( BinaryKey key ) throws BinaryStoreException {
Connection connection = newConnection();
try {
InputStream inputStream = database.readContent(key, connection);
if (inputStream == null) {
throw new BinaryStoreException(JcrI18n.unableToFindBinaryValue.text(key, database.getTableName()));
}
// the connection & statement will be left open until the stream is closed !
return inputStream;
} catch (SQLException e) {
throw new BinaryStoreException(e);
}
}
@Override
public void markAsUsed(final Iterable keys ) throws BinaryStoreException {
dbCall(new DBCallable