From e26ae6de8612033368aea317b4c582fad16a7682 Mon Sep 17 00:00:00 2001 From: berryware <231598+berryware@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:29:37 -0400 Subject: [PATCH 1/4] The migration is complete. Summary: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What changed: cpo-cassandra now runs on org.apache.cassandra:java-driver-core:4.19.2 (the Apache-maintained successor to the DataStax 3.x driver) against a cassandra:4.1.5 Testcontainers image, replacing the EOL com.datastax.cassandra:cassandra-driver-core:3.11.5/Cluster API. Key work: Rewrote ClusterDataSourceInfo/ClusterDataSource/CassandraSessionStrategy around CqlSession and driver 4.x's config-loader model Fixed CassandraCpoAdapter, CassandraMethodMapper, and CassandraCpoMetaAdapter for renamed APIs (DataType no longer an enum, BatchStatement builder, CqlIdentifier column names, several renamed getter/setters) Handled the immutable BoundStatement correctly in two places — including a subtle bug in a shared cpo-core code path (CpoStatementFactory.setBindValues) that silently dropped WHERE-clause bind values (e.g. UPDATE/EXIST with an auto-generated WHERE id = ?) until fixed with a Cassandra-specific override Removed 5 policy factory classes that no longer fit driver 4.x's architecture (driver now instantiates load-balancing/retry/reconnection/address-translator/speculative-execution/timestamp-generator classes itself); kept the factory pattern for auth/SSL/listeners where the driver still accepts pre-built instances Added a required localDatacenter config element, migrated timestamp columns from java.util.Date to java.time.Instant Updated all affected tests and the XSD Verification: full mvn verify at the repo root passes — all modules build, the 73-test cpo-cassandra suite is green against a live Cassandra 4.x container, and the aggregate JaCoCo coverage gate passes. Signed-off-by: berryware <231598+berryware@users.noreply.github.com> --- cpo-cassandra/pom.xml | 8 +- .../CassandraBoundStatementCpoData.java | 36 +- .../CassandraBoundStatementFactory.java | 68 ++- .../cpo/cassandra/CassandraCpoAdapter.java | 115 ++-- .../cassandra/CassandraSessionStrategy.java | 10 +- .../cpo/cassandra/ClusterDataSource.java | 33 +- .../cpo/cassandra/ClusterDataSourceInfo.java | 559 ++++++++++++------ .../config/AddressTranslatorFactory.java | 52 -- .../cassandra/config/AuthProviderFactory.java | 2 +- .../config/CassandraCpoConfigProcessor.java | 213 +++---- .../cpo/cassandra/config/ListenerFactory.java | 10 +- .../config/LoadBalancingPolicyFactory.java | 52 -- .../cassandra/config/NettyOptionsFactory.java | 52 -- .../config/ReconnectionPolicyFactory.java | 52 -- .../cassandra/config/RetryPolicyFactory.java | 52 -- .../cassandra/config/SSLOptionsFactory.java | 10 +- .../meta/CassandraCpoMetaAdapter.java | 98 ++- .../cassandra/meta/CassandraMethodMapper.java | 84 ++- .../cassandra/ClusterDataSourceInfoTest.java | 159 ++--- .../cpo/cassandra/DeleteObjectTest.java | 5 +- .../cpo/cassandra/InheritanceTest.java | 9 +- .../cpo/cassandra/InsertObjectTest.java | 9 +- .../cpo/cassandra/UpdateObjectTest.java | 5 +- .../CassandraCpoConfigProcessorTest.java | 49 +- .../cassandra/config/ConfigFactoryTest.java | 150 ++--- .../src/test/resources/cassandraMetaData.xml | 2 +- .../src/test/resources/cpoConfig.xml | 3 + cpo-core/src/main/resources/xsd/CpoConfig.xsd | 77 ++- 28 files changed, 969 insertions(+), 1005 deletions(-) delete mode 100644 cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/config/AddressTranslatorFactory.java delete mode 100644 cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/config/LoadBalancingPolicyFactory.java delete mode 100644 cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/config/NettyOptionsFactory.java delete mode 100644 cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/config/ReconnectionPolicyFactory.java delete mode 100644 cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/config/RetryPolicyFactory.java diff --git a/cpo-cassandra/pom.xml b/cpo-cassandra/pom.xml index 3f67b6d06..5dd0cab9d 100644 --- a/cpo-cassandra/pom.xml +++ b/cpo-cassandra/pom.xml @@ -38,7 +38,7 @@ initDB.cql cpoConfig.xml - cassandra:3.11.19 + cassandra:4.1.5 127.0.0.1 cpokeyspace ${project.baseUri}target/test-classes/cassandraMetaData.xml @@ -127,9 +127,9 @@ test - com.datastax.cassandra - cassandra-driver-core - 3.11.5 + org.apache.cassandra + java-driver-core + 4.19.2 + - + - This is the user to log into the data source. If the user is supplied in the url, this element should not be included. + + Whether DefaultLoadBalancingPolicy orders replicas by responsiveness/uptime (true) or + simply shuffles them (false) (basic.load-balancing-policy.slow-replica-avoidance). + - + - This is the password to log into the data source. If the password is supplied in the url, this element should not be included. + + Fully qualified class name of a custom NodeDistanceEvaluator, constructed by the driver + via a (DriverContext, String profileName) constructor + (advanced.load-balancing-policy.evaluator.class / LOAD_BALANCING_DISTANCE_EVALUATOR_CLASS). + + + + + + + The maximum number of nodes to contact in each remote datacenter for cross-datacenter + failover; 0 (the default) disables failover + (advanced.load-balancing-policy.dc-failover.max-nodes-per-remote-dc). + + + + + + + Whether cross-datacenter failover is allowed for requests using a local consistency + level (LOCAL_ONE, LOCAL_QUORUM, LOCAL_SERIAL). + + + + + + + An ordered remote datacenter name to prefer when building a failover query plan; repeat + for multiple datacenters, in preference order. + - - - - - - - + + + + + + A cipher suite to enable; repeat for multiple suites. + + + + + Whether to validate that the server certificate's hostname matches. + + + + + Whether to allow a DNS reverse lookup for SAN addresses when contact points are literal IPs. + + + + + + + + + How often to reload the keystore from keystorePath; if absent, it is never reloaded. + + + + - - - - - - - - + + + + + + Used only with FixedHostNameAddressTranslator. + + + + + Used only with SubnetAddressTranslator; repeat for multiple subnets. + + + + + Used only with SubnetAddressTranslator: the address to translate to when no subnet matches. + + + + + Whether to resolve addresses once on initialization (true) or on each node (re-)connection (false). + + + + - - + - + - Sets the core and maximum number of connections per host in one call. + A CIDR block, e.g. "100.64.0.0/15". - + - Sets the core number of connections per host. + The "host:port" to translate matching addresses to. - + + + + + + + - Sets the heart beat interval, after which a message is sent on an idle connection to make sure it's still alive. + Whether to force the driver to use Java's millisecond-precision system clock instead of the native microsecond-precision clock. + + + + + How far in the future timestamps may drift before a warning is logged; 0 disables the warning. + + + + + How often the drift warning is re-logged while timestamps keep drifting above the threshold. + + + + + + + + + + + Whether a warning is logged when a request changes the session's active keyspace (advanced.request.warn-if-set-keyspace). + + + + + Whether server-generated query warnings are logged by the driver (advanced.request.log-warnings). + + + + + How many times the driver retries fetching a query trace if it is not ready yet. + + + + + The interval between each query trace fetch attempt. + + + + + The consistency level to use when fetching query traces. + + + + + + + + + + + + Fully qualified class name of a RequestTracker to register; repeat for multiple + trackers. The driver provides RequestLogger out of the box (configured below). + + + + + + + The threshold above which a successful request is classified as "slow". + + + + + + + + + + + + + + + + + + + Fully qualified class name of a RequestThrottler implementation, constructed by the + driver via a (DriverContext) constructor. The driver provides + PassThroughRequestThrottler (default, no throttling), ConcurrencyLimitingRequestThrottler, + and RateLimitingRequestThrottler out of the box. + + + + + + The maximum number of requests that can be enqueued once the throttling threshold is exceeded. + + + + + Used only by ConcurrencyLimitingRequestThrottler. + + + + + Used only by RateLimitingRequestThrottler. + + + + + Used only by RateLimitingRequestThrottler: how often the throttler attempts to dequeue requests. + + + + + + + + + + + Whether schema metadata (Session.getMetadata().getKeyspaces()) is enabled. + + + + + + A keyspace name-matching rule for which schema/token metadata is maintained (exact + inclusion, "!"-prefixed exact exclusion, "/regex/" inclusion, or "!/regex/" exclusion); + repeat for multiple rules. If none are given, all keyspaces are included. + + + + + + + + The debounce window for coalescing bursts of schema-change events. + + + + + The maximum number of schema-change events to accumulate before forcing a refresh. + + + + + The debounce window for coalescing bursts of topology events. + + + + + The maximum number of topology events to accumulate before forcing propagation. + + + + + Whether token metadata (Session.getMetadata().getTokenMap()) is enabled. + + + + + + + + + + + Whether Session.prepare() calls are re-sent to all other active nodes after succeeding on the first one. + + + + + Whether the driver re-prepares statements on a node that just came back up or joined the cluster. + + + + + + The maximum number of statements to reprepare; 0 or negative means no limit. - + + + - Sets the timeout before an idle connection is removed. + Whether the driver-side prepared statement cache uses weak references for its values. - + + + + + + + + + Whether the driver's internal threads are daemon threads. + + + - Sets the maximum number of connections per host. + The number of I/O threads; 0 means availableProcessors() * 2. - + + + - Sets the maximum number of requests per connection. + The number of admin threads (cluster events, metadata refresh, reconnection scheduling). - + + + - Sets the threshold that triggers the creation of a new connection to a host. + How often the request-timeout/speculative-execution timer wakes up to check for expired tasks. - + - Sets the timeout when trying to acquire a connection from a host's pool. + The size of the timer's hashed wheel. - + + - + + + + The metrics library to use: "DefaultMetricsFactory"/"DropwizardMetricsFactory" (default), + "MicrometerMetricsFactory", "MicroProfileMetricsFactory", "NoopMetricsFactory" to force + disable, or a custom fully qualified MetricsFactory class name. + + + + + + "DefaultMetricIdGenerator" (name-only, e.g. for Dropwizard) or "TaggingMetricIdGenerator" (name+tags, e.g. for Micrometer). + + + - The HostDistance for which to set these threshold. + An optional prefix prepended to every generated metric name. - + + - the core number of connections. + + The name of a session-level metric to enable (e.g. "cql-requests", "bytes-sent", + "connected-nodes"); repeat for multiple metrics. All session metrics are disabled by + default. + - + - the max number of connections. + + The name of a node-level metric to enable (e.g. "cql-messages", "pool.open-connections"); + repeat for multiple metrics. All node metrics are disabled by default. + + + + + + How long to keep a node's metrics registered after it leaves the cluster. + + + + + Histogram tuning for the "cql-requests" session metric, if enabled. + + + + + Histogram tuning for the "throttling.delay" session metric, if enabled. + + + + + Histogram tuning for the "cql-messages" node metric, if enabled. - + - + - The HostDistance for which to set these threshold. + The largest latency expected to be recorded; used to scale internal data structures. - + + + + The number of significant decimal digits of resolution (Dropwizard: 0-5). + + + + + + A service-level-objective latency boundary to guarantee in the histogram; repeat for multiple boundaries (Micrometer only). + + + + + A percentile (0.0-1.0) to publish as its own time series; repeat for multiple percentiles (Micrometer only). + + + + + + + + + + This is the user to log into the data source. If the user is supplied in the url, this element should not be included. + + + - the new threshold. + This is the password to log into the data source. If the password is supplied in the url, this element should not be included. - + - - - + + + + + + + + + + + + + + + + + + + + The number of connections in the pool for a node in the local datacenter (advanced.connection.pool.local.size). + + + + + The number of connections in the pool for a node outside the local datacenter (advanced.connection.pool.remote.size). + + + + + Sets the heart beat interval, after which a message is sent on an idle connection to make sure it's still alive. + + + + + How long the driver waits for the response to a heartbeat before considering it failed (advanced.heartbeat.timeout). + + + + + The timeout for internal queries run just after a connection is opened (advanced.connection.init-query-timeout). + + + + + The timeout used when the driver changes the active keyspace on a connection at runtime (advanced.connection.set-keyspace-timeout). + + + + + The maximum number of requests that can execute concurrently on a single connection; must be positive and less than 32768 (advanced.connection.max-requests-per-connection). + + + + + The maximum number of "orphaned" (timed-out but still unacknowledged) requests before a connection is closed and replaced; must be lower than maxRequestsPerConnection (advanced.connection.max-orphan-requests). + + + + + Whether to log non-fatal errors when the driver tries to open a new connection (advanced.connection.warn-on-init-error). + + + + + @@ -563,11 +1175,6 @@ Sets whether to enable TCP keepalive. - - - Sets the per-host read timeout in milliseconds. - - Sets a hint to the size of the underlying buffers for incoming network I/O. From 8f61a7b4376a90484b98828d3c51d632fcb3e1e3 Mon Sep 17 00:00:00 2001 From: berryware <231598+berryware@users.noreply.github.com> Date: Thu, 23 Jul 2026 07:52:20 -0400 Subject: [PATCH 3/4] Change bind variale binding to one shot on the PreparedStatement vs one at a time on the BoundStatement. Signed-off-by: berryware <231598+berryware@users.noreply.github.com> --- .../CassandraBoundStatementFactory.java | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementFactory.java b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementFactory.java index 78ac837f0..e414f8ed1 100644 --- a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementFactory.java +++ b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementFactory.java @@ -24,7 +24,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.BoundStatement; -import java.lang.reflect.InvocationTargetException; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; import java.util.Collection; import java.util.List; import org.slf4j.Logger; @@ -32,7 +32,6 @@ import org.synchronoss.cpo.cassandra.meta.CassandraMethodMapper; import org.synchronoss.cpo.core.*; import org.synchronoss.cpo.core.helper.ExceptionHelper; -import org.synchronoss.cpo.core.meta.MethodMapEntry; import org.synchronoss.cpo.core.meta.MethodMapper; import org.synchronoss.cpo.core.meta.domain.CpoAttribute; import org.synchronoss.cpo.core.meta.domain.CpoClass; @@ -51,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; /** @@ -90,9 +90,9 @@ public CassandraBoundStatementFactory( getLocalLogger().debug("CpoFunction SQL = <" + sql + ">"); try { - boundStatement = sess.prepare(sql).bind(); - boundStatement = boundStatement.setPageSize(cassandraCpoAdapter.getFetchSize()); + preparedStatement = sess.prepare(sql); setBindValues(bindValues); + boundStatement = boundStatement.setPageSize(cassandraCpoAdapter.getFetchSize()); } catch (Throwable t) { getLocalLogger() .error( @@ -110,50 +110,46 @@ protected MethodMapper getMethodMapper() { } /** - * Binds dynamic where-clause values (raw datastore-typed literals, not backed by a CpoAttribute) - * directly to the BoundStatement. Overridden from {@link CpoStatementFactory#setBindValues} - * because that base implementation invokes the setter and discards its return value, which is - * correct for JDBC's mutating {@code PreparedStatement} but silently drops the bind value on - * driver 4.x's immutable {@code BoundStatement} -- every {@code setXxx(index, value)} call there - * returns a new instance that must be written back. + * 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; if - * {@code null} the call is a no-op - * @throws CpoException if a value could not be bound to the underlying statement + * @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 bindValues) throws CpoException { - if (bindValues == null) { - return; - } - - int index = getStartingIndex(); - - for (BindAttribute bindAttr : bindValues) { - Object bindObject = bindAttr.bindObject(); - CpoAttribute cpoAttribute = bindAttr.cpoAttribute(); - - MethodMapEntry jsm = getMethodMapper().getDataMethodMapEntry(bindObject.getClass()); - - if (jsm != null) { - getLocalLogger() - .debug( - "{}={}", - cpoAttribute == null ? bindAttr.name() : cpoAttribute.getDataName(), - bindObject); - try { - boundStatement = - (BoundStatement) jsm.getBsSetter().invoke(boundStatement, index++, bindObject); - } catch (IllegalAccessException iae) { - throw new CpoException("Error Accessing Prepared Statement Setter: ", iae); - } catch (InvocationTargetException ite) { - throw new CpoException("Error Invoking Prepared Statement Setter: ", ite); + 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; } - } else { - CpoData cpoData = getCpoData(cpoAttribute, index++); - cpoData.invokeSetter(bindObject); + i++; } } + + boundStatement = preparedStatement.bind(values); } @Override @@ -183,7 +179,8 @@ public BoundStatement getBoundStatement() { /** * 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 each bind-value assignment must write its result back here. + * so callers that individually adjust a single bound value (e.g. paging) must write the result + * back here. * * @param boundStatement The new BoundStatement instance */ From 8cf48e18d91ff13685fc3bf269f8a1978d184e3c Mon Sep 17 00:00:00 2001 From: berryware <231598+berryware@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:45:22 -0400 Subject: [PATCH 4/4] remove invokeSetter as all the bindings are set once using the .bind(Object ...) method on preparedStatement Signed-off-by: berryware <231598+berryware@users.noreply.github.com> --- .../CassandraBoundStatementCpoData.java | 81 +------- .../cassandra/meta/CassandraMethodMapper.java | 186 ++++-------------- 2 files changed, 46 insertions(+), 221 deletions(-) diff --git a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementCpoData.java b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementCpoData.java index 7f11c7203..cf48ab80f 100644 --- a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementCpoData.java +++ b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/CassandraBoundStatementCpoData.java @@ -22,29 +22,24 @@ * ]] */ -import com.datastax.oss.driver.api.core.cql.BoundStatement; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.synchronoss.cpo.cassandra.meta.CassandraCpoAttribute; -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 @@ -59,70 +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 { - // BoundStatement is immutable in driver 4.x: every setter returns a new instance that must - // be written back onto the factory, or the bind value is silently dropped. - BoundStatement current = cpoStatementFactory.getBoundStatement(); - BoundStatement updated; - switch (methodMapEntry.getMethodType()) { - case CassandraMethodMapEntry.METHOD_TYPE_BASIC: - updated = - (BoundStatement) methodMapEntry.getBsSetter().invoke(current, getIndex(), param); - break; - case CassandraMethodMapEntry.METHOD_TYPE_ONE: - { - CassandraCpoAttribute cassandraCpoAttribute = (CassandraCpoAttribute) cpoAttribute; - updated = - (BoundStatement) - methodMapEntry - .getBsSetter() - .invoke( - current, getIndex(), param, cassandraCpoAttribute.getValueTypeClass()); - } - break; - case CassandraMethodMapEntry.METHOD_TYPE_TWO: - { - CassandraCpoAttribute cassandraCpoAttribute = (CassandraCpoAttribute) cpoAttribute; - updated = - (BoundStatement) - methodMapEntry - .getBsSetter() - .invoke( - current, - getIndex(), - param, - cassandraCpoAttribute.getKeyTypeClass(), - cassandraCpoAttribute.getValueTypeClass()); - } - break; - default: - throw new CpoException( - "Invalid CassandraMethodMapEntry MetthodType: " + methodMapEntry.getMethodType()); - } - cpoStatementFactory.setBoundStatement(updated); - } 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; diff --git a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/meta/CassandraMethodMapper.java b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/meta/CassandraMethodMapper.java index 6bd4e75b4..7dc21c02d 100644 --- a/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/meta/CassandraMethodMapper.java +++ b/cpo-cassandra/src/main/java/org/synchronoss/cpo/cassandra/meta/CassandraMethodMapper.java @@ -22,7 +22,6 @@ * ]] */ -import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; @@ -45,8 +44,14 @@ import org.synchronoss.cpo.core.meta.MethodMapper; /** - * CassandraMethodMapper is a class that defines the getters and setters for all the - * Cassandra-specific data classes + * CassandraMethodMapper defines, for each Java type CPO binds to/from Cassandra, the {@link Row} + * getter used to read that type back from a query result. It also serves as the registry of known + * driver-native Java types: {@code org.synchronoss.cpo.cassandra.CassandraBoundStatementFactory}'s + * {@code setBindValues} consults it to tell a raw datastore-typed literal (e.g. a dynamic + * where-clause value) apart from a bean instance whose attribute value still needs to be extracted. + * There is no corresponding registry of BoundStatement setter methods -- bind values are applied + * via a single {@code PreparedStatement.bind(Object...)} call rather than per-attribute reflective + * setter invocation. * * @author david berry */ @@ -56,7 +61,6 @@ public class CassandraMethodMapper implements Serializable, Cloneable { /** Version Id for this class. */ @Serial private static final long serialVersionUID = 1L; - private static final Class bsc = BoundStatement.class; private static final Class rsc = Row.class; private static MethodMapper> methodMapper = initMethodMapper(); @@ -79,184 +83,112 @@ private CassandraMethodMapper() {} MethodMapper> mapper = new MethodMapper<>(); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - boolean.class, - boolean.class, - "getBool", - "setBool")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, boolean.class, boolean.class, "getBool")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Boolean.class, - boolean.class, - "getBool", - "setBool")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Boolean.class, boolean.class, "getBool")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - byte.class, - byte.class, - "getByte", - "setByte")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, byte.class, byte.class, "getByte")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Byte.class, - byte.class, - "getByte", - "setByte")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Byte.class, byte.class, "getByte")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, ByteBuffer.class, ByteBuffer.class, - "getByteBuffer", - "setByteBuffer")); + "getByteBuffer")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, ByteBuffer.class, ByteBuffer.class, - "getBytesUnsafe", - "setBytesUnsafe")); + "getBytesUnsafe")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, LocalDate.class, LocalDate.class, - "getLocalDate", - "setLocalDate")); + "getLocalDate")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, BigDecimal.class, BigDecimal.class, - "getBigDecimal", - "setBigDecimal")); + "getBigDecimal")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - double.class, - double.class, - "getDouble", - "setDouble")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, double.class, double.class, "getDouble")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Double.class, - double.class, - "getDouble", - "setDouble")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Double.class, double.class, "getDouble")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - float.class, - float.class, - "getFloat", - "setFloat")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, float.class, float.class, "getFloat")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Float.class, - float.class, - "getFloat", - "setFloat")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Float.class, float.class, "getFloat")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, InetAddress.class, InetAddress.class, - "getInetAddress", - "setInetAddress")); + "getInetAddress")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, int.class, int.class, "getInt", "setInt")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, int.class, int.class, "getInt")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Integer.class, - int.class, - "getInt", - "setInt")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Integer.class, int.class, "getInt")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_ONE, List.class, List.class, "getList", "setList")); + CassandraMethodMapEntry.METHOD_TYPE_ONE, List.class, List.class, "getList")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - long.class, - long.class, - "getLong", - "setLong")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, long.class, long.class, "getLong")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Long.class, - long.class, - "getLong", - "setLong")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Long.class, long.class, "getLong")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_TWO, Map.class, Map.class, "getMap", "setMap")); + CassandraMethodMapEntry.METHOD_TYPE_TWO, Map.class, Map.class, "getMap")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_ONE, Set.class, Set.class, "getSet", "setSet")); + CassandraMethodMapEntry.METHOD_TYPE_ONE, Set.class, Set.class, "getSet")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - short.class, - short.class, - "getShort", - "setShort")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, short.class, short.class, "getShort")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Short.class, - short.class, - "getShort", - "setShort")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Short.class, short.class, "getShort")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - String.class, - String.class, - "getString", - "setString")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, String.class, String.class, "getString")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - Instant.class, - Instant.class, - "getInstant", - "setInstant")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, Instant.class, Instant.class, "getInstant")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, TupleValue.class, TupleValue.class, - "getTupleValue", - "setTupleValue")); + "getTupleValue")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, UdtValue.class, UdtValue.class, - "getUdtValue", - "setUdtValue")); + "getUdtValue")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( - CassandraMethodMapEntry.METHOD_TYPE_BASIC, - UUID.class, - UUID.class, - "getUuid", - "setUuid")); + CassandraMethodMapEntry.METHOD_TYPE_BASIC, UUID.class, UUID.class, "getUuid")); mapper.addMethodMapEntry( makeCassandraMethodMapEntry( CassandraMethodMapEntry.METHOD_TYPE_BASIC, BigInteger.class, BigInteger.class, - "getBigInteger", - "setBigInteger")); + "getBigInteger")); return mapper; } @@ -271,52 +203,14 @@ public static MethodMapper getMethodMapper() { } private static CassandraMethodMapEntry makeCassandraMethodMapEntry( - int methodType, - Class javaClass, - Class datasourceMethodClass, - String getterName, - String setterName) + int methodType, Class javaClass, Class datasourceMethodClass, String getterName) throws IllegalArgumentException { Method rsGetter = loadGetter(methodType, rsc, getterName); - Method bsSetter = loadSetter(methodType, bsc, datasourceMethodClass, setterName); + // no BoundStatement setter Method is resolved: bind values are applied via a single + // PreparedStatement.bind(Object...) call, not per-attribute reflective setter invocation return new CassandraMethodMapEntry( - methodType, javaClass, datasourceMethodClass, rsGetter, bsSetter); - } - - /** - * Loads the SetterMethod for the Datasource class. Collection setters (METHOD_TYPE_ONE/TWO) take - * one or two extra {@code Class} parameters describing the element type(s), mirroring the shape - * of {@link #loadGetter(int, Class, String)} -- driver 4.x's {@code SettableByIndex} has no plain - * {@code setList(int, List)} overload, only the element-type-qualified ones. - */ - private static Method loadSetter( - int methodType, Class methodClass, Class datasourceClass, String setterName) - throws IllegalArgumentException { - Method setter; - try { - switch (methodType) { - case CassandraMethodMapEntry.METHOD_TYPE_BASIC: - setter = methodClass.getMethod(setterName, new Class[] {int.class, datasourceClass}); - break; - case CassandraMethodMapEntry.METHOD_TYPE_ONE: - setter = - methodClass.getMethod( - setterName, new Class[] {int.class, datasourceClass, Class.class}); - break; - case CassandraMethodMapEntry.METHOD_TYPE_TWO: - setter = - methodClass.getMethod( - setterName, new Class[] {int.class, datasourceClass, Class.class, Class.class}); - break; - default: - throw new IllegalArgumentException("Illegal Method Type:" + methodType); - } - } catch (NoSuchMethodException nsme) { - logger.error("Error loading Setter" + setterName, nsme); - throw new IllegalArgumentException(nsme); - } - return setter; + methodType, javaClass, datasourceMethodClass, rsGetter, null); } /**