View Javadoc
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 }