/* * 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.core.data.db; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.CountingInputStream; import org.apache.jackrabbit.core.data.AbstractDataStore; import org.apache.jackrabbit.core.data.DataIdentifier; import org.apache.jackrabbit.core.data.DataRecord; import org.apache.jackrabbit.core.data.DataStoreException; import org.apache.jackrabbit.core.data.MultiDataStoreAware; import org.apache.jackrabbit.core.util.db.CheckSchemaOperation; import org.apache.jackrabbit.core.util.db.ConnectionFactory; import org.apache.jackrabbit.core.util.db.ConnectionHelper; import org.apache.jackrabbit.core.util.db.DatabaseAware; import org.apache.jackrabbit.core.util.db.DbUtility; import org.apache.jackrabbit.core.util.db.StreamWrapper; import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.WeakHashMap; import javax.jcr.RepositoryException; import javax.sql.DataSource; /** * A data store implementation that stores the records in a database using JDBC. * * Configuration: * <pre> * <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore"> * <param name="{@link #setUrl(String) url}" value="jdbc:postgresql:test"/> * <param name="{@link #setUser(String) user}" value="sa"/> * <param name="{@link #setPassword(String) password}" value="sa"/> * <param name="{@link #setDatabaseType(String) databaseType}" value="postgresql"/> * <param name="{@link #setDriver(String) driver}" value="org.postgresql.Driver"/> * <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/> * <param name="{@link #setMaxConnections(int) maxConnections}" value="2"/> * <param name="{@link #setCopyWhenReading(boolean) copyWhenReading}" value="true"/> * <param name="{@link #setTablePrefix(String) tablePrefix}" value=""/> * <param name="{@link #setSchemaObjectPrefix(String) schemaObjectPrefix}" value=""/> * <param name="{@link #setSchemaCheckEnabled(boolean) schemaCheckEnabled}" value="true"/> * </DataStore> * </pre> * <p> * Only URL, user name and password usually need to be set. * The remaining settings are generated using the database URL sub-protocol from the * database type resource file. * <p> * JNDI can be used to get the connection. In this case, use the javax.naming.InitialContext as the driver, * and the JNDI name as the URL. If the user and password are configured in the JNDI resource, * they should not be configured here. Example JNDI settings: * <pre> * <param name="driver" value="javax.naming.InitialContext" /> * <param name="url" value="java:comp/env/jdbc/Test" /> * </pre> * <p> * For Microsoft SQL Server 2005, there is a problem reading large BLOBs. You will need to use * the JDBC driver version 1.2 or newer, and append ;responseBuffering=adaptive to the database URL. * Don't append ;selectMethod=cursor, otherwise it can still run out of memory. * Example database URL: jdbc:sqlserver://localhost:4220;DatabaseName=test;responseBuffering=adaptive * <p> * By default, the data is copied to a temp file when reading, to avoid problems when reading multiple * blobs at the same time. * <p> * The tablePrefix can be used to specify a schema and / or catalog name: * <param name="tablePrefix" value="ds."> */ public class DbDataStore extends AbstractDataStore implements DatabaseAware, MultiDataStoreAware { /** * The default value for the minimum object size. */ public static final int DEFAULT_MIN_RECORD_LENGTH = 100; /** * Write to a temporary file to get the length (slow, but always works). * This is the default setting. */ public static final String STORE_TEMP_FILE = "tempFile"; /** * Call PreparedStatement.setBinaryStream(..., -1) */ public static final String STORE_SIZE_MINUS_ONE = "-1"; /** * Call PreparedStatement.setBinaryStream(..., Integer.MAX_VALUE) */ public static final String STORE_SIZE_MAX = "max"; /** * The prefix used for temporary objects. */ protected static final String TEMP_PREFIX = "TEMP_"; /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(DbDataStore.class); /** * The minimum modified date. If a file is accessed (read or write) with a modified date * older than this value, the modified date is updated to the current time. */ protected long minModifiedDate; /** * The database URL used. */ protected String url; /** * The database driver. */ protected String driver; /** * The user name. */ protected String user; /** * The password */ protected String password; /** * The database type used. */ protected String databaseType; /** * The minimum size of an object that should be stored in this data store. */ protected int minRecordLength = DEFAULT_MIN_RECORD_LENGTH; /** * The prefix for the datastore table, empty by default. */ protected String tablePrefix = ""; /** * The prefix of the table names. By default it is empty. */ protected String schemaObjectPrefix = ""; /** * Whether the schema check must be done during initialization. */ private boolean schemaCheckEnabled = true; /** * The logical name of the DataSource to use. */ protected String dataSourceName; /** * This is the property 'table' * in the [databaseType].properties file, initialized with the default value. */ protected String tableSQL = "DATASTORE"; /** * This is the property 'createTable' * in the [databaseType].properties file, initialized with the default value. */ protected String createTableSQL = "CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB)"; /** * This is the property 'insertTemp' * in the [databaseType].properties file, initialized with the default value. */ protected String insertTempSQL = "INSERT INTO ${tablePrefix}${table} VALUES(?, 0, ?, NULL)"; /** * This is the property 'updateData' * in the [databaseType].properties file, initialized with the default value. */ protected String updateDataSQL = "UPDATE ${tablePrefix}${table} SET DATA=? WHERE ID=?"; /** * This is the property 'updateLastModified' * in the [databaseType].properties file, initialized with the default value. */ protected String updateLastModifiedSQL = "UPDATE ${tablePrefix}${table} SET LAST_MODIFIED=? WHERE ID=? AND LAST_MODIFIED<?"; /** * This is the property 'update' * in the [databaseType].properties file, initialized with the default value. */ protected String updateSQL = "UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=? " + "WHERE ID=? AND LAST_MODIFIED=?"; /** * This is the property 'delete' * in the [databaseType].properties file, initialized with the default value. */ protected String deleteSQL = "DELETE FROM ${tablePrefix}${table} WHERE ID=?"; /** * This is the property 'deleteOlder' * in the [databaseType].properties file, initialized with the default value. */ protected String deleteOlderSQL = "DELETE FROM ${tablePrefix}${table} WHERE LAST_MODIFIED<?"; /** * This is the property 'selectMeta' * in the [databaseType].properties file, initialized with the default value. */ protected String selectMetaSQL = "SELECT LENGTH, LAST_MODIFIED FROM ${tablePrefix}${table} WHERE ID=?"; /** * This is the property 'selectAll' * in the [databaseType].properties file, initialized with the default value. */ protected String selectAllSQL = "SELECT ID FROM ${tablePrefix}${table}"; /** * This is the property 'selectData' * in the [databaseType].properties file, initialized with the default value. */ protected String selectDataSQL = "SELECT ID, DATA FROM ${tablePrefix}${table} WHERE ID=?"; /** * The stream storing mechanism used. */ protected String storeStream = STORE_TEMP_FILE; /** * Copy the stream to a temp file before returning it. * Enabled by default to support concurrent reads. */ protected boolean copyWhenReading = true; /** * All data identifiers that are currently in use are in this set until they are garbage collected. */ protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse = Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>()); /** * The temporary identifiers that are currently in use. */ protected List<String> temporaryInUse = Collections.synchronizedList(new ArrayList<String>()); /** * The {@link ConnectionHelper} set in the {@link #init(String)} method. * */ protected ConnectionHelper conHelper; /** * The repositories {@link ConnectionFactory}. */ private ConnectionFactory connectionFactory; public void setConnectionFactory(ConnectionFactory connnectionFactory) { this.connectionFactory = connnectionFactory; } public DataRecord addRecord(InputStream stream) throws DataStoreException { InputStream fileInput = null; String tempId = null; ResultSet rs = null; try { long tempModified; while (true) { try { tempModified = System.currentTimeMillis(); String id = UUID.randomUUID().toString(); tempId = TEMP_PREFIX + id; temporaryInUse.add(tempId); // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=? rs = conHelper.query(selectMetaSQL, tempId); boolean hasNext = rs.next(); DbUtility.close(rs); rs = null; if (hasNext) { // re-try in the very, very unlikely event that the row already exists continue; } // INSERT INTO DATASTORE VALUES(?, 0, ?, NULL) conHelper.exec(insertTempSQL, tempId, tempModified); break; } catch (Exception e) { throw convert("Can not insert new record", e); } finally { DbUtility.close(rs); // prevent that rs.close() is called again rs = null; } } MessageDigest digest = getDigest(); DigestInputStream dIn = new DigestInputStream(stream, digest); CountingInputStream in = new CountingInputStream(dIn); StreamWrapper wrapper; if (STORE_SIZE_MINUS_ONE.equals(storeStream)) { wrapper = new StreamWrapper(in, -1); } else if (STORE_SIZE_MAX.equals(storeStream)) { wrapper = new StreamWrapper(in, Integer.MAX_VALUE); } else if (STORE_TEMP_FILE.equals(storeStream)) { File temp = moveToTempFile(in); long length = temp.length(); wrapper = new StreamWrapper(new ResettableTempFileInputStream(temp), length); } else { throw new DataStoreException("Unsupported stream store algorithm: " + storeStream); } // UPDATE DATASTORE SET DATA=? WHERE ID=? conHelper.exec(updateDataSQL, wrapper, tempId); long length = in.getByteCount(); DataIdentifier identifier = new DataIdentifier(encodeHexString(digest.digest())); usesIdentifier(identifier); String id = identifier.toString(); long newModified; while (true) { newModified = System.currentTimeMillis(); if (checkExisting(tempId, length, identifier)) { touch(identifier, newModified); conHelper.exec(deleteSQL, tempId); break; } try { // UPDATE DATASTORE SET ID=?, LENGTH=?, LAST_MODIFIED=? // WHERE ID=? AND LAST_MODIFIED=? int count = conHelper.update(updateSQL, id, length, newModified, tempId, tempModified); // If update count is 0, the last modified time of the // temporary row was changed - which means we need to // re-try using a new last modified date (a later one) // because we need to ensure the new last modified date // is _newer_ than the old (otherwise the garbage // collection could delete rows) if (count != 0) { // update was successful break; } } catch (SQLException e) { // duplicate key (the row already exists) - repeat // we use exception handling for flow control here, which is bad, // but the alternative is to use UPDATE ... WHERE ... (SELECT ...) // which could cause a deadlock in some databases - also, // duplicate key will only occur if somebody else concurrently // added the same record (which is very unlikely) } // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=? rs = conHelper.query(selectMetaSQL, tempId); if (!rs.next()) { // the row was deleted, which is unexpected / not allowed String msg = DIGEST + " temporary entry deleted: " + " id=" + tempId + " length=" + length; log.error(msg); throw new DataStoreException(msg); } tempModified = rs.getLong(2); DbUtility.close(rs); rs = null; } usesIdentifier(identifier); DbDataRecord record = new DbDataRecord(this, identifier, length, newModified); return record; } catch (Exception e) { throw convert("Can not insert new record", e); } finally { if (tempId != null) { temporaryInUse.remove(tempId); } DbUtility.close(rs); if (fileInput != null) { try { fileInput.close(); } catch (IOException e) { throw convert("Can not close temporary file", e); } } } } /** * Check if a row with this ID already exists. * * @return true if the row exists and the length matches * @throw DataStoreException if a row exists, but the length is different */ private boolean checkExisting(String tempId, long length, DataIdentifier identifier) throws DataStoreException, SQLException { String id = identifier.toString(); // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=? ResultSet rs = null; try { rs = conHelper.query(selectMetaSQL, id); if (rs.next()) { long oldLength = rs.getLong(1); long lastModified = rs.getLong(2); if (oldLength != length) { String msg = DIGEST + " collision: temp=" + tempId + " id=" + id + " length=" + length + " oldLength=" + oldLength; log.error(msg); throw new DataStoreException(msg); } DbUtility.close(rs); rs = null; touch(identifier, lastModified); // row already exists conHelper.exec(deleteSQL, tempId); return true; } } finally { DbUtility.close(rs); } return false; } /** * Creates a temp file and copies the data there. * The input stream is closed afterwards. * * @param in the input stream * @return the file * @throws IOException */ private File moveToTempFile(InputStream in) throws IOException { File temp = File.createTempFile("dbRecord", null); writeToFileAndClose(in, temp); return temp; } private void writeToFileAndClose(InputStream in, File file) throws IOException { OutputStream out = new FileOutputStream(file); try { IOUtils.copy(in, out); } finally { IOUtils.closeQuietly(out); IOUtils.closeQuietly(in); } } public synchronized void deleteRecord(DataIdentifier identifier) throws DataStoreException { try { conHelper.exec(deleteSQL, identifier.toString()); } catch (Exception e) { throw convert("Can not delete record", e); } } public synchronized int deleteAllOlderThan(long min) throws DataStoreException { try { ArrayList<String> touch = new ArrayList<String>(); ArrayList<DataIdentifier> ids = new ArrayList<DataIdentifier>(inUse.keySet()); for (DataIdentifier identifier: ids) { if (identifier != null) { touch.add(identifier.toString()); } } touch.addAll(temporaryInUse); for (String key : touch) { updateLastModifiedDate(key, 0); } // DELETE FROM DATASTORE WHERE LAST_MODIFIED<? return conHelper.update(deleteOlderSQL, min); } catch (Exception e) { throw convert("Can not delete records", e); } } public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException { ArrayList<DataIdentifier> list = new ArrayList<DataIdentifier>(); ResultSet rs = null; try { // SELECT ID FROM DATASTORE rs = conHelper.query(selectAllSQL); while (rs.next()) { String id = rs.getString(1); if (!id.startsWith(TEMP_PREFIX)) { DataIdentifier identifier = new DataIdentifier(id); list.add(identifier); } } log.debug("Found " + list.size() + " identifiers."); return list.iterator(); } catch (Exception e) { throw convert("Can not read records", e); } finally { DbUtility.close(rs); } } public int getMinRecordLength() { return minRecordLength; } /** * Set the minimum object length. * The maximum value is around 32000. * * @param minRecordLength the length */ public void setMinRecordLength(int minRecordLength) { this.minRecordLength = minRecordLength; } public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException { usesIdentifier(identifier); ResultSet rs = null; try { String id = identifier.toString(); // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID = ? rs = conHelper.query(selectMetaSQL, id); if (!rs.next()) { return null; } long length = rs.getLong(1); long lastModified = rs.getLong(2); DbUtility.close(rs); rs = null; lastModified = touch(identifier, lastModified); return new DbDataRecord(this, identifier, length, lastModified); } catch (Exception e) { throw convert("Can not read identifier " + identifier, e); } finally { DbUtility.close(rs); } } /** * Open the input stream. This method sets those fields of the caller * that need to be closed once the input stream is read. * * @param inputStream the database input stream object * @param identifier data identifier * @throws DataStoreException if the data store could not be accessed, * or if the given identifier is invalid */ InputStream openStream(DbInputStream inputStream, DataIdentifier identifier) throws DataStoreException { ResultSet rs = null; try { // SELECT ID, DATA FROM DATASTORE WHERE ID = ? rs = conHelper.query(selectDataSQL, identifier.toString()); if (!rs.next()) { throw new DataStoreException("Record not found: " + identifier); } InputStream stream = rs.getBinaryStream(2); if (stream == null) { stream = new ByteArrayInputStream(new byte[0]); DbUtility.close(rs); } else if (copyWhenReading) { // If we copy while reading, create a temp file and close the stream File temp = moveToTempFile(stream); stream = new BufferedInputStream(new TempFileInputStream(temp)); DbUtility.close(rs); } else { stream = new BufferedInputStream(stream); inputStream.setResultSet(rs); } return stream; } catch (Exception e) { DbUtility.close(rs); throw convert("Retrieving database resource ", e); } } public synchronized void init(String homeDir) throws DataStoreException { try { initDatabaseType(); conHelper = createConnectionHelper(getDataSource()); if (isSchemaCheckEnabled()) { createCheckSchemaOperation().run(); } } catch (Exception e) { throw convert("Can not init data store, driver=" + driver + " url=" + url + " user=" + user + " schemaObjectPrefix=" + schemaObjectPrefix + " tableSQL=" + tableSQL + " createTableSQL=" + createTableSQL, e); } } private DataSource getDataSource() throws Exception { if (getDataSourceName() == null || "".equals(getDataSourceName())) { return connectionFactory.getDataSource(getDriver(), getUrl(), getUser(), getPassword()); } else { return connectionFactory.getDataSource(dataSourceName); } } /** * This method is called from the {@link #init(String)} method of this class and returns a * {@link ConnectionHelper} instance which is assigned to the {@code conHelper} field. Subclasses may * override it to return a specialized connection helper. * * @param dataSrc the {@link DataSource} of this persistence manager * @return a {@link ConnectionHelper} * @throws Exception on error */ protected ConnectionHelper createConnectionHelper(DataSource dataSrc) throws Exception { return new ConnectionHelper(dataSrc, false); } /** * This method is called from {@link #init(String)} after the * {@link #createConnectionHelper(DataSource)} method, and returns a default {@link CheckSchemaOperation}. * * @return a new {@link CheckSchemaOperation} instance */ protected final CheckSchemaOperation createCheckSchemaOperation() { String tableName = tablePrefix + schemaObjectPrefix + tableSQL; return new CheckSchemaOperation(conHelper, new ByteArrayInputStream(createTableSQL.getBytes()), tableName); } protected void initDatabaseType() throws DataStoreException { boolean failIfNotFound = false; if (databaseType == null) { if (dataSourceName != null) { try { databaseType = connectionFactory.getDataBaseType(dataSourceName); } catch (RepositoryException e) { throw new DataStoreException(e); } } else { if (!url.startsWith("jdbc:")) { return; } int start = "jdbc:".length(); int end = url.indexOf(':', start); databaseType = url.substring(start, end); } } else { failIfNotFound = true; } InputStream in = DbDataStore.class.getResourceAsStream(databaseType + ".properties"); if (in == null) { if (failIfNotFound) { String msg = "Configuration error: The resource '" + databaseType + ".properties' could not be found;" + " Please verify the databaseType property"; log.debug(msg); throw new DataStoreException(msg); } else { return; } } Properties prop = new Properties(); try { try { prop.load(in); } finally { in.close(); } } catch (IOException e) { String msg = "Configuration error: Could not read properties '" + databaseType + ".properties'"; log.debug(msg); throw new DataStoreException(msg, e); } if (driver == null) { driver = getProperty(prop, "driver", driver); } tableSQL = getProperty(prop, "table", tableSQL); createTableSQL = getProperty(prop, "createTable", createTableSQL); insertTempSQL = getProperty(prop, "insertTemp", insertTempSQL); updateDataSQL = getProperty(prop, "updateData", updateDataSQL); updateLastModifiedSQL = getProperty(prop, "updateLastModified", updateLastModifiedSQL); updateSQL = getProperty(prop, "update", updateSQL); deleteSQL = getProperty(prop, "delete", deleteSQL); deleteOlderSQL = getProperty(prop, "deleteOlder", deleteOlderSQL); selectMetaSQL = getProperty(prop, "selectMeta", selectMetaSQL); selectAllSQL = getProperty(prop, "selectAll", selectAllSQL); selectDataSQL = getProperty(prop, "selectData", selectDataSQL); storeStream = getProperty(prop, "storeStream", storeStream); if (!STORE_SIZE_MINUS_ONE.equals(storeStream) && !STORE_TEMP_FILE.equals(storeStream) && !STORE_SIZE_MAX.equals(storeStream)) { String msg = "Unsupported Stream store mechanism: " + storeStream + " supported are: " + STORE_SIZE_MINUS_ONE + ", " + STORE_TEMP_FILE + ", " + STORE_SIZE_MAX; log.debug(msg); throw new DataStoreException(msg); } } /** * Get the expanded property value. The following placeholders are supported: * ${table}: the table name (the default is DATASTORE) and * ${tablePrefix}: tablePrefix plus schemaObjectPrefix as set in the configuration * * @param prop the properties object * @param key the key * @param defaultValue the default value * @return the property value (placeholders are replaced) */ protected String getProperty(Properties prop, String key, String defaultValue) { String sql = prop.getProperty(key, defaultValue); sql = Text.replace(sql, "${table}", tableSQL).trim(); sql = Text.replace(sql, "${tablePrefix}", tablePrefix + schemaObjectPrefix).trim(); return sql; } /** * Convert an exception to a data store exception. * * @param cause the message * @param e the root cause * @return the data store exception */ protected DataStoreException convert(String cause, Exception e) { log.warn(cause, e); if (e instanceof DataStoreException) { return (DataStoreException) e; } else { return new DataStoreException(cause, e); } } public void updateModifiedDateOnAccess(long before) { log.debug("Update modifiedDate on access before " + before); minModifiedDate = before; } /** * Update the modified date of an entry if required. * * @param identifier the entry identifier * @param lastModified the current last modified date * @return the new modified date */ long touch(DataIdentifier identifier, long lastModified) throws DataStoreException { usesIdentifier(identifier); return updateLastModifiedDate(identifier.toString(), lastModified); } private long updateLastModifiedDate(String key, long lastModified) throws DataStoreException { if (lastModified < minModifiedDate) { long now = System.currentTimeMillis(); try { // UPDATE DATASTORE SET LAST_MODIFIED = ? WHERE ID = ? AND LAST_MODIFIED < ? conHelper.update(updateLastModifiedSQL, now, key, now); return now; } catch (Exception e) { throw convert("Can not update lastModified", e); } } return lastModified; } /** * Get the database type (if set). * @return the database type */ public String getDatabaseType() { return databaseType; } /** * Set the database type. By default the sub-protocol of the JDBC database URL is used if it is not set. * It must match the resource file [databaseType].properties. Example: mysql. * * @param databaseType */ public void setDatabaseType(String databaseType) { this.databaseType = databaseType; } /** * Get the database driver * * @return the driver */ public String getDriver() { return driver; } /** * Set the database driver class name. * If not set, the default driver class name for the database type is used, * as set in the [databaseType].properties resource; key 'driver'. * * @param driver */ public void setDriver(String driver) { this.driver = driver; } /** * Get the password. * * @return the password */ public String getPassword() { return password; } /** * Set the password. * * @param password */ public void setPassword(String password) { this.password = password; } /** * Get the database URL. * * @return the URL */ public String getUrl() { return url; } /** * Set the database URL. * Example: jdbc:postgresql:test * * @param url */ public void setUrl(String url) { this.url = url; } /** * Get the user name. * * @return the user name */ public String getUser() { return user; } /** * Set the user name. * * @param user */ public void setUser(String user) { this.user = user; } /** * @return whether the schema check is enabled */ public final boolean isSchemaCheckEnabled() { return schemaCheckEnabled; } /** * @param enabled set whether the schema check is enabled */ public final void setSchemaCheckEnabled(boolean enabled) { schemaCheckEnabled = enabled; } public synchronized void close() throws DataStoreException { // nothing to do } protected void usesIdentifier(DataIdentifier identifier) { inUse.put(identifier, new WeakReference<DataIdentifier>(identifier)); } public void clearInUse() { inUse.clear(); } protected synchronized MessageDigest getDigest() throws DataStoreException { try { return MessageDigest.getInstance(DIGEST); } catch (NoSuchAlgorithmException e) { throw convert("No such algorithm: " + DIGEST, e); } } /** * Get the maximum number of concurrent connections. * * @deprecated * @return the maximum number of connections. */ public int getMaxConnections() { return -1; } /** * Set the maximum number of concurrent connections in the pool. * At least 3 connections are required if the garbage collection process is used. * *@deprecated * @param maxConnections the new value */ public void setMaxConnections(int maxConnections) { // no effect } /** * Is a stream copied to a temporary file before returning? * * @return the setting */ public boolean getCopyWhenReading() { return copyWhenReading; } /** * The the copy setting. If enabled, * a stream is always copied to a temporary file when reading a stream. * * @param copyWhenReading the new setting */ public void setCopyWhenReading(boolean copyWhenReading) { this.copyWhenReading = copyWhenReading; } /** * Get the table prefix. * * @return the table prefix. */ public String getTablePrefix() { return tablePrefix; } /** * Set the new table prefix. The default is empty. * The table name is constructed like this: * ${tablePrefix}${schemaObjectPrefix}${tableName} * * @param tablePrefix the new value */ public void setTablePrefix(String tablePrefix) { this.tablePrefix = tablePrefix; } /** * Get the schema prefix. * * @return the schema object prefix */ public String getSchemaObjectPrefix() { return schemaObjectPrefix; } /** * Set the schema object prefix. The default is empty. * The table name is constructed like this: * ${tablePrefix}${schemaObjectPrefix}${tableName} * * @param schemaObjectPrefix the new prefix */ public void setSchemaObjectPrefix(String schemaObjectPrefix) { this.schemaObjectPrefix = schemaObjectPrefix; } public String getDataSourceName() { return dataSourceName; } public void setDataSourceName(String dataSourceName) { this.dataSourceName = dataSourceName; } }