/*
* Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates.
* Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
* of Amazon Technologies, Inc. or its affiliates. All rights reserved.
*
* 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 com.amazon.carbonado.repo.jdbc;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.sql.DataSource;
import com.amazon.carbonado.ConfigurationException;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.spi.AbstractRepositoryBuilder;
/**
* Builds a repository instance backed by a JDBC accessible database.
* JDBCRepository is not independent of the underlying database schema, and so
* it requires matching tables and columns in the database. It will not alter
* or create tables. Use the {@link com.amazon.carbonado.Alias Alias}
* annotation to control precisely which tables and columns must be matched up.
*
*
Note: The current JDBC repository implementation makes certain
* assumptions about the database it is accessing. It must support transactions
* and multiple statements per connection. If it doesn't support savepoints,
* then nested transactions are faked -- rollback of inner transaction will
* appear to do nothing.
*
*
* The following extra capabilities are supported:
*
* - {@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability}
*
- {@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability}
*
- {@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
*
- {@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
*
- {@link JDBCConnectionCapability JDBCConnectionCapability}
*
*
* @author Brian S O'Neill
* @author bcastill
* @author Adam D Bradley
*/
public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder {
private String mName;
private boolean mIsMaster = true;
private DataSource mDataSource;
private boolean mDataSourceClose;
private boolean mDataSourceLogging;
private String mCatalog;
private String mSchema;
private String mDriverClassName;
private String mURL;
private String mUsername;
private String mPassword;
private Integer mFetchSize;
private Map mAutoVersioningMap;
private Map mSuppressReloadMap;
private String mSequenceSelectStatement;
private boolean mForceStoredSequence;
private boolean mPrimaryKeyCheckDisabled;
private SchemaResolver mResolver;
public JDBCRepositoryBuilder() {
}
public Repository build(AtomicReference rootRef) throws RepositoryException {
assertReady();
final Repository originalRoot = rootRef.get();
JDBCRepository repo = new JDBCRepository
(rootRef, getName(), isMaster(), getTriggerFactories(),
getDataSource(), getDataSourceCloseOnShutdown(),
mCatalog, mSchema,
mFetchSize,
getAutoVersioningMap(),
getSuppressReloadMap(),
mSequenceSelectStatement, mForceStoredSequence, mPrimaryKeyCheckDisabled,
mResolver);
// Don't wipe out root when using BelatedRepositoryCreator.
rootRef.compareAndSet(originalRoot, repo);
return repo;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public boolean isMaster() {
return mIsMaster;
}
public void setMaster(boolean b) {
mIsMaster = b;
}
/**
* Set the source of JDBC connections, overriding any configuration
* supported by these methods:
*
*
* - {@link #setDriverClassName}
*
- {@link #setDriverURL}
*
- {@link #setUserName}
*
- {@link #setPassword}
*
*/
public void setDataSource(DataSource dataSource) {
mDataSource = dataSource;
mDriverClassName = null;
mURL = null;
mUsername = null;
mPassword = null;
}
/**
* Returns the source of JDBC connections, which defaults to a non-pooling
* source if driver class, driver URL, username, and password are all
* supplied.
*
* @throws ConfigurationException if driver class wasn't found
*/
public DataSource getDataSource() throws ConfigurationException {
if (mDataSource == null) {
if (mDriverClassName != null && mURL != null) {
try {
mDataSource = new SimpleDataSource
(mDriverClassName, mURL, mUsername, mPassword);
} catch (SQLException e) {
Throwable cause = e.getCause();
if (cause == null) {
cause = e;
}
throw new ConfigurationException(cause);
}
}
}
DataSource ds = mDataSource;
if (getDataSourceLogging() && !(ds instanceof LoggingDataSource)) {
ds = LoggingDataSource.create(ds);
}
return ds;
}
/**
* Pass true to cause the DataSource to be closed when the repository is
* closed or shutdown. By default, this option is false.
*
* @since 1.2
*/
public void setDataSourceCloseOnShutdown(boolean b) {
mDataSourceClose = b;
}
/**
* Returns true if DataSource is closed when the repository is closed or
* shutdown. By default, this option is false.
*
* @since 1.2
*/
public boolean getDataSourceCloseOnShutdown() {
return mDataSourceClose;
}
/**
* Pass true to enable debug logging. By default, it is false.
*
* @see LoggingDataSource
*/
public void setDataSourceLogging(boolean b) {
mDataSourceLogging = b;
}
/**
* Returns true if debug logging is enabled.
*
* @see LoggingDataSource
*/
public boolean getDataSourceLogging() {
return mDataSourceLogging;
}
/**
* Optionally set the catalog to search for metadata.
*/
public void setCatalog(String catalog) {
mCatalog = catalog;
}
/**
* Returns the optional catalog to search for metadata.
*/
public String getCatalog() {
return mCatalog;
}
/**
* Optionally set the schema to search for metadata.
*/
public void setSchema(String schema) {
mSchema = schema;
}
/**
* Returns the optional schema to search for metadata.
*/
public String getSchema() {
return mSchema;
}
/**
* Set the JDBC driver class name, which is required if a DataSource was not provided.
*/
public void setDriverClassName(String driverClassName) {
mDriverClassName = driverClassName;
}
/**
* Returns the driver class name, which may be null if a DataSource was provided.
*/
public String getDriverClassName() {
return mDriverClassName;
}
/**
* Set the JDBC connection URL, which is required if a DataSource was not
* provided.
*/
public void setDriverURL(String url) {
mURL = url;
}
/**
* Returns the connection URL, which may be null if a DataSource was
* provided.
*/
public String getDriverURL() {
return mURL;
}
/**
* Optionally set the username to use with DataSource.
*/
public void setUserName(String username) {
mUsername = username;
}
/**
* Returns the optional username to use with DataSource.
*/
public String getUserName() {
return mUsername;
}
/**
* Optionally set the password to use with DataSource.
*/
public void setPassword(String password) {
mPassword = password;
}
/**
* Returns the optional password to use with DataSource.
*/
public String getPassword() {
return mPassword;
}
/**
* Set the default fetch size when running queries. Pass null to let driver
* use its own default.
*
* @since 1.2
*/
public void setDefaultFetchSize(Integer size) {
mFetchSize = size;
}
/**
* Returns the default fetch size when running queries, or null if driver
* default is used instead.
*
* @since 1.2
*/
public Integer getDefaultFetchSize() {
return mFetchSize;
}
/**
* By default, JDBCRepository assumes that {@link
* com.amazon.carbonado.Version version numbers} are initialized and
* incremented by triggers installed on the database. Enabling automatic
* versioning here causes the JDBCRepository to manage these operations
* itself.
*
* @param enabled true to enable, false to disable
* @param className name of Storable type to enable automatic version
* management on; pass null to enable all
* @since 1.2
*/
public void setAutoVersioningEnabled(boolean enabled, String className) {
if (mAutoVersioningMap == null) {
mAutoVersioningMap = new HashMap();
}
mAutoVersioningMap.put(className, enabled);
}
private Map getAutoVersioningMap() {
if (mAutoVersioningMap == null) {
return null;
}
return new HashMap(mAutoVersioningMap);
}
/**
* By default, JDBCRepository reloads Storables after every insert or
* update. This ensures that any applied defaults or triggered changes are
* available to the Storable. If the database has no such defaults or
* triggers, suppressing reload can improve performance.
*
* Note: If Storable has a version property and auto versioning is not
* enabled, or if the Storable has any automatic properties, the Storable
* might still be reloaded.
*
* @param suppress true to suppress, false to unsuppress
* @param className name of Storable type to suppress reload for; pass null
* to suppress all
* @since 1.1.3
*/
public void setSuppressReload(boolean suppress, String className) {
if (mSuppressReloadMap == null) {
mSuppressReloadMap = new HashMap();
}
mSuppressReloadMap.put(className, suppress);
}
private Map getSuppressReloadMap() {
if (mSuppressReloadMap == null) {
return null;
}
return new HashMap(mSuppressReloadMap);
}
/**
* Returns the native sequence select statement, which is null if the
* default is chosen.
*
* @since 1.2
*/
public String getSequenceSelectStatement() {
return mSequenceSelectStatement;
}
/**
* Override the default native sequence select statement with a printf.
* For example, "SELECT %s.NEXTVAL FROM DUAL".
*
* @since 1.2
*/
public void setSequenceSelectStatement(String sequenceSelectStatement) {
mSequenceSelectStatement = sequenceSelectStatement;
}
/**
* Returns true if native sequences should not be used.
*
* @since 1.2
*/
public boolean isForceStoredSequence() {
return mForceStoredSequence;
}
/**
* By default, native sequences are used if supported. Otherwise, a table
* named "CARBONADO_SEQUENCE" or "CARBONADO_SEQUENCES" is used instead to
* hold sequence values. When forced, the table is always used instead of
* native sequences.
*
* @since 1.2
*/
public void setForceStoredSequence(boolean forceStoredSequence) {
mForceStoredSequence = forceStoredSequence;
}
/**
* By default, JDBCRepository makes sure that every declared primary key
* in the database table for a Storable lines up with a declared
* PrimaryKey or AlternateKey. This is not always the desired behavior;
* for example, you may have a table which uses a bigint for its actual
* primary key but uses another column with a unique index as the
* "primary" key from the application's point of view. Setting this
* value to true allows this check to fail gracefully instead of
* throwing a {@link com.amazon.carbonado.MismatchException}.
*
* @since 1.2
*/
public void setPrimaryKeyCheckDisabled(boolean primaryKeyCheckDisabled) {
mPrimaryKeyCheckDisabled = primaryKeyCheckDisabled;
}
@Override
public void errorCheck(Collection messages) throws ConfigurationException {
super.errorCheck(messages);
if (mDataSource == null) {
if (mDriverClassName == null) {
messages.add("driverClassName missing");
}
if (mURL == null) {
messages.add("driverURL missing");
}
if (messages.size() == 0) {
// Verify driver exists, only if no other errors.
getDataSource();
}
}
}
// Experimental feature.
void setSchemaResolver(SchemaResolver resolver) {
mResolver = resolver;
}
}