/*****************************************************************
* 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.cayenne.dba.oracle;
import java.io.OutputStream;
import java.io.Writer;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import org.apache.cayenne.CayenneException;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.OperationObserver;
import org.apache.cayenne.access.translator.DbAttributeBinding;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.log.JdbcEventLogger;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.query.BatchQuery;
import org.apache.cayenne.query.BatchQueryRow;
import org.apache.cayenne.query.InsertBatchQuery;
import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.query.UpdateBatchQuery;
import org.apache.cayenne.util.Util;
/**
* @since 3.0
*/
class Oracle8LOBBatchAction implements SQLAction {
private BatchQuery query;
private DbAdapter adapter;
private JdbcEventLogger logger;
private static void bind(DbAdapter adapter, PreparedStatement statement, DbAttributeBinding[] bindings)
throws SQLException, Exception {
for (DbAttributeBinding b : bindings) {
DbAttributeBinding binding = new DbAttributeBinding(b.getAttribute());
adapter.bindParameter(statement, binding);
}
}
Oracle8LOBBatchAction(BatchQuery query, DbAdapter adapter, JdbcEventLogger logger) {
this.adapter = adapter;
this.query = query;
this.logger = logger;
}
@Override
public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception {
Oracle8LOBBatchTranslator translator;
if (query instanceof InsertBatchQuery) {
translator = new Oracle8LOBInsertBatchTranslator((InsertBatchQuery) query, adapter,
OracleAdapter.TRIM_FUNCTION);
} else if (query instanceof UpdateBatchQuery) {
translator = new Oracle8LOBUpdateBatchTranslator((UpdateBatchQuery) query, adapter,
OracleAdapter.TRIM_FUNCTION);
} else {
throw new CayenneException("Unsupported batch type for special LOB processing: " + query);
}
translator.setNewBlobFunction(OracleAdapter.NEW_BLOB_FUNCTION);
translator.setNewClobFunction(OracleAdapter.NEW_CLOB_FUNCTION);
// no batching is done, queries are translated
// for each batch set, since prepared statements
// may be different depending on whether LOBs are NULL or not..
Oracle8LOBBatchQueryWrapper selectQuery = new Oracle8LOBBatchQueryWrapper(query);
List<DbAttribute> qualifierAttributes = selectQuery.getDbAttributesForLOBSelectQualifier();
for (BatchQueryRow row : query.getRows()) {
selectQuery.indexLOBAttributes(row);
int updated;
String updateStr = translator.createSql(row);
// 1. run row update
logger.log(updateStr);
try (PreparedStatement statement = connection.prepareStatement(updateStr)) {
DbAttributeBinding[] bindings = translator.updateBindings(row);
logger.logQueryParameters("bind", bindings);
bind(adapter, statement, bindings);
updated = statement.executeUpdate();
logger.logUpdateCount(updated);
}
// 2. run row LOB update (SELECT...FOR UPDATE and writing out LOBs)
processLOBRow(connection, translator, selectQuery, qualifierAttributes, row);
// finally, notify delegate that the row was updated
observer.nextCount(query, updated);
}
}
void processLOBRow(Connection con, Oracle8LOBBatchTranslator queryBuilder, Oracle8LOBBatchQueryWrapper selectQuery,
List<DbAttribute> qualifierAttributes, BatchQueryRow row) throws SQLException, Exception {
List<DbAttribute> lobAttributes = selectQuery.getDbAttributesForUpdatedLOBColumns();
if (lobAttributes.size() == 0) {
return;
}
final boolean isLoggable = logger.isLoggable();
List<Object> qualifierValues = selectQuery.getValuesForLOBSelectQualifier(row);
List<Object> lobValues = selectQuery.getValuesForUpdatedLOBColumns();
int parametersSize = qualifierValues.size();
int lobSize = lobAttributes.size();
String selectStr = queryBuilder.createLOBSelectString(lobAttributes, qualifierAttributes);
try (PreparedStatement selectStatement = con.prepareStatement(selectStr)) {
DbAttributeBinding[] attributeBindings = null;
if(isLoggable) {
attributeBindings = new DbAttributeBinding[parametersSize];
}
for (int i = 0; i < parametersSize; i++) {
DbAttribute attribute = qualifierAttributes.get(i);
Object value = qualifierValues.get(i);
ExtendedType extendedType = value != null
? adapter.getExtendedTypes().getRegisteredType(value.getClass())
: adapter.getExtendedTypes().getDefaultType();
DbAttributeBinding binding = new DbAttributeBinding(attribute);
binding.setStatementPosition(i + 1);
binding.setValue(value);
binding.setExtendedType(extendedType);
adapter.bindParameter(selectStatement, binding);
if(isLoggable) {
attributeBindings[i] = binding;
}
}
if (isLoggable) {
logger.logQuery(selectStr, attributeBindings);
}
try (ResultSet result = selectStatement.executeQuery()) {
if (!result.next()) {
throw new CayenneRuntimeException("Missing LOB row.");
}
// read the only expected row
for (int i = 0; i < lobSize; i++) {
DbAttribute attribute = lobAttributes.get(i);
int type = attribute.getType();
if (type == Types.CLOB) {
Clob clob = result.getClob(i + 1);
Object clobVal = lobValues.get(i);
if (clobVal instanceof char[]) {
writeClob(clob, (char[]) clobVal);
} else {
writeClob(clob, clobVal.toString());
}
} else if (type == Types.BLOB) {
Blob blob = result.getBlob(i + 1);
Object blobVal = lobValues.get(i);
if (blobVal instanceof byte[]) {
writeBlob(blob, (byte[]) blobVal);
} else {
String className = (blobVal != null) ? blobVal.getClass().getName() : null;
throw new CayenneRuntimeException("Unsupported class of BLOB value: %s", className);
}
} else {
throw new CayenneRuntimeException("Only BLOB or CLOB is expected here, got: %s", type);
}
}
if (result.next()) {
throw new CayenneRuntimeException("More than one LOB row found.");
}
}
}
}
/**
* Override the Oracle writeBlob() method to be compatible with Oracle8
* drivers.
*/
protected void writeBlob(Blob blob, byte[] value) {
// Fix for CAY-1307. For Oracle8, get the method found by reflection in
// OracleAdapter. (Code taken from Cayenne 2.)
Method getBinaryStreamMethod = Oracle8Adapter.getOutputStreamFromBlobMethod();
try {
try (OutputStream out = (OutputStream) getBinaryStreamMethod.invoke(blob, (Object[]) null)) {
out.write(value);
out.flush();
}
} catch (Exception e) {
throw new CayenneRuntimeException("Error processing BLOB.", Util.unwindException(e));
}
}
/**
* Override the Oracle writeClob() method to be compatible with Oracle8
* drivers.
*/
protected void writeClob(Clob clob, char[] value) {
Method getWriterMethod = Oracle8Adapter.getWriterFromClobMethod();
try {
try (Writer out = (Writer) getWriterMethod.invoke(clob, (Object[]) null)) {
out.write(value);
out.flush();
}
} catch (Exception e) {
throw new CayenneRuntimeException("Error processing CLOB.", Util.unwindException(e));
}
}
/**
* Override the Oracle writeClob() method to be compatible with Oracle8
* drivers.
*/
protected void writeClob(Clob clob, String value) {
Method getWriterMethod = Oracle8Adapter.getWriterFromClobMethod();
try {
try (Writer out = (Writer) getWriterMethod.invoke(clob, (Object[]) null)) {
out.write(value);
out.flush();
}
} catch (Exception e) {
throw new CayenneRuntimeException("Error processing CLOB.", Util.unwindException(e));
}
}
}