001/* 002 * JKNIV, whinstone one contract to access your database. 003 * 004 * Copyright (C) 2017, the original author or authors. 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software Foundation, Inc., 018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 019 */ 020package net.sf.jkniv.whinstone.jpa2.statement; 021 022import java.sql.ResultSet; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import javax.persistence.Query; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import net.sf.jkniv.exception.HandleableException; 035import net.sf.jkniv.experimental.TimerKeeper; 036import net.sf.jkniv.reflect.NumberFactory; 037import net.sf.jkniv.reflect.Numerical; 038import net.sf.jkniv.reflect.beans.ObjectProxy; 039import net.sf.jkniv.reflect.beans.ObjectProxyFactory; 040import net.sf.jkniv.reflect.beans.PropertyAccess; 041import net.sf.jkniv.sqlegance.LanguageType; 042import net.sf.jkniv.sqlegance.RepositoryException; 043import net.sf.jkniv.sqlegance.dialect.SqlDialect; 044import net.sf.jkniv.sqlegance.dialect.SqlFeatureSupport; 045import net.sf.jkniv.sqlegance.logger.DataMasking; 046import net.sf.jkniv.sqlegance.statement.ColumnParserFactory; 047import net.sf.jkniv.whinstone.Param; 048import net.sf.jkniv.whinstone.Queryable; 049import net.sf.jkniv.whinstone.ResultRow; 050import net.sf.jkniv.whinstone.classification.Groupable; 051import net.sf.jkniv.whinstone.classification.GroupingBy; 052import net.sf.jkniv.whinstone.classification.Transformable.TransformableType; 053import net.sf.jkniv.whinstone.statement.AutoKey; 054import net.sf.jkniv.whinstone.statement.StatementAdapter; 055import net.sf.jkniv.whinstone.types.Convertible; 056import net.sf.jkniv.whinstone.types.RegisterType; 057import net.sf.jkniv.whinstone.types.NoConverterType; 058 059public class JpaStatementAdapter<T, R> implements StatementAdapter<T, ResultSet> 060{ 061 private static final Logger LOG = LoggerFactory.getLogger(JpaStatementAdapter.class); 062 private static final Logger SQLLOG = net.sf.jkniv.whinstone.jpa2.LoggerFactory.getLogger(); 063 private static final DataMasking MASKING = net.sf.jkniv.whinstone.jpa2.LoggerFactory.getDataMasking(); 064 private final Query query; 065 private int index; 066 private final HandleableException handlerException; 067 private Queryable queryable; 068 private boolean scalar; 069 070 public JpaStatementAdapter(Query query, Queryable queryable, HandleableException handlerException) 071 { 072 this.query = query; 073 this.queryable = queryable; 074 this.handlerException = handlerException; 075 this.reset(); 076 } 077 078 @Override 079 public StatementAdapter<T, ResultSet> bind(String name, Object value) 080 { 081 log(new Param(value, name, index)); 082 //Convertible<Object,Object> convertible = getConverter(new PropertyAccess(name)); 083 query.setParameter(++index, value); 084 return this; 085 } 086 087 @Override 088 public StatementAdapter<T, ResultSet> bind(Param... values) 089 { 090 for (int j=0; j < values.length; j++) 091 { 092 Param param = values[j]; 093 log(param); 094 query.setParameter(++index, param.getValueAs()); 095 } 096 return this; 097 } 098 099 @Override 100 public int reset() 101 { 102 int before = index; 103 index = 0; 104 return before; 105 } 106 107 @Override 108 public StatementAdapter<T, ResultSet> bind(Param value) 109 { 110 log(value); 111 query.setParameter(++index, value.getValue()); 112 return this; 113 } 114 115 @Override 116 public StatementAdapter<T, ResultSet> with(ResultRow<T, ResultSet> resultRow) 117 { 118 //this.resultRow = resultRow; 119 return this; 120 } 121 122 @Override 123 public void bindKey() 124 { 125 // FIXME auto-key forJPA has, How is behavior with NATIVE query 126 // String[] properties = queryable.getDynamicSql().asInsertable().getAutoGeneratedKey().getPropertiesAsArray(); 127 // ObjectProxy<?> proxy = ObjectProxyFactory.newProxy(queryable.getParams()); 128 // Iterator<Object> it = autoKey.iterator(); 129 // for(int i=0; i<properties.length; i++) 130 // setValueOfKey(proxy, properties[i], it.next()); 131 } 132 133 @Override 134 public StatementAdapter<T, ResultSet> with(AutoKey generateKey) 135 { 136 // FIXME auto-key for JPA native , How is behavior with NATIVE query 137 return this; 138 } 139 140 /* 141 @Override 142 public List<T> rows() 143 { 144 //ResultSet rs = null; 145 //ResultSetParser<T, ResultSet> rsParser = null; 146 Groupable<T, ?> grouping = new NoGroupingBy<T, T>(); 147 List<T> list = Collections.emptyList(); 148 try 149 { 150 TimerKeeper.start(); 151 rs = query.getResultList(); 152 if (queryable != null) 153 queryable.getDynamicSql().getStats().add(TimerKeeper.clear()); 154 155 JdbcColumn<ResultSet>[] columns = getJdbcColumns(rs.getMetaData()); 156 setResultRow(columns); 157 158 Transformable<T> transformable = resultRow.getTransformable(); 159 if (!groupingBy.isEmpty()) 160 { 161 grouping = new GroupingBy(groupingBy, queryable.getReturnType(), transformable); 162 } 163 rsParser = new ObjectResultSetParser(resultRow, grouping); 164 list = rsParser.parser(rs); 165 } 166 catch (SQLException e) 167 { 168 if (queryable != null) 169 queryable.getDynamicSql().getStats().add(e); 170 handlerException.handle(e, e.getMessage()); 171 } 172 return list; 173 } 174 */ 175 176 @Override 177 @SuppressWarnings("unchecked") 178 public List<T> rows() 179 { 180 List<T> list = Collections.emptyList(); 181 try 182 { 183 TimerKeeper.start(); 184 if (queryable.isPaging()) 185 { 186 SqlDialect sqlDialect = queryable.getDynamicSql().getSqlDialect(); 187 if (sqlDialect.supportsFeature(SqlFeatureSupport.LIMIT)) 188 query.setMaxResults(queryable.getMax()); 189 if (sqlDialect.supportsFeature(SqlFeatureSupport.LIMIT_OFF_SET)) 190 query.setFirstResult(queryable.getOffset()); 191 } 192 TimerKeeper.start(); 193 list = query.getResultList(); 194 queryable.getDynamicSql().getStats().add(TimerKeeper.clear()); 195 196 if (queryable.getDynamicSql().getLanguageType() == LanguageType.NATIVE 197 && queryable.getDynamicSql().hasReturnType() && list.size() > 0) 198 { 199 list = cast((List<Object[]>) list, queryable.getDynamicSql().getReturnTypeAsClass()); 200 } 201 int totalBeforeGroup = list.size(); 202 203 list = handleGroupingBy(list); 204 if (LOG.isDebugEnabled()) 205 LOG.debug("Executed [{}] query, {}/{} rows fetched transformed to -> {}", queryable.getName(), 206 totalBeforeGroup, queryable.getTotal(), list.size()); 207 } 208 catch (Exception e) 209 { 210 queryable.getDynamicSql().getStats().add(e); 211 handlerException.handle(e); 212 } 213 finally { 214 TimerKeeper.clear(); 215 } 216 217 return list; 218 } 219 220 @SuppressWarnings("unchecked") 221 private List<T> handleGroupingBy(List<T> list) 222 { 223 List<T> newList = Collections.emptyList(); 224 List<String> groupingBy = Collections.emptyList(); 225 groupingBy = queryable.getDynamicSql().asSelectable().getGroupByAsList(); 226 if (!list.isEmpty() && !groupingBy.isEmpty()) 227 { 228 Class<T> returnedType = (Class<T>) list.get(0).getClass(); 229 Groupable<T, T> grouping = new GroupingBy<T, T>(groupingBy, returnedType, TransformableType.OBJECT); 230 for (T row : list) 231 grouping.classifier(row); 232 233 newList = grouping.asList(); 234 } 235 else 236 newList = list; 237 return newList; 238 } 239 240 /** 241 * Make the conversion from object list to another T 242 * @param list list of original values 243 * @param returnType class type of {@code T} 244 * @param <T> class type 245 * @return list of casted objects 246 */ 247 @SuppressWarnings("unchecked") 248 private List<T> cast(List<?> list, Class<?> returnType)// TODO test me case when jpa return array of objects (native query or select specific columns 249 { 250 List<T> castedList = null; 251 Object firstValue = list.get(0); 252 if (firstValue.getClass().getName().equals(returnType) || returnType == null) 253 { 254 castedList = (List<T>)list; 255 } 256 else if (firstValue instanceof Number) 257 { 258 Numerical factory = NumberFactory.getInstance(returnType.getName()); 259 castedList = new ArrayList<T>(list.size()); 260 List<?> listArray = (List<?>) list; 261 for (Object o : listArray) 262 { 263 T casted = (T) factory.valueOf(o); 264 castedList.add(casted); 265 } 266 } 267 else if (firstValue instanceof String) 268 { 269 castedList = new ArrayList<T>(list.size()); 270 List<?> listArray = (List<?>) list; 271 for (Object o : listArray) 272 { 273 castedList.add((T)String.valueOf(o)); 274 } 275 } 276 // else if(BASIC_TYPE.isBasicType(firstValue.getClass())) 277 // { 278 // castedList = new ArrayList<T>(list.size()); 279 // List<?> listArray = (List<?>)list; 280 // for (Object o : listArray) 281 // { 282 // ObjectProxy<T> proxy = ObjectProxyFactory.newProxy(returnType); 283 // proxy.setConstructorArgs(o); 284 // T casted = proxy.newInstance(); 285 // castedList.add(casted); 286 // } 287 // } 288 else if (Map.class.isAssignableFrom(returnType)) 289 { 290 // TODO check match between columns size and object array 291 String[] columns = ColumnParserFactory.getInstance().extract(queryable.query()); 292 castedList = new ArrayList<T>(list.size()); 293 List<Object[]> listArray = (List<Object[]>) list; 294 for (Object[] tupla : listArray) 295 { 296 Map<String, Object> map = new HashMap<String, Object>(); 297 for(int i=0; i<tupla.length; i++) 298 map.put(columns[i], tupla[i]); 299 300 castedList.add((T)map); 301 } 302 } 303 else if (returnType != null) 304 { 305 castedList = new ArrayList<T>(list.size()); 306 List<Object[]> listArray = (List<Object[]>) list; 307 for (Object[] o : listArray) 308 { 309 ObjectProxy<?> proxy = ObjectProxyFactory.of(returnType); 310 proxy.setConstructorArgs(o); 311 T casted = (T)proxy.newInstance(); 312 castedList.add(casted); 313 } 314 } 315 if (castedList.size() != list.size()) 316 throw new RepositoryException("Wrong conversion type from List<Object[]> to List of [" + returnType + "]"); 317 return castedList; 318 } 319 320 /* 321 @Override 322 public void batch() 323 { 324 // TODO batch adapter 325 } 326 */ 327 @Override 328 public int execute() 329 { 330 int ret = 0; 331 try 332 { 333 TimerKeeper.start(); 334 ret = query.executeUpdate(); 335 queryable.getDynamicSql().getStats().add(TimerKeeper.clear()); 336 } 337 catch (Exception e) 338 { 339 queryable.getDynamicSql().getStats().add(e); 340 handlerException.handle(e, e.getMessage()); 341 } 342 return ret; 343 } 344 345 @Override 346 public void close() 347 { 348 // TODO implements close for JpaStatementAdapter 349 LOG.warn("close Statement Adapter not implemented for RepositoryJpa!"); 350 } 351 352 @Override 353 public void setFetchSize(int rows) 354 { 355 query.setMaxResults(rows); 356 } 357 358 private void log(Param param) 359 { 360 if (SQLLOG.isDebugEnabled()) 361 SQLLOG.debug("Setting SQL Parameter from index [{}] with name [{}] with value of [{}] type of [{}]", index, 362 param.getName(), MASKING.mask(param.getName(), param.getValue()), (param.getValue() == null ? "NULL" : param.getValue().getClass())); 363 } 364 365 private void log(int position, Object value) 366 { 367 String name = String.valueOf(position); 368 if (SQLLOG.isDebugEnabled()) 369 SQLLOG.debug("Setting SQL Parameter from index [{}] with name [{}] with value of [{}] type of [{}]", index, 370 name, MASKING.mask(name, value), (value == null ? "NULL" : value.getClass())); 371 } 372 373 private boolean hasGroupBy() 374 { 375 return !queryable.getDynamicSql().asSelectable().getGroupByAsList().isEmpty(); 376 } 377}