001/** 002 * Copyright 2016 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.sensors; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.Set; 025import java.util.UUID; 026 027import org.apache.commons.lang3.ArrayUtils; 028import org.apache.log4j.Logger; 029import org.springframework.beans.factory.annotation.Autowired; 030import org.springframework.jdbc.core.JdbcTemplate; 031import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 032import org.springframework.transaction.TransactionStatus; 033import org.springframework.transaction.support.TransactionCallback; 034 035import service.tut.pori.apilta.sensors.datatypes.Measurement; 036import service.tut.pori.apilta.sensors.datatypes.MeasurementList; 037import core.tut.pori.dao.SQLDAO; 038import core.tut.pori.dao.SQLDeleteBuilder; 039import core.tut.pori.dao.SQLSelectBuilder; 040import core.tut.pori.dao.SQLSelectBuilder.OrderDirection; 041import core.tut.pori.dao.clause.AndClause; 042import core.tut.pori.dao.clause.AndCompareClause; 043import core.tut.pori.dao.clause.AndCompareClause.CompareType; 044import core.tut.pori.dao.clause.AndSubClause; 045import core.tut.pori.dao.clause.JoinClause; 046import core.tut.pori.dao.clause.OrSubClause; 047import core.tut.pori.dao.clause.SQLClause.SQLType; 048import core.tut.pori.http.parameters.DataGroups; 049import core.tut.pori.http.parameters.DateIntervalParameter.Interval; 050import core.tut.pori.http.parameters.Limits; 051 052/** 053 * dao for accessing sensor data 054 * 055 */ 056public class SensorsDAO extends SQLDAO { 057 private static final Logger LOGGER = Logger.getLogger(SensorsDAO.class); 058 /* tables */ 059 private static final String TABLE_MEASUREMENTS = DATABASE+".measurements"; 060 private static final String TABLE_MEASUREMENTS_FILES = DATABASE+".measurements_files"; 061 private static final String TABLE_MEASUREMENTS_TASKS = DATABASE+".measurements_tasks"; 062 /* columns */ 063 private static final String COLUMN_BACKEND_ID = "backend_id"; 064 private static final String COLUMN_MEASUREMENT_ID = "measurement_id"; 065 private static final String COLUMN_TABLE_MEASUREMENTS_ROW_CREATED = TABLE_MEASUREMENTS+"."+COLUMN_ROW_CREATED; 066 private static final String COLUMN_TASK_ID_WITH_TABLE_NAME = TABLE_MEASUREMENTS_TASKS+"."+Definitions.COLUMN_TASK_ID; 067 /* sql */ 068 private static final String SQL_BACKEND_HAS_GUID = "SELECT "+COLUMN_COUNT+" FROM "+TABLE_MEASUREMENTS_FILES+" WHERE "+COLUMN_BACKEND_ID+"=? AND "+COLUMN_GUID+"=? LIMIT 1"; 069 private static final int[] SQL_BACKEND_HAS_GUID_SQL_TYPES = {SQLType.LONG.toInt(), SQLType.STRING.toInt()}; 070 private static final String[] SQL_COLUMNS_GET_MEASUREMENTS = {TABLE_MEASUREMENTS+"."+COLUMN_MEASUREMENT_ID, TABLE_MEASUREMENTS+"."+COLUMN_BACKEND_ID}; // also used in the where filters of getMeasurements(), check before making modifications 071 private static final String[] SQL_COLUMNS_INSERT_MEASUREMENT = {COLUMN_BACKEND_ID, COLUMN_MEASUREMENT_ID, COLUMN_ROW_CREATED}; 072 private static final String SQL_DELETE_FILES = "DELETE FROM "+TABLE_MEASUREMENTS_FILES+" WHERE "+COLUMN_BACKEND_ID+"=?"; 073 private static final String SQL_DELETE_MEASUREMENTS = "DELETE FROM "+TABLE_MEASUREMENTS+" WHERE "+COLUMN_BACKEND_ID+"=?"; 074 private static final String SQL_GET_BACKEND_ID = "SELECT "+COLUMN_BACKEND_ID+", "+COLUMN_COUNT+" FROM "+TABLE_MEASUREMENTS_FILES+" WHERE "+COLUMN_GUID+"=? LIMIT 1"; 075 private static final int[] SQL_GET_BACKEND_ID_SQL_TYPES = {SQLType.STRING.toInt()}; 076 private static final String SQL_GET_FILE_GUIDS = "SELECT "+COLUMN_GUID+" FROM "+TABLE_MEASUREMENTS_FILES+" WHERE "+COLUMN_BACKEND_ID+"=?"; 077 private static final String SQL_GET_MEASUREMENT_IDS = "SELECT "+COLUMN_MEASUREMENT_ID+" FROM "+TABLE_MEASUREMENTS+" WHERE "+COLUMN_BACKEND_ID+"=?"; 078 private static final String SQL_INSERT_FILE = "INSERT INTO "+TABLE_MEASUREMENTS_FILES+" ("+COLUMN_BACKEND_ID+", "+COLUMN_GUID+", "+COLUMN_ROW_CREATED+") VALUES (?,?,NOW())"; 079 private static final int[] SQL_INSERT_FILE_SQL_TYPES = {SQLType.LONG.toInt(), SQLType.STRING.toInt()}; 080 private static final String SQL_INSERT_MEASUREMENT_TASK = "INSERT INTO "+TABLE_MEASUREMENTS_TASKS+" ("+COLUMN_MEASUREMENT_ID+", "+Definitions.COLUMN_TASK_ID+", "+COLUMN_ROW_CREATED+") VALUES (?,?,NOW())"; 081 private static final int[] SQL_INSERT_MEASUREMENT_TASK_SQL_TYPES = {SQLType.STRING.toInt(), SQLType.STRING.toInt()}; 082 private static final JoinClause SQL_JOIN_TASKS = new JoinClause("INNER JOIN "+TABLE_MEASUREMENTS_TASKS+" ON "+TABLE_MEASUREMENTS+"."+COLUMN_MEASUREMENT_ID+"="+TABLE_MEASUREMENTS_TASKS+"."+COLUMN_MEASUREMENT_ID); 083 private static final int[] SQL_TYPE_BACKEND_ID = {SQLType.LONG.toInt()}; 084 @Autowired 085 private DataPointDAO _dataPointDAO = null; 086 087 /** 088 * 089 * @param backendIdFilter optional filter 090 * @param createdFilter optional filter, this is targeted to the data points (if requested) 091 * @param dataGroups 092 * @param limits 093 * @param measurementIdFilter optional filter 094 * @param taskIds 095 * @return list of measurements or null if none was found 096 */ 097 public MeasurementList getMeasurements(long[] backendIdFilter, Set<Interval> createdFilter, DataGroups dataGroups, Limits limits, List<String> measurementIdFilter, List<String> taskIds) { 098 SQLSelectBuilder sql = new SQLSelectBuilder(TABLE_MEASUREMENTS); 099 sql.addSelectColumns(SQL_COLUMNS_GET_MEASUREMENTS); 100 sql.addOrderBy(COLUMN_TABLE_MEASUREMENTS_ROW_CREATED, OrderDirection.DESCENDING); 101 sql.setLimits(limits); 102 103 sql.addWhereClause(new AndClause(COLUMN_TASK_ID_WITH_TABLE_NAME, taskIds, SQLType.STRING)); 104 sql.addJoin(SQL_JOIN_TASKS); 105 106 if(!ArrayUtils.isEmpty(backendIdFilter)){ 107 LOGGER.debug("Using back end id filter..."); 108 sql.addWhereClause(new AndClause(SQL_COLUMNS_GET_MEASUREMENTS[1], backendIdFilter)); 109 } 110 111 if(measurementIdFilter != null && !measurementIdFilter.isEmpty()){ 112 LOGGER.debug("Using measurement id filter..."); 113 sql.addWhereClause(new AndClause(SQL_COLUMNS_GET_MEASUREMENTS[0], measurementIdFilter, SQLType.STRING)); 114 } 115 116 if(createdFilter != null && !createdFilter.isEmpty()){ 117 LOGGER.debug("Using created filter..."); 118 AndSubClause asc = new AndSubClause(); 119 for(Interval interval : createdFilter){ 120 asc.addWhereClause(new OrSubClause(new AndCompareClause[]{new AndCompareClause(COLUMN_TABLE_MEASUREMENTS_ROW_CREATED, interval.getStart(), CompareType.GREATER_OR_EQUAL, SQLType.TIMESTAMP), new AndCompareClause(COLUMN_TABLE_MEASUREMENTS_ROW_CREATED, interval.getEnd(), CompareType.LESS_OR_EQUAL, SQLType.TIMESTAMP)})); 121 } 122 sql.addWhereClause(asc); 123 } 124 125 List<Map<String, Object>> rows = getJdbcTemplate().queryForList(sql.toSQLString(service.tut.pori.apilta.sensors.datatypes.Definitions.ELEMENT_MEASUREMENT_LIST), sql.getValues(), sql.getValueTypes()); 126 if(rows.isEmpty()){ 127 LOGGER.debug("No measurements found."); 128 return null; 129 } 130 131 boolean getDataPoints = (DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups) || DataGroups.hasDataGroup(Definitions.DATA_GROUP_DATA_POINTS, dataGroups)); 132 ArrayList<Measurement> measurements = new ArrayList<>(rows.size()); 133 for(Map<String, Object> row : rows){ 134 measurements.add(extractMeasurement(row, createdFilter, getDataPoints, limits)); 135 } 136 137 MeasurementList list = new MeasurementList(); 138 list.setMeasurements(measurements); 139 return list; 140 } 141 142 /** 143 * 144 * @param row 145 * @param createdFilter 146 * @param getDataPoints if true, data points for the measuremnt will be retrieved 147 * @param limits 148 * @return measurement extracted from the given row 149 */ 150 private Measurement extractMeasurement(Map<String, Object> row, Set<Interval> createdFilter, boolean getDataPoints, Limits limits) { 151 Measurement m = new Measurement(); 152 String measurementId = null; 153 for(Entry<String, Object> entry : row.entrySet()){ 154 switch(entry.getKey()){ 155 case COLUMN_MEASUREMENT_ID: 156 measurementId = (String) entry.getValue(); 157 m.setMeasurementId(measurementId); 158 break; 159 case COLUMN_BACKEND_ID: 160 m.setBackendId((Long) entry.getValue()); 161 break; 162 default: 163 if(checkCountColumn(entry.getKey(), entry.getValue()) < 1){ // this should be count(*) 164 LOGGER.warn("Unknown column name, or no results, column: "+entry.getKey()); 165 return null; 166 } 167 break; 168 } 169 } 170 if(getDataPoints){ 171 m.setDataPoints(_dataPointDAO.getDataPoints(createdFilter, limits, measurementId)); 172 } 173 return m; 174 } 175 176 /** 177 * 178 * Add measurements, the generated measurement identifiers are set to the passed objects 179 * 180 * @param measurements the measurements to add 181 * @param taskIds the tasks which are to be associated with the measurements 182 */ 183 public void addMeasurements(Collection<Measurement> measurements, Collection<String> taskIds) { 184 getTransactionTemplate().execute(new TransactionCallback<Void>() { 185 186 @Override 187 public Void doInTransaction(TransactionStatus status) { 188 JdbcTemplate t = getJdbcTemplate(); 189 SimpleJdbcInsert sql = new SimpleJdbcInsert(t); 190 sql.withTableName(TABLE_MEASUREMENTS); 191 sql.withoutTableColumnMetaDataAccess(); 192 sql.usingColumns(SQL_COLUMNS_INSERT_MEASUREMENT); 193 194 HashMap<String, Object> params = new HashMap<>(SQL_COLUMNS_INSERT_MEASUREMENT.length); 195 params.put(COLUMN_ROW_CREATED, null); 196 Object[] ob = new Object[2]; 197 for(Measurement m : measurements){ 198 params.put(COLUMN_BACKEND_ID, m.getBackendId()); 199 String measurementId = UUID.randomUUID().toString(); 200 params.put(COLUMN_MEASUREMENT_ID, measurementId); 201 sql.execute(params); 202 203 ob[0] = measurementId; 204 for(String taskId : taskIds) { 205 ob[1] = taskId; 206 t.update(SQL_INSERT_MEASUREMENT_TASK, ob, SQL_INSERT_MEASUREMENT_TASK_SQL_TYPES); 207 } 208 209 m.setMeasurementId(measurementId); 210 _dataPointDAO.createDataPoints(m.getDataPoints(), measurementId); 211 } 212 return null; 213 } 214 }); 215 } 216 217 /** 218 * delete all measurements taken by the back end 219 * 220 * @param backendId 221 */ 222 public void deleteMeasurements(Long backendId) { 223 getTransactionTemplate().execute(new TransactionCallback<Void>() { 224 225 @Override 226 public Void doInTransaction(TransactionStatus status) { 227 JdbcTemplate t = getJdbcTemplate(); 228 Object[] ob = {backendId}; 229 List<Long> measurementIds = t.queryForList(SQL_GET_MEASUREMENT_IDS, ob, SQL_TYPE_BACKEND_ID, Long.class); 230 if(measurementIds.isEmpty()){ 231 LOGGER.debug("No measurements for back end, id: "+backendId); 232 return null; 233 } 234 235 _dataPointDAO.deleteDataPoints(measurementIds); 236 237 t.update(SQL_DELETE_MEASUREMENTS, ob, SQL_TYPE_BACKEND_ID); 238 239 SQLDeleteBuilder sql = new SQLDeleteBuilder(TABLE_MEASUREMENTS_TASKS); 240 sql.addWhereClause(new AndClause(COLUMN_MEASUREMENT_ID, measurementIds, SQLType.LONG)); 241 sql.execute(getJdbcTemplate()); 242 return null; 243 } 244 }); 245 } 246 247 /** 248 * 249 * @param backendId 250 * @param guid 251 */ 252 protected void addFile(Long backendId, String guid) { 253 getJdbcTemplate().update(SQL_INSERT_FILE, new Object[]{backendId, guid}, SQL_INSERT_FILE_SQL_TYPES); 254 } 255 256 /** 257 * 258 * @param guid 259 * @return back end identifier associated with the given file GUID or null if not found 260 */ 261 protected Long getFileBackendId(String guid) { 262 return (Long) getJdbcTemplate().queryForMap(SQL_GET_BACKEND_ID, new Object[]{guid}, SQL_GET_BACKEND_ID_SQL_TYPES).get(COLUMN_BACKEND_ID); 263 } 264 265 /** 266 * 267 * @param backendId 268 * @return list of file GUIDs associated with the given back end id or null if none was found 269 */ 270 protected List<String> getFileGUIDs(Long backendId) { 271 List<String> guids = getJdbcTemplate().queryForList(SQL_GET_FILE_GUIDS, new Object[]{backendId}, SQL_GET_BACKEND_ID_SQL_TYPES, String.class); 272 return (guids.isEmpty() ? null : guids); 273 } 274 275 /** 276 * 277 * @param backendId 278 * @param guid 279 * @return true if the given GUID is associated with the given back end 280 */ 281 protected boolean backendHasGUID(Long backendId, String guid) { 282 return (getJdbcTemplate().queryForObject(SQL_BACKEND_HAS_GUID, new Object[]{backendId, guid}, SQL_BACKEND_HAS_GUID_SQL_TYPES, Integer.class) > 0); 283 } 284 285 /** 286 * delete all file GUID associations for the given back end id 287 * 288 * @param backendId 289 */ 290 protected void deleteFiles(Long backendId) { 291 getJdbcTemplate().update(SQL_DELETE_FILES, new Object[]{backendId}, SQL_GET_BACKEND_ID_SQL_TYPES); 292 } 293}