View Javadoc
1   package org.synchronoss.cpo.core;
2   
3   /*-
4    * [[
5    * core
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 java.util.Collection;
26  import org.synchronoss.cpo.core.enums.Comparison;
27  import org.synchronoss.cpo.core.enums.Logical;
28  import org.synchronoss.cpo.core.meta.domain.CpoAttribute;
29  import org.synchronoss.cpo.core.meta.domain.CpoClass;
30  
31  /**
32   * Default {@link CpoWhere} implementation: a bindable where-clause node that is also a {@link
33   * Node}, allowing leaf comparisons to be chained into branches via {@link #addWhere(CpoWhere)} and
34   * rendered to native SQL/CQL text via {@link #toString(CpoClass)}.
35   *
36   * @author david berry
37   */
38  public class BindableCpoWhere extends Node implements CpoWhere {
39  
40    /** Version Id for this class. */
41    private static final long serialVersionUID = 1L;
42  
43    static final String[] comparisons = {
44      "=", // COMP_EQ
45      "<", // COMP_LT
46      ">", // COMP_GT
47      "<>", // COMP_NEQ
48      "IN", // COMP_IN
49      "LIKE", // COMP_LIKE
50      "<=", // COMP_LTEQ
51      ">=", // COMP_GTEQ
52      "EXISTS", // COMP_EXISTS
53      "IS NULL" // COMP_ISNULL
54    };
55    static final String[] logicals = {
56      "AND", // LOGIC_AND
57      "OR" // LOGIC_OR
58    };
59  
60    /** The comparison operator applied between the attribute and value. */
61    private Comparison comparison = Comparison.NONE;
62  
63    /** The logical operator joining this clause to the next one added via {@link #addWhere}. */
64    private Logical logical = Logical.NONE;
65  
66    /** The left-hand bean attribute name. */
67    private String attribute = null;
68  
69    /** The right-hand bean attribute name, when comparing two attributes. */
70    private String rightAttribute = null;
71  
72    /** The literal comparison value. */
73    private Object value = null;
74  
75    /** Native function applied to the left-hand attribute before comparison. */
76    private String attributeFunction = null;
77  
78    /** Native function applied to the right-hand attribute before comparison. */
79    private String rightAttributeFunction = null;
80  
81    /** Native function applied to the comparison value before comparison. */
82    private String valueFunction = null;
83  
84    /** Whether the comparison result is negated. */
85    private boolean not = false;
86  
87    /** A literal, unescaped value inserted directly into the native expression. */
88    private String staticValue_ = null;
89  
90    /** The name of this where clause node. */
91    private String name = "__CPO_WHERE__";
92  
93    /**
94     * Creates a leaf where clause comparing the named attribute to a literal value, joined to a
95     * subsequent clause (if any) by the given logical operator.
96     *
97     * @param <T> the type of the comparison value
98     * @param logical the logical operator joining this clause to the next one added
99     * @param attr the name of the bean attribute to compare
100    * @param comp the comparison operator to apply
101    * @param value the value to compare the attribute against
102    */
103   public <T> BindableCpoWhere(Logical logical, String attr, Comparison comp, T value) {
104     setLogical(logical);
105     setAttribute(attr);
106     setComparison(comp);
107     setValue(value);
108   }
109 
110   /**
111    * Creates a leaf where clause comparing the named attribute to a literal value, optionally
112    * negated, joined to a subsequent clause (if any) by the given logical operator.
113    *
114    * @param <T> the type of the comparison value
115    * @param logical the logical operator joining this clause to the next one added
116    * @param attr the name of the bean attribute to compare
117    * @param comp the comparison operator to apply
118    * @param value the value to compare the attribute against
119    * @param not {@code true} to negate the comparison, {@code false} otherwise
120    */
121   public <T> BindableCpoWhere(Logical logical, String attr, Comparison comp, T value, boolean not) {
122     setLogical(logical);
123     setAttribute(attr);
124     setComparison(comp);
125     setValue(value);
126     setNot(not);
127   }
128 
129   /** Creates an empty where clause with no comparison, attribute, or value set. */
130   public BindableCpoWhere() {}
131 
132   /** {@inheritDoc} */
133   @Override
134   public void setComparison(Comparison comparison) {
135     this.comparison = comparison;
136   }
137 
138   /** {@inheritDoc} */
139   @Override
140   public Comparison getComparison() {
141     return this.comparison;
142   }
143 
144   /** {@inheritDoc} */
145   @Override
146   public void setLogical(Logical logical) {
147     this.logical = logical;
148   }
149 
150   /** {@inheritDoc} */
151   @Override
152   public Logical getLogical() {
153     return this.logical;
154   }
155 
156   /** {@inheritDoc} */
157   @Override
158   public void setAttribute(String s) {
159     this.attribute = s;
160   }
161 
162   /** {@inheritDoc} */
163   @Override
164   public String getAttribute() {
165     return this.attribute;
166   }
167 
168   /** {@inheritDoc} */
169   @Override
170   public void setRightAttribute(String s) {
171     this.rightAttribute = s;
172   }
173 
174   /** {@inheritDoc} */
175   @Override
176   public String getRightAttribute() {
177     return this.rightAttribute;
178   }
179 
180   /** {@inheritDoc} */
181   @Override
182   public void setValue(Object s) {
183     this.value = s;
184   }
185 
186   /** {@inheritDoc} */
187   @Override
188   public Object getValue() {
189     return this.value;
190   }
191 
192   /** {@inheritDoc} */
193   @Override
194   public void setStaticValue(String staticValue) {
195     this.staticValue_ = staticValue;
196   }
197 
198   /** {@inheritDoc} */
199   @Override
200   public String getStaticValue() {
201     return this.staticValue_;
202   }
203 
204   /** {@inheritDoc} */
205   @Override
206   public boolean getNot() {
207     return this.not;
208   }
209 
210   /** {@inheritDoc} */
211   @Override
212   public void setNot(boolean b) {
213     this.not = b;
214   }
215 
216   /**
217    * Builds the native (SQL/CQL) fragment for this where clause against the given class's
218    * attribute-to-column mapping. Unlike {@link #toString()}, this resolves attribute names to
219    * datastore column names and does not require the class to declare the attribute (unresolved
220    * names are used verbatim, e.g. as raw column names).
221    *
222    * @param cpoClass the class metadata used to resolve attributes to datastore columns
223    * @return the native expression fragment for this clause
224    */
225   public String toString(CpoClass cpoClass) {
226     StringBuilder sb = new StringBuilder();
227     CpoAttribute cpoAttribute = null;
228 
229     if (getLogical() != Logical.NONE) {
230       sb.append(" ");
231       sb.append(getLogical().operator);
232     } else if (!hasParent()) {
233       // This is the root where clause
234       sb.append("WHERE");
235     }
236 
237     if (getNot()) {
238       sb.append(" NOT");
239     }
240 
241     if (getAttribute() != null) {
242       if (!sb.isEmpty()) {
243         sb.append(" ");
244       }
245       String fullyQualifiedColumn;
246 
247       cpoAttribute = cpoClass.getAttributeJava(getAttribute());
248       if (cpoAttribute == null) {
249         // This is not an attribute on the cpo bean passed to the retrieveBeans method.
250         // treat it as the column name
251         fullyQualifiedColumn = getAttribute();
252       } else {
253         fullyQualifiedColumn = buildColumnName(cpoAttribute);
254       }
255 
256       if (getAttributeFunction() != null) {
257         if (cpoAttribute != null) {
258           sb.append(
259               buildFunction(
260                   getAttributeFunction(), cpoAttribute.getJavaName(), fullyQualifiedColumn));
261         } else {
262           sb.append(getAttributeFunction());
263         }
264       } else {
265         sb.append(fullyQualifiedColumn);
266       }
267     }
268 
269     if (getComparison() != Comparison.NONE) {
270       sb.append(" ");
271       sb.append(getComparison().operator);
272     }
273 
274     if (getComparison() != Comparison.ISNULL
275         && (getValue() != null || getRightAttribute() != null || getStaticValue() != null)) {
276       sb.append(" ");
277 
278       if (getValue() != null) {
279         if (getValueFunction() != null) {
280           if (cpoAttribute == null) {
281             cpoAttribute = cpoClass.getAttributeJava(getRightAttribute());
282           }
283           sb.append(
284               buildFunction(
285                   getValueFunction(),
286                   getAttributeName(cpoAttribute, getAttribute(), getRightAttribute()),
287                   "?"));
288         } else if (getComparison() == Comparison.IN && getValue() instanceof Collection) {
289           Collection coll = (Collection) getValue();
290           sb.append("(");
291           if (coll.size() > 0) {
292             sb.append("?"); // add the parameter, we will bind it later.
293             for (int i = 1; i < coll.size(); i++) {
294               sb.append(", ?"); // add the parameter, we will bind it later.
295             }
296           }
297           sb.append(")");
298         } else {
299           sb.append("?"); // add the parameter, we will bind it later.
300         }
301       } else if (getRightAttribute() != null) {
302         cpoAttribute = cpoClass.getAttributeJava(getRightAttribute());
303         String fullyQualifiedColumn;
304         if (cpoAttribute == null) {
305           fullyQualifiedColumn = getRightAttribute();
306         } else {
307           fullyQualifiedColumn = buildColumnName(cpoAttribute);
308         }
309 
310         if (getRightAttributeFunction() != null) {
311           sb.append(
312               buildFunction(
313                   getRightAttributeFunction(),
314                   getAttributeName(cpoAttribute, getAttribute(), getRightAttribute()),
315                   fullyQualifiedColumn));
316         } else {
317           sb.append(fullyQualifiedColumn);
318         }
319       } else if (getStaticValue() != null) {
320         sb.append(getStaticValue());
321       }
322     }
323     return sb.toString();
324   }
325 
326   private String getAttributeName(
327       CpoAttribute attribute, String leftAttribute, String rightAttribute) {
328     String attrName = null;
329 
330     if (attribute != null) {
331       attrName = attribute.getJavaName();
332     }
333 
334     if (attrName == null && leftAttribute != null) {
335       attrName = leftAttribute;
336     }
337 
338     if (attrName == null && rightAttribute != null) {
339       attrName = rightAttribute;
340     }
341 
342     return attrName;
343   }
344 
345   /**
346    * {@inheritDoc}
347    *
348    * <p>The added clause must itself be a {@link Node} (all built-in {@code CpoWhere}
349    * implementations are); it is attached as a child node of this instance.
350    */
351   @Override
352   public void addWhere(CpoWhere cw) throws CpoException {
353     try {
354       this.addChild((Node) cw);
355     } catch (ChildNodeException cne) {
356       throw new CpoException("Error Adding Where Statement");
357     }
358   }
359 
360   /** {@inheritDoc} */
361   @Override
362   public void setAttributeFunction(String s) {
363     this.attributeFunction = s;
364   }
365 
366   /** {@inheritDoc} */
367   @Override
368   public String getAttributeFunction() {
369     return this.attributeFunction;
370   }
371 
372   /** {@inheritDoc} */
373   @Override
374   public void setValueFunction(String s) {
375     this.valueFunction = s;
376   }
377 
378   /** {@inheritDoc} */
379   @Override
380   public String getValueFunction() {
381     return this.valueFunction;
382   }
383 
384   /** {@inheritDoc} */
385   @Override
386   public void setRightAttributeFunction(String s) {
387     this.rightAttributeFunction = s;
388   }
389 
390   /** {@inheritDoc} */
391   @Override
392   public String getRightAttributeFunction() {
393     return this.rightAttributeFunction;
394   }
395 
396   private String buildFunction(String function, String match, String value) {
397     StringBuilder sb = new StringBuilder();
398     int attrOffset;
399     int fromIndex = 0;
400 
401     if (function != null && !function.isEmpty()) {
402       while ((attrOffset = function.indexOf(match, fromIndex)) != -1) {
403         sb.append(function, fromIndex, attrOffset);
404         sb.append(value);
405         fromIndex = attrOffset + match.length();
406       }
407       sb.append(function.substring(fromIndex));
408     }
409 
410     return sb.toString();
411   }
412 
413   /**
414    * Gets the datastore column name for the given attribute. Subclasses may override this to further
415    * qualify the column name (e.g. with a table alias).
416    *
417    * @param attribute the attribute to resolve
418    * @return the datastore column name for {@code attribute}
419    */
420   protected String buildColumnName(CpoAttribute attribute) {
421     return attribute.getDataName();
422   }
423 
424   /** {@inheritDoc} */
425   @Override
426   public String getName() {
427     return name;
428   }
429 
430   /** {@inheritDoc} */
431   @Override
432   public void setName(String name) {
433     this.name = name;
434   }
435 }