1 package org.synchronoss.cpo.cassandra;
2
3 /*-
4 * [[
5 * cassandra
6 * ==
7 * Copyright (C) 2003 - 2026 Exaxis LLC, Synchronoss Technologies Inc
8 * ==
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Lesser Public License for more details.
18 *
19 * You should have received a copy of the GNU General Lesser Public
20 * License along with this program. If not, see
21 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22 * ]]
23 */
24
25 import com.datastax.oss.driver.api.core.CqlSession;
26 import com.datastax.oss.driver.api.core.cql.BoundStatement;
27 import com.datastax.oss.driver.api.core.cql.PreparedStatement;
28 import java.util.Collection;
29 import java.util.List;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32 import org.synchronoss.cpo.cassandra.meta.CassandraMethodMapper;
33 import org.synchronoss.cpo.core.*;
34 import org.synchronoss.cpo.core.helper.ExceptionHelper;
35 import org.synchronoss.cpo.core.meta.MethodMapper;
36 import org.synchronoss.cpo.core.meta.domain.CpoAttribute;
37 import org.synchronoss.cpo.core.meta.domain.CpoClass;
38 import org.synchronoss.cpo.core.meta.domain.CpoFunction;
39
40 /**
41 * CassandraBoundStatementFactory is the object that encapsulates the creation of the actual
42 * Cassandra {@link BoundStatement} used to execute a CpoFunction against the datastax driver.
43 *
44 * @author david berry
45 */
46 public class CassandraBoundStatementFactory extends CpoStatementFactory implements CpoReleasable {
47
48 /** Version Id for this class. */
49 private static final long serialVersionUID = 1L;
50
51 private static final Logger logger =
52 LoggerFactory.getLogger(CassandraBoundStatementFactory.class);
53 private final PreparedStatement preparedStatement;
54 private BoundStatement boundStatement;
55
56 /**
57 * Used to build the Cassandra {@link BoundStatement} that CPO executes for a given CpoFunction.
58 * The constructor is called by the internal CPO framework. This is not to be used by users of
59 * CPO. Programmers that build Transforms may need to use this object to get access to the actual
60 * bound statement.
61 *
62 * @param <T> The type of the object being bound
63 * @param sess The Cassandra session that will be used to prepare and bind the statement.
64 * @param cassandraCpoAdapter The CassandraCpoAdapter that is controlling this transaction
65 * @param criteria The object that will be used to look up the cpo metadata
66 * @param function The CpoFunction that is being executed
67 * @param bean The bean that is being acted upon
68 * @param wheres A collection of wheres to find the object
69 * @param orderBy A collection of orderbys to sort the objects
70 * @param nativeQueries Additional CQL to be embedded into the CpoFunction CQL that is used to
71 * create the actual Cassandra BoundStatement
72 * @throws CpoException if a CPO error occurs
73 */
74 public <T> CassandraBoundStatementFactory(
75 CqlSession sess,
76 CassandraCpoAdapter cassandraCpoAdapter,
77 CpoClass criteria,
78 CpoFunction function,
79 T bean,
80 Collection<CpoWhere> wheres,
81 Collection<CpoOrderBy> orderBy,
82 Collection<CpoNativeFunction> nativeQueries)
83 throws CpoException {
84 super(bean == null ? logger : LoggerFactory.getLogger(bean.getClass()));
85 // get the list of bindValues from the function parameters
86 List<BindAttribute> bindValues = getBindValues(function, bean);
87
88 String sql =
89 buildSql(criteria, function.getExpression(), wheres, orderBy, nativeQueries, bindValues);
90
91 getLocalLogger().debug("CpoFunction SQL = <" + sql + ">");
92 try {
93 preparedStatement = sess.prepare(sql);
94 setBindValues(bindValues);
95 boundStatement = boundStatement.setPageSize(cassandraCpoAdapter.getFetchSize());
96 } catch (Throwable t) {
97 getLocalLogger()
98 .error(
99 "Error Instantiating CassandraBoundStatementFactory SQL=<"
100 + sql
101 + ">"
102 + ExceptionHelper.getLocalizedMessage(t));
103 throw new CpoException(t);
104 }
105 }
106
107 @Override
108 protected MethodMapper getMethodMapper() {
109 return CassandraMethodMapper.getMethodMapper();
110 }
111
112 /**
113 * Binds every value in a single call, rather than one call per bind variable. Overridden from
114 * {@link CpoStatementFactory#setBindValues} because driver 4.x's {@code BoundStatement} is
115 * immutable -- every individual {@code setXxx(index, value)} call returns a new instance instead
116 * of mutating in place -- so building the full value array once and calling {@link
117 * PreparedStatement#bind(Object...)} is both simpler and avoids that pitfall entirely.
118 *
119 * @param bindValues the bind values to apply to the underlying statement, in parameter order;
120 * {@code null} is treated as no bind values
121 * @throws CpoException if a value could not be resolved for binding
122 */
123 @Override
124 public void setBindValues(Collection<BindAttribute> bindValues) throws CpoException {
125 Object[] values = new Object[bindValues == null ? 0 : bindValues.size()];
126
127 if (bindValues != null) {
128 int i = 0;
129 for (BindAttribute bindAttr : bindValues) {
130 Object bindObject = bindAttr.bindObject();
131 CpoAttribute cpoAttribute = bindAttr.cpoAttribute();
132
133 if (getMethodMapper().getDataMethodMapEntry(bindObject.getClass()) != null) {
134 // a raw datastore-typed literal (e.g. a dynamic where-clause value): bind as-is
135 getLocalLogger()
136 .debug(
137 "{}={}",
138 cpoAttribute == null ? bindAttr.name() : cpoAttribute.getDataName(),
139 bindObject);
140 values[i] = bindObject;
141 } else {
142 // bindObject is the bean; extract and transform the attribute's value
143 CpoData cpoData = getCpoData(cpoAttribute, i);
144 Object param = cpoData.transformOut(cpoAttribute.invokeGetter(bindObject));
145 getLocalLogger().debug("{}={}", cpoAttribute.getDataName(), param);
146 values[i] = param;
147 }
148 i++;
149 }
150 }
151
152 boundStatement = preparedStatement.bind(values);
153 }
154
155 @Override
156 protected CpoData getCpoData(CpoAttribute cpoAttribute, int index) {
157 return new CassandraBoundStatementCpoData(this, cpoAttribute, index);
158 }
159
160 @Override
161 protected Object getBindableStatement() {
162 return getBoundStatement();
163 }
164
165 @Override
166 protected int getStartingIndex() {
167 return 0;
168 }
169
170 /**
171 * Gets the BoundStatent associated with this factory
172 *
173 * @return The BoundStatement
174 */
175 public BoundStatement getBoundStatement() {
176 return boundStatement;
177 }
178
179 /**
180 * Replaces the BoundStatement held by this factory. Driver 4.x's BoundStatement is immutable:
181 * every {@code setXxx(index, value)} call returns a new instance rather than mutating in place,
182 * so callers that individually adjust a single bound value (e.g. paging) must write the result
183 * back here.
184 *
185 * @param boundStatement The new BoundStatement instance
186 */
187 void setBoundStatement(BoundStatement boundStatement) {
188 this.boundStatement = boundStatement;
189 }
190 }