View Javadoc

1   /**
2    * 2009, Digitalis Informatica. All rights reserved. Distribuicao e Gestao de Informatica, Lda. Estrada de Paco de Arcos
3    * num.9 - Piso -1 2780-666 Paco de Arcos Telefone: (351) 21 4408990 Fax: (351) 21 4408999 http://www.digitalis.pt
4    */
5   
6   package pt.digitalis.dif.model.dataset;
7   
8   import java.util.ArrayList;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import pt.digitalis.dif.model.sql.SQLDataSet;
14  import pt.digitalis.dif.utils.ObjectFormatter;
15  import pt.digitalis.utils.common.CollectionUtils;
16  import pt.digitalis.utils.common.IBeanAttributes;
17  
18  /**
19   * Defines a query for a specific data source
20   * 
21   * @author Pedro Viegas <a href="mailto:pviegas@digitalis.pt">pviegas@digitalis.pt</a><br/>
22   * @param <T>
23   *            the data object type
24   * @created 2009/07/31
25   */
26  public class QueryPart<T extends IBeanAttributes> implements IQueryPart<T> {
27  
28      /** Joins not supported message */
29      private static final String JOINS_NOT_SUPPORTED = "Joins not supported";
30  
31      /** if T will allow join. If not will throw unsupported if used */
32      private final boolean allowJoins;
33  
34      /** The data source to apply the query to */
35      protected IDataSet<T> dataSet;
36  
37      /** Query fields */
38      private List<String> fields = new ArrayList<String>();
39  
40      /** Query filters */
41      private List<Filter> filters = new ArrayList<Filter>();
42  
43      /** T if the query should ignore join hierarchies as sub query parts */
44      private final boolean flatMode;
45  
46      /**
47       * The Full hierarchy path of this query part in the main query (for join fields/filters). Ex:
48       * table1.table2.table3.attributeName
49       */
50      private String fullPath = "";
51  
52      /** The inner joins of the query part */
53      private Map<String, Join<T>> joins = new HashMap<String, Join<T>>();
54  
55      /**
56       * The Path of this query part in the main query (for join fields/filters). Ex: table1_table2_table3.attributeName
57       */
58      private String path = "";
59  
60      /**
61       * The prefix for the Path of this query part in the main query (for join fields/filters). Ex:
62       * table1_table2_table3_attributeName
63       */
64      private String prefix = "";
65  
66      /** The main query part */
67      protected Query<T> query;
68  
69      /**
70       * Default constructor
71       * 
72       * @param dataSet
73       * @param query
74       * @param flatMode
75       * @param allowJoins
76       *            if T will allow join. If not will throw unsupported if used
77       */
78      public QueryPart(IDataSet<T> dataSet, Query<T> query, boolean flatMode, boolean allowJoins)
79      {
80          this.dataSet = dataSet;
81          this.query = query;
82          this.flatMode = flatMode;
83          this.allowJoins = allowJoins;
84      }
85  
86      /**
87       * @see pt.digitalis.dif.model.dataset.IQueryPart#addField(java.lang.String)
88       */
89      public Query<T> addField(String fieldName) throws DataSetException
90      {
91          if ("id".equals(fieldName))
92              fieldName = getDataSet().getIDFieldName();
93  
94          if (!getFields().contains(fieldName))
95          {
96              // FlatMode or does not contain path information...
97              if (!flatMode && fieldName != null && fieldName.contains(".")
98              // Is not a Composite key element
99                      && !fieldName.toLowerCase().startsWith("id.")
100                     // Is not a primary key reference (that should already exist in the parent relation entity)
101                     && !((fieldName.split("\\.").length >= 2) && fieldName.indexOf(".id") == fieldName.indexOf('.')))
102             {
103                 QueryPart<T> queryPart = this;
104 
105                 String[] parts = fieldName.split("\\.", 2);
106                 String fullPath = parts[0];
107                 String path = parts[0];
108                 String alias = parts[0];
109 
110                 queryPart = queryPart.getOrAddJoin(path, alias, fullPath, false).getQueryPart();
111 
112                 while (parts[1].contains("."))
113                 {
114                     parts = parts[1].split("\\.", 2);
115 
116                     fullPath += "." + parts[0];
117                     path = path.replace(".", "_") + "." + parts[0];
118                     alias += "_" + parts[0];
119 
120                     queryPart = queryPart.getOrAddJoin(path, alias, fullPath, false).getQueryPart();
121                 }
122 
123             }
124 
125             fields.add(fieldName);
126         }
127 
128         return query;
129     }
130 
131     /**
132      * @see pt.digitalis.dif.model.dataset.IQueryPart#addFields(java.lang.String)
133      */
134     public Query<T> addFields(String fieldNames) throws DataSetException
135     {
136         if (fieldNames != null)
137         {
138             String[] fields = fieldNames.split(",");
139 
140             for (String fieldName: fields)
141                 this.addField(fieldName.trim());
142         }
143 
144         return query;
145     }
146 
147     /**
148      * @see pt.digitalis.dif.model.dataset.IQueryPart#addFilter(pt.digitalis.dif.model.dataset.Filter)
149      */
150     public Query<T> addFilter(Filter filter) throws DataSetException
151     {
152         if (prefix.equals("") && "id".equals(filter.getAttributeName()) && getDataSet().isCompositeID())
153         {
154             String[] fields = getDataSet().getIDFieldName().split(",");
155             String[] values = filter.getValue().split(":");
156 
157             for (int i = 0; i < fields.length; i++)
158                 this.addFilter(new Filter("id." + fields[i], filter.getType(), values[i]));
159 
160             return query;
161         }
162         else
163         {
164             // Will test if the attribute exists for all fields, except inner fields (i.e.: "relation.fieldName") in
165             // HibernateDataSet's
166             if (filter.getType() != FilterType.SQL)
167             {
168                 // Must have the attribute name set
169                 if (filter.getAttributeName() == null)
170                     throw new InvalidFieldName(filter.getAttributeName());
171 
172                 // Since hibernate queries recursively call addFilter with each field part (i.e. field.field2 will
173                 // result in
174                 // two calls: addFilter("field1.field2") -> addFilter("field2)) we cannot validate the attribute name.
175                 if (!(this.getDataSet() instanceof HibernateDataSet))
176                 {
177                     // For the first field on the field path check if it exists in the fieldDefinitions
178                     // Exception: SQLDataSet cannot have field paths (i.e.: field1.field2)
179                     if (this.getDataSet() instanceof SQLDataSet && filter.getAttributeName().contains(".")
180                             || !this.getAttributesDefinition().isEmpty()
181                             && this.getAttributesDefinition().get(filter.getAttributeName().split("\\.")[0]) == null)
182                         throw new InvalidFieldName(filter.getAttributeName());
183                 }
184             }
185 
186             // Will throw an exception if not supported
187             if (!allowJoins && filter.getAttributeName() != null && filter.getAttributeName().contains("."))
188                 AbstractDataSet.throwUnsuportedOperationException(JOINS_NOT_SUPPORTED);
189 
190             if (prefix.equals("") && "id".equals(filter.getAttributeName()) && !getDataSet().isCompositeID())
191                 filter.setAttributeName(getDataSet().getIDFieldName());
192 
193             // FlatMode or does not contain path information...
194             if (!flatMode && filter.getAttributeName() != null
195                     && filter.getAttributeName().contains(".")
196                     // Is not a Composite key element
197                     && !filter.getAttributeName().toLowerCase().startsWith("id.")
198                     // Is not a primary key reference (that should already exist in the parent relation entity)
199                     && !((filter.getAttributeName().split("\\.").length >= 2)
200                             && filter.getAttributeName().indexOf(".id") == filter.getAttributeName().indexOf('.') && getJoins()
201                             .containsKey(filter.getAttributeName().split("\\.")[0])))
202             {
203                 Filter parsedFilter = new Filter(filter.getAttributeName(), filter.getType(), filter.getValue(),
204                         filter.getValue2());
205 
206                 String[] parts = filter.getAttributeName().split("\\.", 2);
207                 parsedFilter.setAttributeName(parts[1]);
208 
209                 String fullPath = parts[0];
210                 String path = parts[0];
211                 String alias = parts[0];
212 
213                 if (!prefix.equals(""))
214                 {
215                     fullPath = this.fullPath + "." + path;
216                     path = prefix + "." + path;
217                     alias = prefix + "_" + alias;
218                 }
219 
220                 getOrAddJoin(path, alias, fullPath, false).getQueryPart().addFilter(parsedFilter);
221             }
222             else
223                 filters.add(filter);
224 
225             return query;
226         }
227     }
228 
229     /**
230      * @see pt.digitalis.dif.model.dataset.IQueryPart#addFilters(java.util.List)
231      */
232     public Query<T> addFilters(List<Filter> filters) throws DataSetException
233     {
234         for (Filter filter: filters)
235             addFilter(filter);
236 
237         return query;
238     }
239 
240     /**
241      * @see pt.digitalis.dif.model.dataset.IQueryPart#addJoin(java.lang.String, pt.digitalis.dif.model.dataset.JoinType)
242      */
243     public Query<T> addJoin(String join, JoinType joinType) throws DataSetException
244     {
245 
246         addJoin(join, joinType, false);
247 
248         return query;
249     }
250 
251     /**
252      * @see pt.digitalis.dif.model.dataset.IQueryPart#addJoin(java.lang.String, pt.digitalis.dif.model.dataset.JoinType,
253      *      boolean)
254      */
255     public Query<T> addJoin(String join, JoinType joinType, boolean eagerJoin) throws DataSetException
256     {
257         QueryPart<? extends IBeanAttributes> queryPart;
258 
259         String[] parts = join.split("\\.", 2);
260         String fullPath = parts[0];
261         String path = parts[0];
262         String alias = parts[0];
263 
264         queryPart = query;
265 
266         if (parts.length > 1)
267         {
268             queryPart = queryPart.getOrAddJoin(path, alias, fullPath, eagerJoin).getQueryPart();
269 
270             while (parts[1].contains("."))
271             {
272                 parts = parts[1].split("\\.", 2);
273 
274                 fullPath += "." + parts[0];
275                 path = path.replace(".", "_") + "." + parts[0];
276                 alias += "_" + parts[0];
277 
278                 queryPart = queryPart.getOrAddJoin(path, alias, fullPath, eagerJoin).getQueryPart();
279             }
280 
281             fullPath += "." + parts[1];
282             path = path.replace(".", "_") + "." + parts[1];
283             alias += "_" + parts[1];
284         }
285 
286         queryPart.getOrAddJoin(path, alias, fullPath, joinType, eagerJoin);
287 
288         return query;
289     }
290 
291     /**
292      * @see pt.digitalis.dif.model.dataset.IQueryPart#between(java.lang.String, java.lang.String, java.lang.String)
293      */
294     public Query<T> between(String attribute, String first, String last) throws DataSetException
295     {
296         this.addFilter(new Filter(attribute, FilterType.BETWEEN, first, last));
297 
298         return query;
299     }
300 
301     /**
302      * @see pt.digitalis.dif.model.dataset.IQueryPart#equals(java.lang.String, java.lang.String)
303      */
304     public Query<T> equals(String attribute, String value) throws DataSetException
305     {
306         this.addFilter(new Filter(attribute, FilterType.EQUALS, value));
307 
308         return query;
309     }
310 
311     /**
312      * @see pt.digitalis.dif.model.dataset.IQueryPart#getAttributesDefinition()
313      */
314     public Map<String, AttributeDefinition> getAttributesDefinition()
315     {
316         return this.getDataSet().getAttributesDefinition();
317     }
318 
319     /**
320      * Inspector for the 'dataSet' attribute.
321      * 
322      * @return the dataSet value
323      */
324     public IDataSet<T> getDataSet()
325     {
326         return dataSet;
327     }
328 
329     /**
330      * @see pt.digitalis.dif.model.dataset.IQueryPart#getFields()
331      */
332     public List<String> getFields()
333     {
334         return fields;
335     }
336 
337     /**
338      * @see pt.digitalis.dif.model.dataset.IQueryPart#getFilters()
339      */
340     public List<Filter> getFilters()
341     {
342         return filters;
343     }
344 
345     /**
346      * @see pt.digitalis.dif.model.dataset.IQueryPart#getFullPath()
347      */
348     public String getFullPath()
349     {
350         return fullPath;
351     }
352 
353     /**
354      * @see pt.digitalis.dif.model.dataset.IQueryPart#getJoins()
355      */
356     public Map<String, Join<T>> getJoins()
357     {
358         return joins;
359     }
360 
361     /**
362      * @param joinName
363      *            the name of the join to get or add to the joins map
364      * @param alias
365      *            the alias for the given join
366      * @param fullPath
367      *            The Full hierarchy path of this query part in the main query (for join fields/filters)
368      * @return the existent or newly created join
369      * @param eager
370      *            if T will join eager join the relation
371      * @throws DataSetException
372      */
373     public Join<T> getOrAddJoin(String joinName, String alias, String fullPath, boolean eager) throws DataSetException
374     {
375         return getOrAddJoin(joinName, alias, fullPath, JoinType.NORMAL, eager);
376     }
377 
378     /**
379      * @param joinName
380      *            the name of the join to get or add to the joins map
381      * @param alias
382      *            the alias for the given join
383      * @param fullPath
384      *            The Full hierarchy path of this query part in the main query (for join fields/filters)
385      * @param joinType
386      *            the join type to use
387      * @return the existent or newly created join
388      * @throws DataSetException
389      */
390     public Join<T> getOrAddJoin(String joinName, String alias, String fullPath, JoinType joinType)
391             throws DataSetException
392     {
393         return getOrAddJoin(joinName, alias, fullPath, joinType, false);
394     }
395 
396     /**
397      * @param joinName
398      *            the name of the join to get or add to the joins map
399      * @param alias
400      *            the alias for the given join
401      * @param fullPath
402      *            The Full hierarchy path of this query part in the main query (for join fields/filters)
403      * @param joinType
404      *            the join type to use
405      * @param eager
406      *            if T will join eager join the relation
407      * @return the existent or newly created join
408      * @throws DataSetException
409      */
410     public Join<T> getOrAddJoin(String joinName, String alias, String fullPath, JoinType joinType, boolean eager)
411             throws DataSetException
412     {
413         if (!allowJoins)
414             AbstractDataSet.throwUnsuportedOperationException(JOINS_NOT_SUPPORTED);
415 
416         Join<T> join = getJoins().get(joinName);
417 
418         if (join == null)
419         {
420             QueryPart<T> joinQueryPart = new QueryPart<T>(getDataSet(), query, flatMode, allowJoins);
421             joinQueryPart.setPath(joinName);
422             joinQueryPart.setPrefix(alias);
423             joinQueryPart.setFullPath(fullPath);
424 
425             join = new Join<T>(joinName, joinType, joinQueryPart);
426             joins.put(joinName, join);
427             join.setEager(eager);
428         }
429         else
430         {
431             if (joinType != JoinType.NORMAL)
432                 join.setJoinType(joinType);
433 
434             if (eager && !join.isEager())
435                 join.setEager(true);
436         }
437 
438         return join;
439     }
440 
441     /**
442      * @see pt.digitalis.dif.model.dataset.IQueryPart#getPath()
443      */
444     public String getPath()
445     {
446         return path;
447     }
448 
449     /**
450      * @see pt.digitalis.dif.model.dataset.IQueryPart#getPrefix()
451      */
452     public String getPrefix()
453     {
454         return prefix;
455     }
456 
457     /**
458      * @see pt.digitalis.dif.model.dataset.IQueryPart#graterOrEqualsThan(java.lang.String, java.lang.String)
459      */
460     public Query<T> graterOrEqualsThan(String attribute, String value) throws DataSetException
461     {
462         this.addFilter(new Filter(attribute, FilterType.GRATER_OR_EQUALS_THAN, value));
463 
464         return query;
465     }
466 
467     /**
468      * @see pt.digitalis.dif.model.dataset.IQueryPart#graterThan(java.lang.String, java.lang.String)
469      */
470     public Query<T> graterThan(String attribute, String value) throws DataSetException
471     {
472         this.addFilter(new Filter(attribute, FilterType.GRATER_THAN, value));
473 
474         return query;
475     }
476 
477     /**
478      * @see pt.digitalis.dif.model.dataset.IQueryPart#in(java.lang.String, java.util.List)
479      */
480     public Query<T> in(String attribute, List<? extends Object> valueList) throws DataSetException
481     {
482         this.addFilter(new Filter(attribute, FilterType.IN, CollectionUtils.listToCommaSeparatedString(valueList)));
483 
484         return query;
485     }
486 
487     /**
488      * @see pt.digitalis.dif.model.dataset.IQueryPart#in(java.lang.String, java.lang.String)
489      */
490     public Query<T> in(String attribute, String valueList) throws DataSetException
491     {
492         this.addFilter(new Filter(attribute, FilterType.IN, valueList));
493 
494         return query;
495     }
496 
497     /**
498      * @see pt.digitalis.dif.model.dataset.IQueryPart#isNotNull(java.lang.String)
499      */
500     public Query<T> isNotNull(String attribute) throws DataSetException
501     {
502         this.addFilter(new Filter(attribute, FilterType.IS_NOT_NULL));
503 
504         return query;
505     }
506 
507     /**
508      * @see pt.digitalis.dif.model.dataset.IQueryPart#isNull(java.lang.String)
509      */
510     public Query<T> isNull(String attribute) throws DataSetException
511     {
512         this.addFilter(new Filter(attribute, FilterType.IS_NULL));
513 
514         return query;
515     }
516 
517     /**
518      * @see pt.digitalis.dif.model.dataset.IQueryPart#lesserOrEqualsThan(java.lang.String, java.lang.String)
519      */
520     public Query<T> lesserOrEqualsThan(String attribute, String value) throws DataSetException
521     {
522         this.addFilter(new Filter(attribute, FilterType.LESSER_OR_EQUALS_THAN, value));
523 
524         return query;
525     }
526 
527     /**
528      * @see pt.digitalis.dif.model.dataset.IQueryPart#lesserThan(java.lang.String, java.lang.String)
529      */
530     public Query<T> lesserThan(String attribute, String value) throws DataSetException
531     {
532         this.addFilter(new Filter(attribute, FilterType.LESSER_THAN, value));
533 
534         return query;
535     }
536 
537     /**
538      * @see pt.digitalis.dif.model.dataset.IQueryPart#like(java.lang.String, java.lang.String)
539      */
540     public Query<T> like(String attribute, String expression) throws DataSetException
541     {
542         this.addFilter(new Filter(attribute, FilterType.LIKE, expression));
543 
544         return query;
545     }
546 
547     /**
548      * @see pt.digitalis.dif.model.dataset.IQueryPart#notBetween(java.lang.String, java.lang.String, java.lang.String)
549      */
550     public Query<T> notBetween(String attribute, String first, String last) throws DataSetException
551     {
552         this.addFilter(new Filter(attribute, FilterType.NOT_BETWEEN, first, last));
553 
554         return query;
555     }
556 
557     /**
558      * @see pt.digitalis.dif.model.dataset.IQueryPart#notEquals(java.lang.String, java.lang.String)
559      */
560     public Query<T> notEquals(String attribute, String value) throws DataSetException
561     {
562         this.addFilter(new Filter(attribute, FilterType.NOT_EQUALS, value));
563 
564         return query;
565     }
566 
567     /**
568      * @see pt.digitalis.dif.model.dataset.IQueryPart#notIn(java.lang.String, java.util.List)
569      */
570     public Query<T> notIn(String attribute, List<Object> valueList) throws DataSetException
571     {
572         this.addFilter(new Filter(attribute, FilterType.NOT_IN, CollectionUtils.listToCommaSeparatedString(valueList)));
573 
574         return query;
575     }
576 
577     /**
578      * @see pt.digitalis.dif.model.dataset.IQueryPart#notIn(java.lang.String, java.lang.String)
579      */
580     public Query<T> notIn(String attribute, String valueList) throws DataSetException
581     {
582         this.addFilter(new Filter(attribute, FilterType.NOT_IN, valueList));
583 
584         return query;
585     }
586 
587     /**
588      * @see pt.digitalis.dif.model.dataset.IQueryPart#notLike(java.lang.String, java.lang.String)
589      */
590     public Query<T> notLike(String attribute, String expression) throws DataSetException
591     {
592         this.addFilter(new Filter(attribute, FilterType.NOT_LIKE, expression));
593 
594         return query;
595     }
596 
597     /**
598      * @see pt.digitalis.dif.model.dataset.IQueryPart#removeFilters()
599      */
600     public Query<T> removeFilters() throws DataSetException
601     {
602         this.filters.clear();
603 
604         for (Join<T> join: this.getJoins().values())
605         {
606             join.getQueryPart().removeFilters();
607         }
608 
609         return query;
610     }
611 
612     /**
613      * @see pt.digitalis.dif.model.dataset.IQueryPart#setFullPath(java.lang.String)
614      */
615     public void setFullPath(String fullPath)
616     {
617         this.fullPath = fullPath;
618     }
619 
620     /**
621      * @see pt.digitalis.dif.model.dataset.IQueryPart#setPath(java.lang.String)
622      */
623     public void setPath(String path)
624     {
625         this.path = path;
626     }
627 
628     /**
629      * @see pt.digitalis.dif.model.dataset.IQueryPart#setPrefix(java.lang.String)
630      */
631     public void setPrefix(String prefix)
632     {
633         this.prefix = prefix;
634     }
635 
636     /**
637      * @see java.lang.Object#toString()
638      */
639     @Override
640     public String toString()
641     {
642         ObjectFormatter formatter = new ObjectFormatter();
643         formatter.addItem("DataSet", getDataSet());
644 
645         formatter.addItem("Prefix", prefix);
646         formatter.addItem("Path", path);
647         formatter.addItem("Fields", getFields());
648         formatter.addItem("Filters", getFilters());
649         formatter.addItem("Joins", getJoins());
650 
651         return formatter.getFormatedObject();
652     }
653 }