Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cpo-cassandra/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<properties>
<cassandra.initScript>initDB.cql</cassandra.initScript>
<cassandra.cpoConfig>cpoConfig.xml</cassandra.cpoConfig>
<cassandra.image>cassandra:3.11.19</cassandra.image>
<cassandra.image>cassandra:4.1.5</cassandra.image>
<cassandra.contactPoint>127.0.0.1</cassandra.contactPoint>
<cassandra.keyspace>cpokeyspace</cassandra.keyspace>
<cassandra.metaXml>${project.baseUri}target/test-classes/cassandraMetaData.xml</cassandra.metaXml>
Expand Down Expand Up @@ -127,9 +127,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>3.11.5</version>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-core</artifactId>
<version>4.19.2</version>
</dependency>
<!-- Testcontainers' shaded Docker client relocates jackson-databind/jackson-core but
still references the unshaded com.fasterxml.jackson.annotation classes, so this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,24 @@
* ]]
*/

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.synchronoss.cpo.cassandra.meta.CassandraMethodMapEntry;
import org.synchronoss.cpo.cassandra.meta.CassandraMethodMapper;
import org.synchronoss.cpo.cassandra.transform.CassandraCpoTransform;
import org.synchronoss.cpo.core.CpoException;
import org.synchronoss.cpo.core.helper.ExceptionHelper;
import org.synchronoss.cpo.core.meta.AbstractBindableCpoData;
import org.synchronoss.cpo.core.meta.domain.CpoAttribute;
import org.synchronoss.cpo.core.transform.CpoTransform;

