001/** 002 * Copyright 2014 Tampere University of Technology, Pori Department 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package core.tut.pori.dao; 017 018import java.util.ArrayList; 019import java.util.HashSet; 020import java.util.Iterator; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map.Entry; 024 025import org.apache.commons.lang3.StringUtils; 026import org.apache.log4j.Logger; 027import org.springframework.dao.DataAccessException; 028import org.springframework.jdbc.core.JdbcTemplate; 029 030import core.tut.pori.dao.clause.JoinClause; 031import core.tut.pori.dao.clause.SQLClause; 032import core.tut.pori.dao.clause.SQLClause.SQLType; 033import core.tut.pori.dao.clause.WhereClause; 034import core.tut.pori.datatypes.IntPrimitiveList; 035import core.tut.pori.http.parameters.Limits; 036 037 038/** 039 * Helper class, which can be used to build an SQL SELECT clause. 040 */ 041public class SQLSelectBuilder { 042 private static final Logger LOGGER = Logger.getLogger(SQLSelectBuilder.class); 043 private static final String SORT_ORDER_RANDOM = " RAND()"; 044 private ArrayList<String> _groupBy = null; 045 private ArrayList<JoinClause> _joins = null; 046 private Limits _limits = null; 047 private LinkedHashMap<String, OrderDirection> _orderBy = null; 048 private HashSet<String> _selectColumns = new HashSet<>(); 049 private String _tableName = null; 050 private ArrayList<WhereClause> _whereClauses = null; 051 052 /** 053 * The direction of an sort order in SQL order by clause. 054 */ 055 public enum OrderDirection{ 056 /** ascending order */ 057 ASCENDING(" ASC"), 058 /** descending order */ 059 DESCENDING(" DESC"); 060 061 private String _value; 062 063 /** 064 * 065 * @param value 066 */ 067 private OrderDirection(String value){ 068 _value = value; 069 } 070 071 /** 072 * 073 * @return order direction as a string 074 */ 075 public String toOrderDirectionString(){ 076 return _value; 077 } 078 } // enum OrderDirection 079 080 /** 081 * 082 * @param table 083 */ 084 public SQLSelectBuilder(String table){ 085 _tableName = table; 086 } 087 088 /** 089 * clear the currently set list of where clauses 090 */ 091 public void clearWhereClauses(){ 092 _whereClauses = null; 093 } 094 095 /** 096 * 097 * @return number of added where clauses 098 */ 099 public int getWhereClauseCount() { 100 return (_whereClauses == null ? 0 : _whereClauses.size()); 101 } 102 103 /** 104 * 105 * @param clause 106 */ 107 public void addJoin(JoinClause clause){ 108 if(_joins == null){ 109 _joins = new ArrayList<>(); 110 } 111 _joins.add(clause); 112 } 113 114 /** 115 * 116 * @param column 117 */ 118 public void addGroupBy(String column){ 119 if(_groupBy == null){ 120 _groupBy = new ArrayList<>(); 121 } 122 _groupBy.add(column); 123 } 124 125 /** 126 * @param startItem the startItem to set, negative value disables limit 127 */ 128 public void setStartItem(int startItem) { 129 if(_limits == null){ 130 _limits = new Limits(startItem, Limits.DEFAULT_MAX_ITEMS); 131 }else{ 132 _limits.setTypeLimits(startItem, _limits.getEndItem(), null); 133 } 134 } 135 136 /** 137 * @param maxItems the maxItems to set, negative value disables limit 138 */ 139 public void setMaxItems(int maxItems) { 140 if(_limits == null){ 141 _limits = new Limits(0, maxItems-1); 142 }else{ 143 _limits.setTypeLimits(_limits.getStartItem(), maxItems-1, null); 144 } 145 } 146 147 /** 148 * 149 * @param limits if null, this is a no-op 150 */ 151 public void setLimits(Limits limits){ 152 _limits = limits; 153 } 154 155 /** 156 * 157 * @param template to use for retrieval 158 * @return record count for the given sql query or < 0 on error 159 */ 160 public long getRecordCount(JdbcTemplate template){ 161 return getRecordCount(template, null); 162 } 163 164 /** 165 * 166 * @param template 167 * @param type 168 * @return record count (SQL COUNT(*)) for the given select query parameters 169 */ 170 public long getRecordCount(JdbcTemplate template, String type){ 171 try{ 172 Long count = template.queryForObject(toSQLString(true, type), getValues(), getValueTypes(), Long.class); 173 return (count == null ? 0 : count); 174 } catch (DataAccessException ex){ 175 LOGGER.error(ex, ex); 176 return -1; 177 } 178 } 179 180 /** 181 * 182 * @return value types or null if none 183 */ 184 public int[] getValueTypes(){ 185 return getValueTypes(_whereClauses); 186 } 187 188 /** 189 * 190 * @param clauses 191 * @return value types or null if none 192 */ 193 protected static int[] getValueTypes(List<? extends SQLClause> clauses){ 194 if(clauses == null || clauses.isEmpty()){ 195 return null; 196 } 197 198 IntPrimitiveList list = new IntPrimitiveList(); 199 for(Iterator<? extends SQLClause> iter = clauses.iterator();iter.hasNext();){ 200 SQLClause c = iter.next(); 201 SQLType[] t = c.getValueTypes(); 202 if(t != null){ 203 for(int i=0;i<t.length;++i){ 204 list.add(t[i].toInt()); 205 } 206 } 207 } 208 return list.toArray(); 209 } 210 211 /** 212 * 213 * @return values in an array or null if none 214 */ 215 public Object[] getValues(){ 216 return getValues(_whereClauses); 217 } 218 219 /** 220 * 221 * @param clauses 222 * @return array of values or null if none 223 */ 224 protected static Object[] getValues(List<? extends SQLClause> clauses){ 225 if(clauses == null || clauses.isEmpty()){ 226 return null; 227 } 228 229 List<Object> objects = new ArrayList<>(); 230 for(Iterator<? extends SQLClause> iter = clauses.iterator();iter.hasNext();){ 231 SQLClause c = iter.next(); 232 Object[] o = c.getValues(); 233 if(o != null){ 234 for(int i=0;i<o.length;++i){ 235 objects.add(o[i]); 236 } 237 } 238 } 239 if(objects.isEmpty()){ 240 return null; 241 }else{ 242 return objects.toArray(); 243 } 244 } 245 246 /** 247 * 248 * @return the query as a SQL string 249 */ 250 public String toSQLString(){ 251 return toSQLString(false, null); 252 } 253 254 /** 255 * 256 * @param type 257 * @return the query as a SQL string using type information for parameters (e.g. Limits). See {@link core.tut.pori.http.parameters.Limits} 258 */ 259 public String toSQLString(String type){ 260 return toSQLString(false, type); 261 } 262 263 /** 264 * helper method for generating SQL query string 265 * 266 * @param onlyCount 267 * @param type 268 * @return the query as a SQL string 269 */ 270 private String toSQLString(boolean onlyCount, String type){ 271 StringBuilder sql = new StringBuilder("SELECT "); 272 273 /* create select columns */ 274 if(onlyCount){ 275 sql.append("COUNT (*) FROM "); 276 }else if(_selectColumns.isEmpty()){ 277 sql.append("* FROM "); 278 }else{ 279 Iterator<String> iter = _selectColumns.iterator(); 280 sql.append(iter.next()); 281 while(iter.hasNext()){ 282 sql.append(','); 283 sql.append(iter.next()); 284 } 285 sql.append(" FROM "); 286 } 287 sql.append(_tableName); 288 289 /* create joins */ 290 if(_joins != null){ 291 for(Iterator<JoinClause> iter = _joins.iterator();iter.hasNext();){ 292 iter.next().toSQLString(sql); 293 } 294 } 295 296 /* create where clauses */ 297 if(_whereClauses != null){ 298 sql.append(" WHERE "); 299 Iterator<WhereClause> iter = _whereClauses.iterator(); 300 iter.next().toSQLString(sql); 301 while(iter.hasNext()){ 302 WhereClause clause = iter.next(); 303 sql.append(clause.getClauseType().toClauseString()); 304 clause.toSQLString(sql); 305 } 306 } 307 308 /* create group by */ 309 if(_groupBy != null){ 310 sql.append(" GROUP BY "); 311 Iterator<String> gIter = _groupBy.iterator(); 312 sql.append(gIter.next()); 313 while(gIter.hasNext()){ 314 sql.append(','); 315 sql.append(gIter.next()); 316 } 317 } 318 319 /* create order by */ 320 if(_orderBy != null){ 321 sql.append(" ORDER BY "); 322 for(Entry<String, OrderDirection> e : _orderBy.entrySet()){ 323 String column = e.getKey(); 324 if(StringUtils.isBlank(column)){ 325 sql.append(SORT_ORDER_RANDOM); 326 }else{ 327 sql.append(column); 328 sql.append(e.getValue().toOrderDirectionString()); 329 } 330 sql.append(','); 331 } 332 sql.setLength(sql.length()-1); // chop the tailing , 333 } 334 335 /* create limit */ 336 if(_limits != null){ 337 sql.append(" LIMIT "); 338 sql.append(_limits.getStartItem(type)); 339 sql.append(','); 340 sql.append(_limits.getMaxItems()); 341 } 342 343 return sql.toString(); 344 } 345 346 /** 347 * 348 * @param column null column name is accepted, though it equals to random sort order 349 * @param direction 350 */ 351 public void addOrderBy(String column, OrderDirection direction){ 352 if(_orderBy == null){ 353 _orderBy = new LinkedHashMap<>(); 354 } 355 _orderBy.put(column,direction); 356 } 357 358 /** 359 * 360 * @param clause 361 */ 362 public void addWhereClause(WhereClause clause){ 363 if(_whereClauses == null){ 364 _whereClauses = new ArrayList<>(); 365 } 366 _whereClauses.add(clause); 367 } 368 369 /** 370 * 371 * @param column 372 */ 373 public void addSelectColumn(String column){ 374 _selectColumns.add(column); 375 } 376 377 /** 378 * 379 * @param columns 380 */ 381 public void addSelectColumns(String[] columns){ 382 for(int i=0;i<columns.length;++i){ 383 _selectColumns.add(columns[i]); 384 } 385 } 386}