/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* NOTE: This class is not included in the build,
* as the Oracle JDBC driver must be present for
* compilation. A pre-compiled version is checked in
* under
* <cmp-basedir>/release/build/OracleSpecialDBOperation.jar.
* This archive is unpacked for every build, and
* must be updated on every change to this class.
* Please see the ant script under
* <cmp-basedir>/support/sqlstore/build.xml
* for targets to update the pre-compiled version:
*
* - compile-oracle-special: compiles this class
* - clean-oracle-special: cleans OracleSpecialDBOperation classes
* - update-oracle-special: updates <cmp-basedir>/release/build/OracleSpecialDBOperation.jar
*
* Oracle's JDBC driver can be downloaded, e.g. the Oracle
* 10.1.0.4 JDBC driver can be retrieved from the URL
* http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/htdocs/jdbc101040.html
* Please specify the Oracle JDBC driver location by defining
* the property 'oracle-jdbc.jar' at the command line when
* calling the ant targets.
*
* The complete instructions to update
* OracleSpecialDBOperation.jar (from <cmp-basedir>) are:
*
* 1. Compile the cmp module, e.g.
* maven clean build
* 2. Clean the oracle special support classes
* ant -f support/sqlstore/build.xml clean-oracle-special
* 3. Update <cmp-basedir>/release/build/OracleSpecialDBOperation.jar
* ant -Doracle-jdbc.jar=<your location>/ojdbc14.jar -f support/sqlstore/build.xml update-oracle-special
*
*/
package com.sun.jdo.spi.persistence.support.sqlstore.database.oracle;
import java.util.List;
import java.util.Arrays;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.PreparedStatement;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Types;
import java.sql.Statement;
import com.sun.jdo.api.persistence.support.FieldMapping;
import com.sun.jdo.spi.persistence.support.sqlstore.LogHelperSQLStore;
import com.sun.jdo.spi.persistence.support.sqlstore.ejb.EJBHelper;
import com.sun.jdo.spi.persistence.support.sqlstore.database.BaseSpecialDBOperation;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
/**
* OracleSpecialDBOperation is derived class for Oracle specific operation.
* @author Shing Wai Chan
* @author Mitesh Meswani
*/
public class OracleSpecialDBOperation extends BaseSpecialDBOperation {
/**
* The logger
*/
private static Logger logger = LogHelperSQLStore.getLogger();
/**
* Interface used to handle driver specific implementation for various
* drivers for Oracle.
*/
private interface DBDriverHandlerFactory {
/**
* Creates an instance of DBDriverHandler to handle driver specific request
* on a <code>PreparedStatement</code>
* @param ps Instance of PreparedStatement
* @return Instance of DBDriverHandler that corresponds to DBDriver used
* to obtain <code>ps</code>
*
*/
DBDriverHandler createDBDriverHandler(PreparedStatement ps);
/**
* Returns true if underlying driver supports defineColumnType() false
* otherwise.
* @return true if underlying driver supports defineColumnType() false
* otherwise.
*/
boolean supportsDefineColumnType();
}
/**
* Base implementation for Oracle's driver's handler.
*/
private abstract class OracleDriverHandlerFactory
implements DBDriverHandlerFactory {
public boolean supportsDefineColumnType() {
return true;
}
}
private DBDriverHandlerFactory dBDriverHandlerFactory;
private static final boolean oracle816ClassesAvailable;
private static final boolean oracle817ClassesAvailable;
static {
ClassLoader loader = OracleSpecialDBOperation.class.getClassLoader();
// OraclePreparedStatement is in the oracle.jdbc.driver package for
// oracle 8.1.6 drivers.
oracle816ClassesAvailable =
loadClass("oracle.jdbc.driver.OraclePreparedStatement", loader) //NOI18N
!= null;
// OraclePreparedStatement is in the oracle.jdbc package for
// oracle 8.1.7 (and higher) drivers.
oracle817ClassesAvailable =
loadClass("oracle.jdbc.OraclePreparedStatement", loader) != null; //NOI18N
}
/**
* The SQL statement used to get implementation type of PreparedStatement.
*/
private static final String TEST_STATEMENT = "select 1 from dual";
public OracleSpecialDBOperation() {
}
/**
* Initializes driver specific behavior classes by determining the
* characteristics of the jdbc driver used with this DataSource.
*/
public void initialize(DatabaseMetaData metaData,
String identifier) throws SQLException {
Connection con = metaData.getConnection();
// Since the PreparedStatement obtained is directly through
// con, there is no need to unwrap it.
PreparedStatement testPs = con.prepareStatement(TEST_STATEMENT);
/*
if (oracle817ClassesAvailable &&
testPs instanceof oracle.jdbc.OraclePreparedStatement) {
// This DataSource uses a driver version 8.1.7 or higher.
// Oracle drivers for version 8.1.7 and higher define interface
// oracle.jdbc.OraclePreparedStatement.
// OraclePreparedStaement obtained from these drivers implement this
// interface. It is possible that in future Oracle might alter
// implementation class for OraclePreparedStatement. However, they
// should continue implementing this interface. Hence, this
// interface should be preferred to communicate with Oracle drivers.
dBDriverHandlerFactory = new OracleDriverHandlerFactory() {
public DBDriverHandler createDBDriverHandler(PreparedStatement ps) {
return new Oracle817Handler(ps);
}
};
} else if (oracle816ClassesAvailable &&
testPs instanceof oracle.jdbc.driver.OraclePreparedStatement) {
// This DataSource uses a driver version lower than 8.1.7.
// Currently all Oracle drivers return instance of
// oracle.jdbc.driver.OraclePreparedStatement for OraclePreparedStatement.
dBDriverHandlerFactory = new OracleDriverHandlerFactory() {
public DBDriverHandler createDBDriverHandler(PreparedStatement ps) {
return new Oracle816Handler(ps);
}
};
} else */{
// This DataSource uses a non oracle driver.
dBDriverHandlerFactory = new DBDriverHandlerFactory() {
public DBDriverHandler createDBDriverHandler(PreparedStatement ps) {
return new NonOracleHandler(ps);
}
public boolean supportsDefineColumnType() {
return false;
}
};
// Warn the user Oracle specific features will be disabled.
if(logger.isLoggable(logger.CONFIG)) {
identifier = identifier == null ?
"Connection Factory" : identifier; //NOI18N
logger.log(logger.CONFIG,
"sqlstore.database.oracle.nooracleavailable", //NOI18N
identifier);
}
}
testPs.close();
}
/**
* Defines Column type for result for specified ps.
*/
public void defineColumnTypeForResult(
PreparedStatement ps, List columns) throws SQLException {
if(dBDriverHandlerFactory.supportsDefineColumnType()) {
int size = columns.size();
if (size > 0) {
DBDriverHandler driverHandler =
dBDriverHandlerFactory.createDBDriverHandler(ps);
try {
for (int i = 0; i < size; i++) {
FieldMapping fieldMapping = (FieldMapping) columns.get(i);
int type = fieldMapping.getColumnType();
if (type == Types.CHAR || type == Types.VARCHAR) {
int len = fieldMapping.getColumnLength();
if (len > 0) {
driverHandler.defineColumnType(i + 1, type, len);
} else {
driverHandler.defineColumnType(i + 1, type);
}
} else {
driverHandler.defineColumnType(i + 1, type);
}
}
} catch (Exception ex) {
if (logger.isLoggable(Logger.INFO)) {
logger.log(Logger.INFO,
"sqlstore.database.oracle.defineCol", // NOI18N
ex);
}
driverHandler.clearDefines();
}
}
}
}
/**
* Implements special handling of char columns on Oracle.
*/
public void bindFixedCharColumn(PreparedStatement stmt,
int index, String strVal, int length) throws SQLException {
DBDriverHandler driverHandler =
dBDriverHandlerFactory.createDBDriverHandler(stmt);
driverHandler.bindFixedCharColumn(index, strVal, length);
}
/**
* Loads className using loader inside a previleged block.
* Returns null if class is not in classpath.
*/
private static Class loadClass(String className, ClassLoader loader) {
final ClassLoader finalLoader = loader;
final String finalClassName = className;
return (Class)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
try {
if (finalLoader != null) {
return Class.forName(finalClassName, true,
finalLoader);
} else {
return Class.forName(finalClassName);
}
} catch(Exception e) {
return null;
}
}
}
);
}
private interface DBDriverHandler {
void defineColumnType(int index, int type) throws SQLException;
void defineColumnType( int index, int type, int length)
throws SQLException;
public void clearDefines() throws SQLException;
public void bindFixedCharColumn(int index, String strVal, int length)
throws SQLException;
}
// private static class Oracle817Handler implements DBDriverHandler {
// oracle.jdbc.OraclePreparedStatement oraclePreparedStatement;
//
// public Oracle817Handler(Statement ps) {
// oraclePreparedStatement = (oracle.jdbc.OraclePreparedStatement)
// EJBHelper.unwrapStatement(ps);
// }
// public void defineColumnType(int index, int type) throws SQLException {
// oraclePreparedStatement.defineColumnType(index, type);
// }
// public void defineColumnType( int index, int type,int length)
// throws SQLException {
// oraclePreparedStatement.defineColumnType(index, type, length);
// }
// public void clearDefines() throws SQLException {
// oraclePreparedStatement.clearDefines();
// }
// public void bindFixedCharColumn(int index, String strVal, int length)
// throws SQLException {
// oraclePreparedStatement.setFixedCHAR(index, strVal);
// }
// }
//
// private static class Oracle816Handler implements DBDriverHandler {
// oracle.jdbc.driver.OraclePreparedStatement oraclePreparedStatement;
//
// public Oracle816Handler(Statement ps) {
// oraclePreparedStatement = (oracle.jdbc.driver.OraclePreparedStatement)
// EJBHelper.unwrapStatement(ps);
// }
// public void defineColumnType(int index, int type) throws SQLException {
// oraclePreparedStatement.defineColumnType(index, type);
// }
// public void defineColumnType( int index, int type,int length)
// throws SQLException {
// oraclePreparedStatement.defineColumnType(index, type, length);
// }
// public void clearDefines() throws SQLException {
// oraclePreparedStatement.clearDefines();
// }
// public void bindFixedCharColumn(int index, String strVal, int length)
// throws SQLException {
// oraclePreparedStatement.setFixedCHAR(index, strVal);
// }
// }
private static class NonOracleHandler implements DBDriverHandler {
PreparedStatement ps;
private NonOracleHandler(PreparedStatement ps) {
this.ps = ps;
}
public void defineColumnType(int index, int type) throws SQLException {}
public void defineColumnType( int index, int type,int length)
throws SQLException {}
public void clearDefines() throws SQLException {}
public void bindFixedCharColumn(int index, String strVal, int length)
throws SQLException {
// We are running on an Oracle database but not using an
// Oracle driver. We need to bind a field mapped to a CHAR column by
// padding the value with spaces to the length specified in the
// dbschema metadata.
ps.setString(index, padSpaceChar(strVal, length) );
if (logger.isLoggable(Logger.FINE) ) {
logger.log(Logger.FINE, "sqlstore.database.oracle.fixedcharpadded",
strVal, new Integer(length) );
}
}
private static final char SPACE_CHAR = ' ';
private static final int PAD_STEP_LENGTH = 50;
private static final char[] SPACES = new char[PAD_STEP_LENGTH];
static {
Arrays.fill(SPACES, SPACE_CHAR);
}
/**
* Pads space characters to specified val to increase its length to
* specified targetLength.
* @param val The input value.
* @param targetLength Target length for returned String
* @return val padded with space chars.
*/
private static String padSpaceChar(String val, int targetLength) {
String retVal = val;
int inputLength = val.length();
if(inputLength < targetLength) {
StringBuffer buf = new StringBuffer(targetLength);
buf.append(val);
int padsize = targetLength - inputLength;
while (padsize >= PAD_STEP_LENGTH) {
buf.append(SPACES);
padsize -= PAD_STEP_LENGTH;
}
buf.append(SPACES, 0, padsize);
retVal = buf.toString();
}
return retVal;
}
}
}