001/** 002 * Copyright 2018 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 service.tut.pori.apilta.shock; 017 018import java.util.Date; 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.UUID; 024 025import org.apache.commons.lang3.ArrayUtils; 026import org.apache.log4j.Logger; 027import org.springframework.transaction.TransactionStatus; 028import org.springframework.transaction.support.TransactionCallback; 029 030import core.tut.pori.dao.SQLDAO; 031import core.tut.pori.dao.SQLSelectBuilder; 032import core.tut.pori.dao.SQLSelectBuilder.OrderDirection; 033import core.tut.pori.dao.clause.AndClause; 034import core.tut.pori.dao.clause.AndCompareClause; 035import core.tut.pori.dao.clause.AndCompareClause.CompareType; 036import core.tut.pori.dao.clause.AndSubClause; 037import core.tut.pori.dao.clause.JoinClause; 038import core.tut.pori.dao.clause.OrClause; 039import core.tut.pori.dao.clause.OrSubClause; 040import core.tut.pori.dao.clause.SQLClause.SQLType; 041import core.tut.pori.dao.clause.WhereClause; 042import core.tut.pori.http.parameters.DataGroups; 043import core.tut.pori.http.parameters.DateIntervalParameter; 044import core.tut.pori.http.parameters.DateIntervalParameter.Interval; 045import core.tut.pori.http.parameters.Limits; 046import core.tut.pori.users.UserIdentity; 047import service.tut.pori.apilta.shock.datatypes.AccelerometerData; 048import service.tut.pori.apilta.shock.datatypes.LocationData; 049import service.tut.pori.apilta.shock.datatypes.LocationLimits; 050import service.tut.pori.apilta.shock.datatypes.LocationLimits.LatLng; 051import service.tut.pori.apilta.shock.datatypes.ShockMeasurement; 052import service.tut.pori.apilta.shock.datatypes.ShockMeasurementList; 053import service.tut.pori.tasks.datatypes.Task.Visibility; 054 055/** 056 * 057 * 058 */ 059public class ShockDAO extends SQLDAO { 060 private static final Logger LOGGER = Logger.getLogger(ShockDAO.class); 061 /* tables */ 062 private static final String TABLE_SHOCK_ACCELEROMETER_DATA = DATABASE+".shock_accelerometer_data"; 063 private static final String TABLE_SHOCK_LOCATION_DATA = DATABASE+".shock_location_data"; 064 private static final String TABLE_SHOCK_MEASUREMENTS = DATABASE+".shock_measurements"; 065 /* columns */ 066 private static final String COLUMN_DATA_VISIBILITY = "data_visibility"; 067 private static final String COLUMN_LATITUDE = "latitude"; 068 private static final String COLUMN_LONGITUDE = "longitude"; 069 private static final String COLUMN_LEVEL = "level"; 070 private static final String COLUMN_HEADING = "heading"; 071 private static final String COLUMN_SPEED = "speed"; 072 private static final String COLUMN_MEASUREMENT_ID = "measurement_id"; 073 private static final String COLUMN_TIMESTAMP = "timestamp"; 074 private static final String COLUMN_MEASUREMENTS_LEVEL = TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_LEVEL; 075 private static final String COLUMN_MEASUREMENTS_TIMESTAMP = TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_TIMESTAMP; 076 private static final String COLUMN_SYSTEMATIC_ERROR = "systematic_error"; 077 private static final String COLUMN_X_ACCELERATION = "x_acc"; 078 private static final String COLUMN_Y_ACCELERATION = "y_acc"; 079 private static final String COLUMN_Z_ACCELERATION = "z_acc"; 080 081 private static final String[] COLUMNS_GET_MEASUREMENTS = {TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_MEASUREMENT_ID, TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_DATA_VISIBILITY, TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_USER_ID, COLUMN_MEASUREMENTS_TIMESTAMP, COLUMN_MEASUREMENTS_LEVEL}; 082 /* sql strings */ 083 private static final String SQL_INSERT_MEASUREMENT = "INSERT INTO "+TABLE_SHOCK_MEASUREMENTS+" ("+COLUMN_MEASUREMENT_ID+", "+COLUMN_LEVEL+", "+COLUMN_DATA_VISIBILITY+", "+COLUMN_USER_ID+", "+COLUMN_TIMESTAMP+", "+COLUMN_ROW_CREATED+") VALUES (?,?,?,?,?,NOW())"; 084 private static final int[] SQL_INSERT_MEASUREMENT_VALUE_TYPES = {SQLType.STRING.toInt(), SQLType.INTEGER.toInt(), SQLType.INTEGER.toInt(), SQLType.LONG.toInt(), SQLType.TIMESTAMP.toInt()}; 085 086 private static final String SQL_INSERT_ACCELEROMETER_DATA = "INSERT INTO "+TABLE_SHOCK_ACCELEROMETER_DATA+" ("+COLUMN_MEASUREMENT_ID+", "+COLUMN_X_ACCELERATION+", "+COLUMN_Y_ACCELERATION+", "+COLUMN_Z_ACCELERATION+", "+COLUMN_SYSTEMATIC_ERROR+", "+COLUMN_TIMESTAMP+", "+COLUMN_ROW_CREATED+") VALUES (?,?,?,?,?,?,NOW())"; 087 private static final int[] SQL_INSERT_ACCELEROMETER_DATA_VALUE_TYPES = {SQLType.STRING.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.TIMESTAMP.toInt()}; 088 089 private static final String SQL_INSERT_LOCATION_DATA = "INSERT INTO "+TABLE_SHOCK_LOCATION_DATA+" ("+COLUMN_MEASUREMENT_ID+", "+COLUMN_HEADING+", "+COLUMN_LATITUDE+", "+COLUMN_LONGITUDE+", "+COLUMN_SPEED+", "+COLUMN_TIMESTAMP+", "+COLUMN_ROW_CREATED+") VALUES (?,?,?,?,?,?,NOW())"; 090 private static final int[] SQL_INSERT_LOCATION_DATA_VALUE_TYPES = {SQLType.STRING.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.DOUBLE.toInt(), SQLType.TIMESTAMP.toInt()}; 091 092 private static final WhereClause SQL_DATA_VISIBILITY_PUBLIC = new OrClause(COLUMN_DATA_VISIBILITY, Visibility.PUBLIC.toInt(), SQLType.INTEGER); 093 094 private static final String SQL_GET_ACCELEROMETER_DATA = "SELECT "+COLUMN_X_ACCELERATION+", "+COLUMN_Y_ACCELERATION+", "+COLUMN_Z_ACCELERATION+", "+COLUMN_TIMESTAMP+", "+COLUMN_SYSTEMATIC_ERROR+" FROM "+TABLE_SHOCK_ACCELEROMETER_DATA+" WHERE "+COLUMN_MEASUREMENT_ID+"=?"; 095 private static final int[] SQL_MEASUREMENT_ID_VALUE_TYPE = {SQLType.STRING.toInt()}; 096 097 private static final String SQL_GET_LOCATION_DATA = "SELECT "+COLUMN_LATITUDE+", "+COLUMN_LONGITUDE+", "+COLUMN_SPEED+", "+COLUMN_HEADING+", "+COLUMN_TIMESTAMP+" FROM "+TABLE_SHOCK_LOCATION_DATA+" WHERE "+COLUMN_MEASUREMENT_ID+"=?"; 098 099 private static final JoinClause SQL_JOIN_LOCATION_DATA = new JoinClause("LEFT JOIN "+TABLE_SHOCK_LOCATION_DATA+" ON "+TABLE_SHOCK_MEASUREMENTS+"."+COLUMN_MEASUREMENT_ID+"="+TABLE_SHOCK_LOCATION_DATA+"."+COLUMN_MEASUREMENT_ID); 100 private static final AndClause SQL_CLAUSE_LEVEL_NOT_NULL = new AndClause(COLUMN_LEVEL, (Object) null, SQLType.INTEGER).setNot(true); 101 102 103 /** 104 * 105 * @param userIdentity 106 * @param locationLimits 107 * @param dataGroups 108 * @param dateInterval 109 * @param levelFilter 110 * @param limits 111 * @param userIdFilter 112 * @return list of measurements or null if none was found 113 */ 114 public ShockMeasurementList getMeasurements(UserIdentity userIdentity, LocationLimits locationLimits, DataGroups dataGroups, DateIntervalParameter dateInterval, int[] levelFilter, Limits limits, long[] userIdFilter) { 115 SQLSelectBuilder sql = new SQLSelectBuilder(TABLE_SHOCK_MEASUREMENTS); 116 sql.setLimits(limits); 117 sql.addWhereClause(new AndSubClause(new WhereClause[]{new OrClause(COLUMN_USER_ID, userIdentity.getUserId(), SQLType.LONG), SQL_DATA_VISIBILITY_PUBLIC})); 118 sql.addSelectColumns(COLUMNS_GET_MEASUREMENTS); 119 sql.addOrderBy(COLUMN_MEASUREMENTS_TIMESTAMP, OrderDirection.DESCENDING); 120 121 if(dateInterval.hasValues()){ 122 LOGGER.debug("Using date filter..."); 123 AndSubClause asc = new AndSubClause(); 124 for(Interval interval : dateInterval.getValues()){ 125 asc.addWhereClause(new OrSubClause(new AndCompareClause[]{new AndCompareClause(COLUMN_MEASUREMENTS_TIMESTAMP, interval.getStart(), CompareType.GREATER_OR_EQUAL, SQLType.TIMESTAMP), new AndCompareClause(COLUMN_MEASUREMENTS_TIMESTAMP, interval.getEnd(), CompareType.LESS_OR_EQUAL, SQLType.TIMESTAMP)})); 126 } 127 sql.addWhereClause(asc); 128 } 129 130 if(!ArrayUtils.isEmpty(userIdFilter)) { 131 LOGGER.debug("Using user id filter..."); 132 sql.addWhereClause(new AndClause(COLUMN_USER_ID, userIdFilter)); 133 } 134 135 if(!ArrayUtils.isEmpty(levelFilter)) { 136 LOGGER.debug("Using level filter..."); 137 sql.addWhereClause(new AndSubClause(new WhereClause[]{SQL_CLAUSE_LEVEL_NOT_NULL, new AndClause(COLUMN_LEVEL, levelFilter)})); 138 } 139 140 if(locationLimits.hasValues()) { 141 LOGGER.debug("Using location limits filter..."); 142 sql.addJoin(SQL_JOIN_LOCATION_DATA); // note: we could also select all data from location table if location datagroup is given (same for accelerometer data) 143 144 LatLng lowerLeft = locationLimits.getLowerLeft(); // column >= lat, column >= lon 145 sql.addWhereClause(new AndCompareClause(COLUMN_LATITUDE, lowerLeft.getLatitude(), CompareType.GREATER_OR_EQUAL, SQLType.DOUBLE)); 146 sql.addWhereClause(new AndCompareClause(COLUMN_LONGITUDE, lowerLeft.getLongitude(), CompareType.GREATER_OR_EQUAL, SQLType.DOUBLE)); 147 148 LatLng upperRight = locationLimits.getUpperRight(); // column <= lat, column <= lon 149 sql.addWhereClause(new AndCompareClause(COLUMN_LATITUDE, upperRight.getLatitude(), CompareType.LESS_OR_EQUAL, SQLType.DOUBLE)); 150 sql.addWhereClause(new AndCompareClause(COLUMN_LONGITUDE, upperRight.getLongitude(), CompareType.LESS_OR_EQUAL, SQLType.DOUBLE)); 151 } 152 153 List<Map<String, Object>> rows = getJdbcTemplate().queryForList(sql.toSQLString(service.tut.pori.apilta.sensors.datatypes.Definitions.ELEMENT_MEASUREMENT_LIST), sql.getValues(), sql.getValueTypes()); 154 int size = rows.size(); 155 if(size < 1) { 156 LOGGER.debug("No measurements founds."); 157 return null; 158 } 159 160 boolean dataGroupAll = DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups); 161 boolean retrieveLocations = (dataGroupAll || DataGroups.hasDataGroup(Definitions.DATA_GROUP_LOCATION_DATA, dataGroups)); 162 boolean retrieveAccelerometer = (dataGroupAll || DataGroups.hasDataGroup(Definitions.DATA_GROUP_ACCELEROMETER_DATA, dataGroups)); 163 164 LinkedList<ShockMeasurement> measurements = new LinkedList<>(); 165 for(Map<String, Object> row : rows) { 166 measurements.add(extractMeasurement(row, retrieveLocations, retrieveAccelerometer)); 167 } 168 169 return ShockMeasurementList.getShockMeasurementList(measurements); 170 } 171 172 /** 173 * 174 * @param row 175 * @param getLocations 176 * @param getAccelerometer 177 * @return the extracted measurement 178 */ 179 private ShockMeasurement extractMeasurement(Map<String, Object> row, boolean getLocations, boolean getAccelerometer) { 180 ShockMeasurement m = new ShockMeasurement(); 181 Object[] measurementId = new Object[1]; 182 for(Entry<String, Object> e : row.entrySet()) { 183 String column = e.getKey(); 184 switch(column) { 185 case COLUMN_MEASUREMENT_ID: 186 measurementId[0] = e.getValue(); 187 m.setMeasurementId((String) measurementId[0]); 188 break; 189 case COLUMN_DATA_VISIBILITY: 190 m.setVisibility(Visibility.fromInt((int) e.getValue())); 191 break; 192 case COLUMN_USER_ID: 193 m.setUserId(new UserIdentity((Long) e.getValue())); 194 break; 195 case COLUMN_TIMESTAMP: 196 m.setTimestamp((Date) e.getValue()); 197 break; 198 case COLUMN_LEVEL: 199 m.setLevel((Integer) e.getValue()); 200 break; 201 default: 202 if(checkCountColumn(column, e.getValue()) < 1){ // this should be count(*) 203 LOGGER.warn("Unknown column name, or no results, column: "+column); 204 return null; 205 } 206 break; 207 } 208 } 209 210 if(getAccelerometer) { 211 List<Map<String, Object>> rows = getJdbcTemplate().queryForList(SQL_GET_ACCELEROMETER_DATA, measurementId, SQL_MEASUREMENT_ID_VALUE_TYPE); 212 if(rows.isEmpty()) { 213 LOGGER.debug("No accelerometer data for measurement, id: "+measurementId[0]); 214 }else { 215 m.setAccelerometerData(extractAccelerometerData(rows.iterator().next())); 216 } 217 } 218 219 if(getLocations) { 220 List<Map<String, Object>> rows = getJdbcTemplate().queryForList(SQL_GET_LOCATION_DATA, measurementId, SQL_MEASUREMENT_ID_VALUE_TYPE); 221 if(rows.isEmpty()) { 222 LOGGER.debug("No location data for measurement, id: "+measurementId[0]); 223 }else { 224 m.setLocationData(extractLocationData(rows.iterator().next())); 225 } 226 } 227 228 return m; 229 } 230 231 /** 232 * 233 * @param row 234 * @return the extracted data 235 */ 236 private AccelerometerData extractAccelerometerData(Map<String, Object> row) { 237 AccelerometerData aData = new AccelerometerData(); 238 for(Entry<String, Object> e : row.entrySet()) { 239 String column = e.getKey(); 240 switch(column) { 241 case COLUMN_X_ACCELERATION: 242 aData.setxAcceleration((Double) e.getValue()); 243 break; 244 case COLUMN_Y_ACCELERATION: 245 aData.setyAcceleration((Double) e.getValue()); 246 break; 247 case COLUMN_Z_ACCELERATION: 248 aData.setzAcceleration((Double) e.getValue()); 249 break; 250 case COLUMN_TIMESTAMP: 251 aData.setTimestamp((Date) e.getValue()); 252 break; 253 case COLUMN_SYSTEMATIC_ERROR: 254 aData.setSystematicError((Double) e.getValue()); 255 break; 256 case COLUMN_MEASUREMENT_ID: // valid column, but not used in data object 257 break; 258 default: 259 if(checkCountColumn(column, e.getValue()) < 1){ // this should be count(*) 260 LOGGER.warn("Unknown column name, or no results, column: "+column); 261 return null; 262 } 263 break; 264 } 265 } 266 return aData; 267 } 268 269 /** 270 * 271 * @param row 272 * @return the extracted data 273 */ 274 private LocationData extractLocationData(Map<String, Object> row) { 275 LocationData lData = new LocationData(); 276 for(Entry<String, Object> e : row.entrySet()) { 277 String column = e.getKey(); 278 switch(column) { 279 case COLUMN_LATITUDE: 280 lData.setLatitude((Double) e.getValue()); 281 break; 282 case COLUMN_LONGITUDE: 283 lData.setLongitude((Double) e.getValue()); 284 break; 285 case COLUMN_HEADING: 286 lData.setHeading((Double) e.getValue()); 287 break; 288 case COLUMN_SPEED: 289 lData.setSpeed((Double) e.getValue()); 290 break; 291 case COLUMN_TIMESTAMP: 292 lData.setTimestamp((Date) e.getValue()); 293 break; 294 case COLUMN_MEASUREMENT_ID: // valid column, but not not used in the data object 295 break; 296 default: 297 if(checkCountColumn(column, e.getValue()) < 1){ // this should be count(*) 298 LOGGER.warn("Unknown column name, or no results, column: "+column); 299 return null; 300 } 301 break; 302 } 303 } 304 return lData; 305 } 306 307 /** 308 * 309 * @param measurement 310 * @return measurement id for the created measurement 311 */ 312 public String createMeasurement(ShockMeasurement measurement) { 313 return getTransactionTemplate().execute(new TransactionCallback<String>() { 314 315 @Override 316 public String doInTransaction(TransactionStatus status) { 317 String measurementId = UUID.randomUUID().toString(); 318 getJdbcTemplate().update(SQL_INSERT_MEASUREMENT, new Object[]{measurementId, measurement.getLevel(), measurement.getVisibility().toInt(), measurement.getUserId().getUserId(), measurement.getTimestamp()}, SQL_INSERT_MEASUREMENT_VALUE_TYPES); 319 320 AccelerometerData aData = measurement.getAccelerometerData(); 321 if(aData != null){ 322 insertData(measurementId, aData); 323 } 324 325 LocationData lData = measurement.getLocationData(); 326 if(lData != null){ 327 insertData(measurementId, lData); 328 } 329 330 measurement.setMeasurementId(measurementId); 331 return measurementId; 332 } 333 }); 334 } 335 336 /** 337 * 338 * @param measurementId 339 * @param data 340 */ 341 private void insertData(String measurementId, AccelerometerData data) { 342 getJdbcTemplate().update(SQL_INSERT_ACCELEROMETER_DATA, new Object[]{measurementId, data.getxAcceleration(), data.getyAcceleration(), data.getzAcceleration(), data.getSystematicError(), data.getTimestamp()}, SQL_INSERT_ACCELEROMETER_DATA_VALUE_TYPES); 343 } 344 345 /** 346 * 347 * @param measurementId 348 * @param data 349 */ 350 private void insertData(String measurementId, LocationData data) { 351 getJdbcTemplate().update(SQL_INSERT_LOCATION_DATA, new Object[]{measurementId, data.getHeading(), data.getLatitude(), data.getLongitude(), data.getSpeed(), data.getTimestamp()}, SQL_INSERT_LOCATION_DATA_VALUE_TYPES); 352 } 353}