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.CassandraMethodMapEntry;
33 import org.synchronoss.cpo.cassandra.meta.CassandraMethodMapper;
34 import org.synchronoss.cpo.core.*;
35 import org.synchronoss.cpo.core.helper.ExceptionHelper;
36 import org.synchronoss.cpo.core.meta.MethodMapper;
37 import org.synchronoss.cpo.core.meta.domain.CpoAttribute;
38 import org.synchronoss.cpo.core.meta.domain.CpoClass;
39 import org.synchronoss.cpo.core.meta.domain.CpoFunction;
40
41 /**
42 * CassandraBoundStatementFactory is the object that encapsulates the creation of the actual
43 * Cassandra {@link BoundStatement} used to execute a CpoFunction against the datastax driver.
44 *
45 * @author david berry
46 */
47 public final class CassandraBoundStatementFactory extends CpoStatementFactory
48 implements CpoReleasable {
49
50 /** Version Id for this class. */
51 private static final long serialVersionUID = 1L;
52
53 private static final Logger logger =
54 LoggerFactory.getLogger(CassandraBoundStatementFactory.class);
55 private final PreparedStatement preparedStatement;
56 private BoundStatement boundStatement;
57
58 /**
59 * Used to build the Cassandra {@link BoundStatement} that CPO executes for a given CpoFunction.
60 * The constructor is called by the internal CPO framework. This is not to be used by users of
61 * CPO. Programmers that build Transforms may need to use this object to get access to the actual
62 * bound statement.
63 *
64 * @param <T> The type of the object being bound
65 * @param sess The Cassandra session that will be used to prepare and bind the statement.
66 * @param cassandraCpoAdapter The CassandraCpoAdapter that is controlling this transaction
67 * @param criteria The object that will be used to look up the cpo metadata
68 * @param function The CpoFunction that is being executed
69 * @param bean The bean that is being acted upon
70 * @param wheres A collection of wheres to find the object
71 * @param orderBy A collection of orderbys to sort the objects
72 * @param nativeQueries Additional CQL to be embedded into the CpoFunction CQL that is used to
73 * create the actual Cassandra BoundStatement
74 * @throws CpoException if a CPO error occurs
75 */
76 public <T> CassandraBoundStatementFactory(
77 CqlSession sess,
78 CassandraCpoAdapter cassandraCpoAdapter,
79 CpoClass criteria,
80 CpoFunction function,
81 T bean,
82 Collection<CpoWhere> wheres,
83 Collection<CpoOrderBy> orderBy,
84 Collection<CpoNativeFunction> nativeQueries)
85 throws CpoException {
86 super(bean == null ? logger : LoggerFactory.getLogger(bean.getClass()));
87 // get the list of bindValues from the function parameters
88 List<BindAttribute> bindValues = getBindValues(function, bean);
89
90 String sql =
91 buildSql(criteria, function.getExpression(), wheres, orderBy, nativeQueries, bindValues);
92
93 getLocalLogger().debug("CpoFunction SQL = <" + sql + ">");
94 try {
95 preparedStatement = sess.prepare(sql);
96 setBindValues(bindValues);
97 boundStatement = boundStatement.setPageSize(cassandraCpoAdapter.getFetchSize());
98 } catch (Throwable t) {
99 getLocalLogger()
100 .error(
101 "Error Instantiating CassandraBoundStatementFactory SQL=<"
102 + sql
103 + ">"
104 + ExceptionHelper.getLocalizedMessage(t));
105 throw new CpoException(t);
106 }
107 }
108
109 @Override
110 protected MethodMapper<CassandraMethodMapEntry<?, ?>> getMethodMapper() {
111 return CassandraMethodMapper.getMethodMapper();
112 }
113
114 /**
115 * Binds every value in a single call, rather than one call per bind variable. Overridden from
116 * {@link CpoStatementFactory#setBindValues} because driver 4.x's {@code BoundStatement} is
117 * immutable -- every individual {@code setXxx(index, value)} call returns a new instance instead
118 * of mutating in place -- so building the full value array once and calling {@link
119 * PreparedStatement#bind(Object...)} is both simpler and avoids that pitfall entirely.
120 *
121 * @param bindValues the bind values to apply to the underlying statement, in parameter order;
122 * {@code null} is treated as no bind values
123 * @throws CpoException if a value could not be resolved for binding
124 */
125 @Override
126 public void setBindValues(Collection<BindAttribute> bindValues) throws CpoException {
127 Object[] values = new Object[bindValues == null ? 0 : bindValues.size()];
128
129 if (bindValues != null) {
130 int i = 0;
131 for (BindAttribute bindAttr : bindValues) {
132 Object bindObject = bindAttr.bindObject();
133 CpoAttribute cpoAttribute = bindAttr.cpoAttribute();
134
135 if (getMethodMapper().getDataMethodMapEntry(bindObject.getClass()) != null) {
136 // a raw datastore-typed literal (e.g. a dynamic where-clause value): bind as-is
137 getLocalLogger()
138 .debug(
139 "{}={}",
140 cpoAttribute == null ? bindAttr.name() : cpoAttribute.getDataName(),
141 bindObject);
142 values[i] = bindObject;
143 } else {
144 // bindObject is the bean; extract and transform the attribute's value
145 CpoData cpoData = getCpoData(cpoAttribute, i);
146 Object param = cpoData.transformOut(cpoAttribute.invokeGetter(bindObject));
147 getLocalLogger().debug("{}={}", cpoAttribute.getDataName(), param);
148 values[i] = param;
149 }
150 i++;
151 }
152 }
153
154 boundStatement = preparedStatement.bind(values);
155 }
156
157 @Override
158 protected CpoData getCpoData(CpoAttribute cpoAttribute, int index) {
159 return new CassandraBoundStatementCpoData(this, cpoAttribute, index);
160 }
161
162 @Override
163 protected Object getBindableStatement() {
164 return getBoundStatement();
165 }
166
167 @Override
168 protected int getStartingIndex() {
169 return 0;
170 }
171
172 /**
173 * Gets the BoundStatent associated with this factory
174 *
175 * @return The BoundStatement
176 */
177 public BoundStatement getBoundStatement() {
178 return boundStatement;
179 }
180
181 /**
182 * Replaces the BoundStatement held by this factory. Driver 4.x's BoundStatement is immutable:
183 * every {@code setXxx(index, value)} call returns a new instance rather than mutating in place,
184 * so callers that individually adjust a single bound value (e.g. paging) must write the result
185 * back here.
186 *
187 * @param boundStatement The new BoundStatement instance
188 */
189 void setBoundStatement(BoundStatement boundStatement) {
190 this.boundStatement = boundStatement;
191 }
192 }