/**
* Helps manage data transfer between the CPO object and the Cassandra bound statement
* Helps manage data transfer between the CPO object and the Cassandra bound statement. Bind values
* are actually applied in {@link CassandraBoundStatementFactory#setBindValues}, which builds the
* whole value array and binds it in a single {@code PreparedStatement.bind(Object...)} call; this
* class only supplies the transform half of the contract (see {@link #transformOut(Object)}), used
* to resolve each attribute's value before it goes into that array.
*
* @author dberry
*/
public class CassandraBoundStatementCpoData extends AbstractBindableCpoData {

private static final Logger logger =
LoggerFactory.getLogger(CassandraBoundStatementCpoData.class);
private CassandraBoundStatementFactory cpoStatementFactory = null;
private final CassandraBoundStatementFactory cpoStatementFactory;

/**
* Constructs the CassandraBoundStatementCpoData
Expand All @@ -57,42 +54,6 @@ public CassandraBoundStatementCpoData(
this.cpoStatementFactory = cpoStatementFactory;
}

@Override
public void invokeSetter(Object instanceObject) throws CpoException {
Logger localLogger =
instanceObject == null ? logger : LoggerFactory.getLogger(instanceObject.getClass());
CpoAttribute cpoAttribute = getCpoAttribute();
Object param = transformOut(cpoAttribute.invokeGetter(instanceObject));
CassandraMethodMapEntry<?, ?> methodMapEntry =
CassandraMethodMapper.getDatasourceMethod(getDataSetterParamType());
if (methodMapEntry == null) {
throw new CpoException(
"Error Retrieveing Cassandra Method for type: " + getDataSetterParamType().getName());
}
// per-attribute bind values are debug detail, not operational info
localLogger.debug("{}={}", cpoAttribute.getDataName(), param);
try {
switch (methodMapEntry.getMethodType()) {
case CassandraMethodMapEntry.METHOD_TYPE_BASIC:
case CassandraMethodMapEntry.METHOD_TYPE_ONE:
case CassandraMethodMapEntry.METHOD_TYPE_TWO:
methodMapEntry
.getBsSetter()
.invoke(cpoStatementFactory.getBoundStatement(), getIndex(), param);
break;
default:
throw new CpoException(
"Invalid CassandraMethodMapEntry MetthodType: " + methodMapEntry.getMethodType());
}
} catch (Exception e) {
throw new CpoException(
"Error Invoking Cassandra Method: "
+ methodMapEntry.getBsSetter().getName()
+ ":"
+ ExceptionHelper.getLocalizedMessage(e));
}
}

@Override
public Object transformOut(Object attributeObject) throws CpoException {
Object retObj = attributeObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
* ]]
*/

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.Session;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
Expand All @@ -49,6 +50,7 @@ public class CassandraBoundStatementFactory extends CpoStatementFactory implemen

private static final Logger logger =
LoggerFactory.getLogger(CassandraBoundStatementFactory.class);
private final PreparedStatement preparedStatement;
private BoundStatement boundStatement;

/**
Expand All @@ -70,7 +72,7 @@ public class CassandraBoundStatementFactory extends CpoStatementFactory implemen
* @throws CpoException if a CPO error occurs
*/
public <T> CassandraBoundStatementFactory(
Session sess,
CqlSession sess,
CassandraCpoAdapter cassandraCpoAdapter,
CpoClass criteria,
CpoFunction function,
Expand All @@ -88,9 +90,9 @@ public <T> CassandraBoundStatementFactory(

getLocalLogger().debug("CpoFunction SQL = <" + sql + ">");
try {
boundStatement = sess.prepare(sql).bind();
boundStatement.setFetchSize(cassandraCpoAdapter.getFetchSize());
preparedStatement = sess.prepare(sql);
setBindValues(bindValues);
boundStatement = boundStatement.setPageSize(cassandraCpoAdapter.getFetchSize());
} catch (Throwable t) {
getLocalLogger()
.error(
Expand All @@ -107,6 +109,49 @@ protected MethodMapper getMethodMapper() {
return CassandraMethodMapper.getMethodMapper();
}

/**
* Binds every value in a single call, rather than one call per bind variable. Overridden from
* {@link CpoStatementFactory#setBindValues} because driver 4.x's {@code BoundStatement} is
* immutable -- every individual {@code setXxx(index, value)} call returns a new instance instead
* of mutating in place -- so building the full value array once and calling {@link
* PreparedStatement#bind(Object...)} is both simpler and avoids that pitfall entirely.
*
* @param bindValues the bind values to apply to the underlying statement, in parameter order;
* {@code null} is treated as no bind values
* @throws CpoException if a value could not be resolved for binding
*/
@Override
public void setBindValues(Collection<BindAttribute> bindValues) throws CpoException {
Object[] values = new Object[bindValues == null ? 0 : bindValues.size()];

if (bindValues != null) {
int i = 0;
for (BindAttribute bindAttr : bindValues) {
Object bindObject = bindAttr.bindObject();
CpoAttribute cpoAttribute = bindAttr.cpoAttribute();

if (getMethodMapper().getDataMethodMapEntry(bindObject.getClass()) != null) {
// a raw datastore-typed literal (e.g. a dynamic where-clause value): bind as-is
getLocalLogger()
.debug(
"{}={}",
cpoAttribute == null ? bindAttr.name() : cpoAttribute.getDataName(),
bindObject);
values[i] = bindObject;
} else {
// bindObject is the bean; extract and transform the attribute's value
CpoData cpoData = getCpoData(cpoAttribute, i);
Object param = cpoData.transformOut(cpoAttribute.invokeGetter(bindObject));
getLocalLogger().debug("{}={}", cpoAttribute.getDataName(), param);
values[i] = param;
}
i++;
}
}

boundStatement = preparedStatement.bind(values);
}

@Override
protected CpoData getCpoData(CpoAttribute cpoAttribute, int index) {
return new CassandraBoundStatementCpoData(this, cpoAttribute, index);
Expand All @@ -130,4 +175,16 @@ protected int getStartingIndex() {
public BoundStatement getBoundStatement() {
return boundStatement;
}

/**
* Replaces the BoundStatement held by this factory. Driver 4.x's BoundStatement is immutable:
* every {@code setXxx(index, value)} call returns a new instance rather than mutating in place,
* so callers that individually adjust a single bound value (e.g. paging) must write the result
* back here.
*
* @param boundStatement The new BoundStatement instance
*/
void setBoundStatement(BoundStatement boundStatement) {
this.boundStatement = boundStatement;
}
}
Loading
Loading