/* * 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.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.sql.PreparedStatement; import java.sql.SQLException; import org.cojen.util.ThrowUnchecked; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.UnsupportedTypeException; import com.amazon.carbonado.sequence.SequenceValueGenerator; import com.amazon.carbonado.sequence.SequenceValueProducer; import com.amazon.carbonado.sequence.StoredSequence; /** * Allows database product specific features to be abstracted. * * @author Brian S O'Neill * @author bcastill * @author Matt Tucker */ class JDBCSupportStrategy { private static final int BLOB_BUFFER_SIZE = 4000; private static final int CLOB_BUFFER_SIZE = 2000; /** * Create a strategy based on the name of the database product. * If one can't be found by product name the default will be used. */ @SuppressWarnings("unchecked") static JDBCSupportStrategy createStrategy(JDBCRepository repo) { String databaseProductName = repo.getDatabaseProductName().trim(); if (databaseProductName != null && databaseProductName.length() > 0) { String strategyName = Character.toUpperCase(databaseProductName.charAt(0)) + databaseProductName.substring(1).toLowerCase(); if (strategyName.indexOf(' ') > 0) { strategyName = strategyName.substring(0, strategyName.indexOf(' ')); } strategyName = strategyName.replaceAll("[^A-Za-z0-9]", ""); String className = "com.amazon.carbonado.repo.jdbc." + strategyName + "SupportStrategy"; try { Class clazz = (Class) Class.forName(className); return clazz.getDeclaredConstructor(JDBCRepository.class).newInstance(repo); } catch (ClassNotFoundException e) { // just use default strategy } catch (Exception e) { ThrowUnchecked.fireFirstDeclaredCause(e); } } return new JDBCSupportStrategy(repo); } protected final JDBCRepository mRepo; private String mSequenceSelectStatement; private boolean mForceStoredSequence = false; private String mTruncateTableStatement; protected JDBCSupportStrategy(JDBCRepository repo) { mRepo = repo; } JDBCExceptionTransformer createExceptionTransformer() { return new JDBCExceptionTransformer(); } /** * @since 1.2 */ SequenceValueProducer createSequenceValueProducer(String name) throws RepositoryException { if (name == null) { throw new IllegalArgumentException("Sequence name is null"); } String format = getSequenceSelectStatement(); if (format != null && format.length() > 0 && !isForceStoredSequence()) { String sequenceQuery = String.format(format, name); return new JDBCSequenceValueProducer(mRepo, sequenceQuery); } else { try { return new SequenceValueGenerator(mRepo, name); } catch (UnsupportedTypeException e) { if (e.getType() != StoredSequence.class) { throw e; } throw new PersistException ("Native sequences are not currently supported for \"" + mRepo.getDatabaseProductName() + "\". Instead, define a table named " + "CARBONADO_SEQUENCE as required by " + StoredSequence.class.getName() + '.'); } } } /** * @param loader used to reload Blob outside original transaction */ JDBCBlob convertBlob(java.sql.Blob blob, JDBCBlobLoader loader) { return blob == null ? null: new JDBCBlob(mRepo, blob, loader); } /** * @param loader used to reload Clob outside original transaction */ JDBCClob convertClob(java.sql.Clob clob, JDBCClobLoader loader) { return clob == null ? null : new JDBCClob(mRepo, clob, loader); } /** * @return original blob if too large and post-insert update is required, null otherwise */ com.amazon.carbonado.lob.Blob setBlobValue(PreparedStatement ps, int column, com.amazon.carbonado.lob.Blob blob) throws PersistException { try { if (blob instanceof JDBCBlob) { ps.setBlob(column, ((JDBCBlob) blob).getInternalBlobForPersist()); return null; } long length = blob.getLength(); if (((long) ((int) length)) != length) { throw new PersistException("BLOB length is too long: " + length); } ps.setBinaryStream(column, blob.openInputStream(), (int) length); return null; } catch (SQLException e) { throw mRepo.toPersistException(e); } catch (FetchException e) { throw e.toPersistException(); } } /** * @return original clob if too large and post-insert update is required, null otherwise */ com.amazon.carbonado.lob.Clob setClobValue(PreparedStatement ps, int column, com.amazon.carbonado.lob.Clob clob) throws PersistException { try { if (clob instanceof JDBCClob) { ps.setClob(column, ((JDBCClob) clob).getInternalClobForPersist()); return null; } long length = clob.getLength(); if (((long) ((int) length)) != length) { throw new PersistException("CLOB length is too long: " + length); } ps.setCharacterStream(column, clob.openReader(), (int) length); return null; } catch (SQLException e) { throw mRepo.toPersistException(e); } catch (FetchException e) { throw e.toPersistException(); } } void updateBlob(com.amazon.carbonado.lob.Blob oldBlob, com.amazon.carbonado.lob.Blob newBlob) throws PersistException { try { OutputStream out = oldBlob.openOutputStream(); InputStream in = newBlob.openInputStream(); byte[] buf = new byte[BLOB_BUFFER_SIZE]; int amt; while ((amt = in.read(buf)) > 0) { out.write(buf, 0, amt); } in.close(); out.close(); oldBlob.setLength(newBlob.getLength()); } catch (FetchException e) { throw e.toPersistException(); } catch (IOException e) { throw mRepo.toPersistException(e); } } void updateClob(com.amazon.carbonado.lob.Clob oldClob, com.amazon.carbonado.lob.Clob newClob) throws PersistException { try { Writer out = oldClob.openWriter(); Reader in = newClob.openReader(); char[] buf = new char[CLOB_BUFFER_SIZE]; int amt; while ((amt = in.read(buf)) > 0) { out.write(buf, 0, amt); } in.close(); out.close(); oldClob.setLength(newClob.getLength()); } catch (FetchException e) { throw e.toPersistException(); } catch (IOException e) { throw mRepo.toPersistException(e); } } boolean printPlan(Appendable app, int indentLevel, String statement) throws FetchException, IOException { return false; } /** * Returns the optional sequence select statement format. The format is * printf style with 1 string parameter which can be passed through {@link * String#format(String, Object[])} to create a sql statement. * * @since 1.2 */ String getSequenceSelectStatement() { return mSequenceSelectStatement; } /** * @since 1.2 */ void setSequenceSelectStatement(String sequenceSelectStatement) { mSequenceSelectStatement = sequenceSelectStatement; } /** * @since 1.2 */ boolean isForceStoredSequence() { return mForceStoredSequence; } /** * @since 1.2 */ void setForceStoredSequence(boolean forceStoredSequence) { mForceStoredSequence = forceStoredSequence; } /** * Return the optional truncate table statement format. The format is * printf style with 1 string parameter which can be passed through {@link * String#format(String, Object[])} to create a sql statement. * * @since 1.2 */ String getTruncateTableStatement() { return mTruncateTableStatement; } /** * @since 1.2 */ void setTruncateTableStatement(String truncateTableStatement) { mTruncateTableStatement = truncateTableStatement; } /** * @since 1.2 */ enum SliceOption { // Slice is always emulated NOT_SUPPORTED, // Slice is emulated if from is not zero LIMIT_ONLY, // Slice is emulated if to is not null OFFSET_ONLY, // Slice is fully supported with limit parameter first LIMIT_AND_OFFSET, // Slice is fully supported with offset parameter first OFFSET_AND_LIMIT, // Slice is fully supported with from parameter first FROM_AND_TO, } /** * @since 1.2 */ SliceOption getSliceOption() { return SliceOption.NOT_SUPPORTED; } /** * @param select base select statement * @param from when true, select must support slice from bound * @param to when true, select must support slice to bound * @return revised select statement * @throws UnsupportedOperationException * @since 1.2 */ String buildSelectWithSlice(String select, boolean from, boolean to) { throw new UnsupportedOperationException(); } }