View Javadoc
1   package xuml.tools.model.compiler.runtime.query;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.PrintStream;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Map.Entry;
9   import java.util.concurrent.atomic.AtomicInteger;
10  
11  import javax.persistence.EntityManager;
12  import javax.persistence.TypedQuery;
13  
14  import com.google.common.annotations.VisibleForTesting;
15  import com.google.common.base.Optional;
16  import com.google.common.base.Preconditions;
17  import com.google.common.collect.Maps;
18  
19  import xuml.tools.model.compiler.runtime.Entity;
20  import xuml.tools.model.compiler.runtime.Info;
21  
22  public class SelectBuilder<T extends Entity<T>> {
23  
24      private final BooleanExpression<T> e;
25      private Info info;
26      private Class<T> entityClass;
27      private final AtomicInteger parameterNo = new AtomicInteger(0);
28  
29      public SelectBuilder(BooleanExpression<T> e) {
30          this.e = e;
31      }
32  
33      public static <R extends Entity<R>> SelectBuilder<R> builder(BooleanExpression<R> e) {
34          return new SelectBuilder<R>(e);
35      }
36  
37      public SelectBuilder<T> select(BooleanExpression<T> exp) {
38          return new SelectBuilder<T>(e.and(exp));
39      }
40  
41      public SelectBuilder<T> entityClass(Class<T> cls) {
42          entityClass = cls;
43          return this;
44      }
45  
46      public SelectBuilder<T> info(Info info) {
47          Preconditions.checkNotNull(info,
48                  "thread local Info not available. You need to set the Context EntityManagerFactory before create a SelectBuilder");
49          this.info = info;
50          return this;
51      }
52  
53      /**
54       * Returns either absent or a single item wrapped in an {@link Optional}.
55       * Throws a {@link RuntimeException} if more than one is returned from the
56       * query.
57       * 
58       * @return
59       */
60      public Optional<T> one() {
61          return one(info.getCurrentEntityManager());
62      }
63  
64      /**
65       * Returns either absent or a single item wrapped in an {@link Optional}.
66       * Throws a {@link RuntimeException} if more than one is returned from the
67       * query.
68       * 
69       * @param em
70       * @return
71       */
72      public Optional<T> one(EntityManager em) {
73          List<T> list = many(em);
74          int size = list.size();
75          if (size == 1)
76              return Optional.of(list.get(0));
77          else if (size == 0)
78              return Optional.absent();
79          else
80              throw new RuntimeException("expected 0 or 1 but found " + size);
81      }
82  
83      public Optional<T> any(EntityManager em) {
84          List<T> list = many(em);
85          if (list.size() >= 1)
86              return Optional.of(list.get(0));
87          else
88              return Optional.absent();
89      }
90  
91      public Optional<T> any() {
92          return any(info.getCurrentEntityManager());
93      }
94  
95      public List<T> many() {
96          return many(info.getCurrentEntityManager());
97      }
98  
99      public List<T> many(EntityManager em) {
100         Preconditions.checkNotNull(em, "entity manager is null!");
101         ClauseAndParameters c = getClauseAndParameters();
102         String sql = getSql(entityClass, c.clause);
103         TypedQuery<T> query = em.createQuery(sql, entityClass);
104         for (Entry<String, Object> p : c.parameters.entrySet())
105             query = query.setParameter(p.getKey(), p.getValue());
106         return query.getResultList();
107     }
108 
109     private ClauseAndParameters getClauseAndParameters() {
110         return getClauseAndParameters(e);
111     }
112 
113     @VisibleForTesting
114     static String getSql(Class<?> entityClass, String clause) {
115         String prefix = "select e from " + entityClass.getSimpleName() + " e";
116         String sql;
117         if (clause.length() > 0)
118             sql = prefix + " where " + clause;
119         else
120             sql = prefix;
121         return sql;
122     }
123 
124     @VisibleForTesting
125     String getClause() {
126         return getClauseAndParameters(e).clause;
127     }
128 
129     private ClauseAndParameters getClauseAndParameters(BooleanExpression<T> e) {
130         if (e == null)
131             return new ClauseAndParameters("", new HashMap<String, Object>());
132         Map<String, Object> parameters = Maps.newHashMap();
133         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
134         PrintStream out = new PrintStream(bytes);
135         if (e instanceof Not) {
136             Not<T> not = (Not<T>) e;
137             ClauseAndParameters c = getClauseAndParameters(not.getExpression());
138             parameters.putAll(c.parameters);
139             out.print("!(" + c.clause + ")");
140         } else if (e instanceof NumericComparison) {
141             NumericComparison<T> c = (NumericComparison<T>) e;
142             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
143             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
144             parameters.putAll(c1.parameters);
145             parameters.putAll(c2.parameters);
146             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
147         } else if (e instanceof BinaryBooleanExpression) {
148             BinaryBooleanExpression<T> c = (BinaryBooleanExpression<T>) e;
149             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
150             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
151             parameters.putAll(c1.parameters);
152             parameters.putAll(c2.parameters);
153             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
154         } else if (e instanceof StringComparison) {
155             StringComparison<T> c = (StringComparison<T>) e;
156             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
157             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
158             parameters.putAll(c1.parameters);
159             parameters.putAll(c2.parameters);
160             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
161         } else if (e instanceof DateComparison) {
162             DateComparison<T> c = (DateComparison<T>) e;
163             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
164             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
165             parameters.putAll(c1.parameters);
166             parameters.putAll(c2.parameters);
167             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
168         }
169         out.close();
170         return new ClauseAndParameters(bytes.toString(), parameters);
171     }
172 
173     private String getOperator(DateComparisonOperator op) {
174         if (op == DateComparisonOperator.EQ)
175             return "=";
176         else if (op == DateComparisonOperator.NEQ)
177             return "!=";
178         else if (op == DateComparisonOperator.LT)
179             return "<";
180         else if (op == DateComparisonOperator.GT)
181             return ">";
182         else if (op == DateComparisonOperator.LTE)
183             return "<=";
184         else if (op == DateComparisonOperator.GTE)
185             return ">=";
186         else
187             throw new RuntimeException("not implemented " + op);
188     }
189 
190     private ClauseAndParameters getClauseAndParameters(DateExpression<T> e) {
191         Map<String, Object> parameters = Maps.newHashMap();
192         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
193         PrintStream out = new PrintStream(bytes);
194         if (e instanceof DateConstant) {
195             DateConstant<T> c = (DateConstant<T>) e;
196             addToParameters(parameters, out, c.getValue());
197         } else if (e instanceof IsNullDate) {
198             IsNullDate<T> n = (IsNullDate<T>) e;
199             ClauseAndParameters c = getClauseAndParameters(n.getExpression());
200             out.print(c.clause + " is null");
201         } else if (e instanceof DateExpressionField) {
202             DateExpressionField<T> f = (DateExpressionField<T>) e;
203             out.print("e." + f.getField().getName());
204         }
205         out.close();
206         return new ClauseAndParameters(bytes.toString(), parameters);
207     }
208 
209     private String getOperator(StringComparisonOperator op) {
210         if (op == StringComparisonOperator.EQ)
211             return "=";
212         else if (op == StringComparisonOperator.NEQ)
213             return "!=";
214         else if (op == StringComparisonOperator.GT)
215             return ">";
216         else if (op == StringComparisonOperator.GTE)
217             return ">=";
218         else if (op == StringComparisonOperator.LT)
219             return "<";
220         else if (op == StringComparisonOperator.LTE)
221             return "<=";
222         else if (op == StringComparisonOperator.LIKE)
223             return "like";
224         else
225             throw new RuntimeException("not implemented " + op);
226     }
227 
228     private ClauseAndParameters getClauseAndParameters(StringExpression<T> e) {
229         Map<String, Object> parameters = Maps.newHashMap();
230         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
231         PrintStream out = new PrintStream(bytes);
232         if (e instanceof BinaryStringExpression) {
233             BinaryStringExpression<T> c = (BinaryStringExpression<T>) e;
234             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
235             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
236             parameters.putAll(c1.parameters);
237             parameters.putAll(c2.parameters);
238             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
239         } else if (e instanceof StringConstant) {
240             StringConstant<T> c = (StringConstant<T>) e;
241             addToParameters(parameters, out, c.getValue());
242         } else if (e instanceof IsNullString) {
243             IsNullString<T> n = (IsNullString<T>) e;
244             ClauseAndParameters c = getClauseAndParameters(n.getExpression());
245             out.print(c.clause + " is null");
246         } else if (e instanceof StringExpressionField) {
247             StringExpressionField<T> f = (StringExpressionField<T>) e;
248             out.print("e." + f.getField().getName());
249         }
250         out.close();
251         return new ClauseAndParameters(bytes.toString(), parameters);
252     }
253 
254     private void addToParameters(Map<String, Object> parameters, PrintStream out, Object object) {
255         int index = parameterNo.incrementAndGet();
256         String parameterName = "_p" + index;
257         parameters.put(parameterName, object);
258         out.print(":" + parameterName);
259     }
260 
261     private String getOperator(BinaryStringOperator op) {
262         if (op == BinaryStringOperator.PLUS)
263             return "+";
264         else
265             throw new RuntimeException("not implemented " + op);
266     }
267 
268     private String getOperator(BinaryBooleanOperator op) {
269         if (op == BinaryBooleanOperator.AND)
270             return "and";
271         else if (op == BinaryBooleanOperator.OR)
272             return "or";
273         else
274             throw new RuntimeException("not implemented " + op);
275     }
276 
277     private String getOperator(NumericComparisonOperator c) {
278         String op;
279         if (c == NumericComparisonOperator.EQ)
280             op = "=";
281         else if (c == NumericComparisonOperator.NEQ)
282             op = "!=";
283         else if (c == NumericComparisonOperator.LT)
284             op = "<";
285         else if (c == NumericComparisonOperator.GT)
286             op = ">";
287         else if (c == NumericComparisonOperator.LTE)
288             op = "<=";
289         else if (c == NumericComparisonOperator.GTE)
290             op = ">=";
291         else
292             throw new RuntimeException("unimplemented operator " + c);
293         return op;
294     }
295 
296     private ClauseAndParameters getClauseAndParameters(NumericExpression<T> e) {
297         Map<String, Object> parameters = Maps.newHashMap();
298         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
299         PrintStream out = new PrintStream(bytes);
300         if (e instanceof BinaryNumericExpression) {
301             BinaryNumericExpression<T> c = (BinaryNumericExpression<T>) e;
302             ClauseAndParameters c1 = getClauseAndParameters(c.getExpression1());
303             ClauseAndParameters c2 = getClauseAndParameters(c.getExpression2());
304             parameters.putAll(c1.parameters);
305             parameters.putAll(c2.parameters);
306             out.print("(" + c1.clause + " " + getOperator(c.getOperator()) + " " + c2.clause + ")");
307         } else if (e instanceof NumericConstant) {
308             NumericConstant<T> c = (NumericConstant<T>) e;
309             addToParameters(parameters, out, c.getValue());
310         } else if (e instanceof IsNullNumeric) {
311             IsNullNumeric<T> n = (IsNullNumeric<T>) e;
312             ClauseAndParameters c = getClauseAndParameters(n.getExpression());
313             out.print(c.clause + " is null");
314         } else if (e instanceof NumericExpressionField) {
315             NumericExpressionField<T> f = (NumericExpressionField<T>) e;
316             out.print("e." + f.getField().getName());
317         }
318         out.close();
319         return new ClauseAndParameters(bytes.toString(), parameters);
320     }
321 
322     private String getOperator(BinaryNumericOperator op) {
323 
324         if (op == BinaryNumericOperator.DIVIDE)
325             return "/";
326         else if (op == BinaryNumericOperator.MINUS)
327             return "-";
328         else if (op == BinaryNumericOperator.PLUS)
329             return "+";
330         else if (op == BinaryNumericOperator.TIMES)
331             return "*";
332         else
333             throw new RuntimeException("not implemented " + op);
334     }
335 
336     private static class ClauseAndParameters {
337         String clause;
338         Map<String, Object> parameters = Maps.newHashMap();
339 
340         ClauseAndParameters(String clause, Map<String, Object> parameters) {
341             super();
342             this.clause = clause;
343             this.parameters = parameters;
344         }
345     }
346 
347 }