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.io.InputStream; 019import java.util.ArrayList; 020import java.util.EnumSet; 021import java.util.List; 022import java.util.Set; 023 024import org.apache.log4j.Logger; 025import org.springframework.context.ApplicationListener; 026 027import service.tut.pori.apilta.files.FilesCore; 028import service.tut.pori.apilta.files.datatypes.FileDetails; 029import service.tut.pori.apilta.sensors.datatypes.DataPoint; 030import service.tut.pori.apilta.sensors.datatypes.Measurement; 031import service.tut.pori.apilta.sensors.datatypes.MeasurementList; 032import service.tut.pori.apilta.sensors.datatypes.SensorTask; 033import service.tut.pori.backends.BackendDAO; 034import service.tut.pori.backends.BackendsCore; 035import service.tut.pori.backends.datatypes.BackendEvent; 036import service.tut.pori.backends.datatypes.BackendUserIdentity.UserPermission; 037import service.tut.pori.tasks.TaskDAO; 038import service.tut.pori.tasks.TasksCore; 039import service.tut.pori.tasks.datatypes.TaskBackend; 040import service.tut.pori.tasks.datatypes.TaskBackend.Status; 041import service.tut.pori.tasks.datatypes.TaskPermissions; 042import service.tut.pori.users.UserDAO; 043import core.tut.pori.context.ServiceInitializer; 044import core.tut.pori.http.parameters.DataGroups; 045import core.tut.pori.http.parameters.DateIntervalParameter.Interval; 046import core.tut.pori.http.parameters.Limits; 047import core.tut.pori.users.UserGroup.Permission; 048import core.tut.pori.users.UserIdentity; 049 050/** 051 * the core methods for sensor service 052 * 053 */ 054public final class SensorsCore { 055 private static final EnumSet<UserPermission> ENUMSET_AUTH_BACKENDS = EnumSet.of(UserPermission.AUTH_BACKENDS); 056 private static final Logger LOGGER = Logger.getLogger(SensorsCore.class); 057 058 /** 059 * 060 */ 061 private SensorsCore() { 062 // nothing needed 063 } 064 065 /** 066 * 067 * @param authenticatedUser 068 * @param task 069 * @throws IllegalArgumentException on invalid task 070 */ 071 public static void taskFinished(UserIdentity authenticatedUser, SensorTask task) throws IllegalArgumentException { 072 List<String> taskIds = task.getTaskIds(); 073 if(taskIds == null || taskIds.isEmpty()){ 074 throw new IllegalArgumentException("Invalid task: task identifier missing."); 075 } 076 077 if(!SensorTask.isValid(task) || task.getConditions() != null){ // it is enough to check that either conditions or output is not present as the validity is checked through isValid() 078 throw new IllegalArgumentException("Invalid task."); 079 } 080 081 List<TaskBackend> backends = task.getBackends(); 082 083 ArrayList<String> validTaskIds = new ArrayList<>(taskIds.size()); 084 SensorTaskDAO taskDAO = task.getTaskDao(); 085 for(String taskId : taskIds){ // check that the user has permissions to modify the tasks 086 TaskPermissions permissions = taskDAO.getTaskPermissions(true, taskId, authenticatedUser); 087 if(!permissions.isTaskExists()){ 088 LOGGER.warn("Ignored non-existing task, id: "+taskId+" for user, id: "+authenticatedUser.getUserId()); // simply ignore tasks that do not exist 089 }else if(!permissions.hasPermissions(backends, ENUMSET_AUTH_BACKENDS)){ // check that the user can really authenticate as every one of the given back ends 090 throw new IllegalArgumentException("Invalid back end identifiers."); 091 }else{ 092 validTaskIds.add(taskId); 093 } 094 } 095 096 if(validTaskIds.isEmpty()){ 097 throw new IllegalArgumentException("Invalid task: no valid task identifiers."); 098 } 099 100 for(TaskBackend backend : backends) { // check that the user has permissions to provide data for the reported back ends 101 Long backendId = backend.getBackendId(); 102 for(String taskId : validTaskIds){ 103 if(!taskDAO.statusUpdated(backend, taskId)){ // update the status information for the back end 104 throw new IllegalArgumentException("Failed to update status for back end, id: "+backendId+", for task, id: "+taskId); // something is wrong if this fails, to do no allow to proceed 105 } 106 } 107 } 108 109 SensorsDAO sensorsDAO = ServiceInitializer.getDAOHandler().getDAO(SensorsDAO.class); 110 List<Measurement> measurements = task.getMeasurements().getMeasurements(); 111 for(Measurement measurement : measurements){ // check that the file GUIDs (if present) has been associated with the given back ends (i.e. the back end has previously uploaded the file) 112 Long backendId = measurement.getBackendId(); 113 for(DataPoint dp : measurement.getDataPoints()){ 114 if(Definitions.DATA_POINT_KEY_FILE_GUID.equals(dp.getKey()) && !sensorsDAO.backendHasGUID(backendId, dp.getValue())){ 115 throw new IllegalArgumentException("File GUID: "+dp.getValue()+" is not associated with back end, id: "+backendId); 116 } // if 117 } // for 118 } 119 120 sensorsDAO.addMeasurements(measurements, validTaskIds); // finally, add the measurements for all valid identifiers 121 } 122 123 /** 124 * Status information for back ends (if given) is ignored and set to {@link service.tut.pori.tasks.datatypes.TaskBackend.Status#NOT_STARTED} 125 * 126 * Task identifier, if given is ignored and a new identifier is generated. 127 * 128 * @param authenticatedUser 129 * @param task 130 * @return identifier for the created task or null on failure (permission denied) 131 * @throws IllegalArgumentException on invalid task 132 */ 133 public static String createTask(UserIdentity authenticatedUser, SensorTask task) throws IllegalArgumentException { 134 List<TaskBackend> backends = task.getBackends(); 135 if(backends == null){ 136 throw new IllegalArgumentException("Invalid task: no back ends."); 137 } 138 BackendDAO backendDAO = ServiceInitializer.getDAOHandler().getDAO(BackendDAO.class); 139 for(TaskBackend backend : backends){ // reset all status information to not started before task validation 140 Long backendId = backend.getBackendId(); 141 Set<UserPermission> permissions = backendDAO.getBackendPermissions(backendId, authenticatedUser); 142 if(permissions == null || !permissions.contains(UserPermission.TASKS)){ 143 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" is not allowed to create tasks for back end, id: "+backendId); 144 return null; 145 } 146 backend.setStatus(Status.NOT_STARTED); 147 } 148 149 List<String> taskIds = task.getTaskIds(); 150 if(taskIds != null && !taskIds.isEmpty()){ 151 LOGGER.debug("Ignoring task ids for new task."); 152 } 153 task.setTaskIds(null); 154 155 if(!SensorTask.isValid(task) || task.getConditions() == null){ // it is enough to check that either conditions or output exists as the validity is checked through isValid() 156 throw new IllegalArgumentException("Invalid task."); 157 } 158 159 UserIdentity target = task.getUserId(); 160 if(UserIdentity.isValid(target)){ 161 if(!ServiceInitializer.getDAOHandler().getDAO(UserDAO.class).hasPermission(authenticatedUser, target, Permission.MODIFY_USERS)){ 162 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" attempted to modify user, id: "+target.getUserId()+" without permission "+Permission.MODIFY_USERS.name()); 163 return null; 164 } 165 }else{ 166 LOGGER.debug("No user id for the task, defaulting to the authenticated user."); 167 task.setUserId(authenticatedUser); 168 } 169 170 Set<String> taskTypes = task.getTaskTypes(); 171 if(taskTypes == null || taskTypes.isEmpty()){ 172 throw new IllegalArgumentException("Invalid task: no task type."); 173 } 174 175 return TasksCore.scheduleTask(task); 176 } 177 178 /** 179 * 180 * Task identifier, if given is ignored and a new identifier is generated. 181 * 182 * @param authenticatedUser 183 * @param task 184 * @return identifier for the created task or null on failure (permission denied) 185 * @throws IllegalArgumentException on invalid task 186 */ 187 public static String modifyTask(UserIdentity authenticatedUser, SensorTask task) throws IllegalArgumentException { 188 List<String> taskIds = task.getTaskIds(); 189 if(taskIds == null || taskIds.size() != 1){ 190 throw new IllegalArgumentException("Invalid task: the task must have exactly one task identifier."); 191 } 192 193 String taskId = taskIds.iterator().next(); 194 TaskPermissions taskPermissions = task.getTaskDao().getTaskPermissions(false, taskId, authenticatedUser); 195 if(!taskPermissions.isTaskExists()){ 196 throw new IllegalArgumentException("Invalid task: the task does not exist, id: "+taskId); 197 }else if(!taskPermissions.isTaskOwner()){ 198 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" is not allowed to modify task, id: "+taskId); 199 return null; 200 } 201 202 List<TaskBackend> backends = task.getBackends(); 203 if(backends == null){ 204 throw new IllegalArgumentException("Invalid task: no back ends."); 205 } 206 207 BackendDAO backendDAO = ServiceInitializer.getDAOHandler().getDAO(BackendDAO.class); 208 for(TaskBackend backend : backends){ // reset all status information to not started before task validation 209 Long backendId = backend.getBackendId(); 210 Set<UserPermission> permissions = backendDAO.getBackendPermissions(backendId, authenticatedUser); 211 if(permissions == null || !permissions.contains(UserPermission.TASKS)){ 212 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" is not allowed to use tasks for back end, id: "+backendId); 213 return null; 214 } 215 } 216 217 if(!SensorTask.isValid(task) || task.getConditions() == null){ // it is enough to check that either conditions or output exists as the validity is checked through isValid() 218 throw new IllegalArgumentException("Invalid task."); 219 } 220 221 UserIdentity target = task.getUserId(); 222 if(!UserIdentity.isValid(target)){ 223 throw new IllegalArgumentException("Invalid task: task must have a user."); 224 } 225 226 if(!ServiceInitializer.getDAOHandler().getDAO(UserDAO.class).hasPermission(authenticatedUser, target, Permission.MODIFY_USERS)){ 227 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" attempted to modify user, id: "+target.getUserId()+" without permission "+Permission.MODIFY_USERS.name()); 228 return null; 229 } 230 231 Set<String> taskTypes = task.getTaskTypes(); 232 if(taskTypes == null || taskTypes.isEmpty()){ 233 throw new IllegalArgumentException("Invalid task: no task type."); 234 } 235 236 return TasksCore.scheduleTask(task); 237 } 238 239 /** 240 * 241 * @param authenticatedUser 242 * @param backendId 243 * @param file 244 * @return details for the created file or null on failure (permission denied) 245 * @throws IllegalArgumentException on bad data 246 */ 247 public static FileDetails createFile(UserIdentity authenticatedUser, Long backendId, InputStream file) throws IllegalArgumentException { 248 Set<UserPermission> permissions = ServiceInitializer.getDAOHandler().getDAO(BackendDAO.class).getBackendPermissions(backendId, authenticatedUser); 249 if(permissions == null || !permissions.contains(UserPermission.AUTH_BACKENDS)){ 250 LOGGER.warn("User, id: "+authenticatedUser.getUserId()+" does not have permission "+UserPermission.AUTH_BACKENDS.name()); 251 return null; 252 } 253 254 FileDetails details = FilesCore.createFile(file); 255 if(!FileDetails.isValid(details)){ 256 throw new IllegalArgumentException("Failed to create file from the given data."); 257 } 258 259 String guid = details.getGUID(); 260 LOGGER.debug("Associating file, guid: "+guid+" with back end, id: "+backendId); 261 ServiceInitializer.getDAOHandler().getDAO(SensorsDAO.class).addFile(backendId, guid); 262 263 return details; 264 } 265 266 /** 267 * 268 * @param authenticatedUser 269 * @param backendIdFilter 270 * @param createdFilter 271 * @param dataGroups valid data groups are: {@value DataGroups#DATA_GROUP_ALL}, {@value DataGroups#DATA_GROUP_BASIC} (only the measurement details are given without data points, this is the default), {@value Definitions#DATA_GROUP_DATA_POINTS} (data points included in the response) 272 * @param limits 273 * @param measurementIdFilter 274 * @param taskIds 275 * @return list of measurements or null if none 276 * @throws IllegalArgumentException on invalid parameters 277 */ 278 public static MeasurementList getMeasurements(UserIdentity authenticatedUser, long[] backendIdFilter, Set<Interval> createdFilter, DataGroups dataGroups, Limits limits, List<String> measurementIdFilter, List<String> taskIds) throws IllegalArgumentException { 279 TaskDAO taskDAO = ServiceInitializer.getDAOHandler().getDAO(TaskDAO.class); 280 for(String taskId : taskIds){ 281 TaskPermissions permissions = taskDAO.getTaskPermissions(false, taskId, authenticatedUser); 282 if(!permissions.canAccessData()){ 283 throw new IllegalArgumentException("Task, id: "+taskId+" was not found or permission was denied."); 284 } 285 } 286 287 MeasurementList measurementList = ServiceInitializer.getDAOHandler().getDAO(SensorsDAO.class).getMeasurements(backendIdFilter, createdFilter, dataGroups, limits, measurementIdFilter, taskIds); 288 resolveFileUrls(measurementList); 289 return measurementList; 290 } 291 292 /** 293 * resolve proper url's for all applicable data points in the measurement list 294 * 295 * @param measurementList 296 */ 297 private static void resolveFileUrls(MeasurementList measurementList) { 298 if(MeasurementList.isEmpty(measurementList)){ 299 LOGGER.debug("Empty measurement list."); 300 }else{ 301 LOGGER.debug("Resolving data point values: "+Definitions.DATA_POINT_KEY_FILE_GUID+" to "+Definitions.DATA_POINT_KEY_FILE_DETAILS_URL); 302 303 for(Measurement m : measurementList.getMeasurements()){ 304 List<DataPoint> dataPoints = m.getDataPoints(); 305 if(dataPoints == null){ 306 LOGGER.debug("No data points for measurement, id: "+m.getMeasurementId()); 307 }else{ 308 for(DataPoint dp : m.getDataPoints()){ 309 if(Definitions.DATA_POINT_KEY_FILE_GUID.equals(dp.getKey())){ 310 dp.setKey(Definitions.DATA_POINT_KEY_FILE_DETAILS_URL); 311 dp.setValue(FilesCore.generateTemporaryUrl(dp.getValue())); 312 } // if guid key 313 } // for data points 314 } // else has data points 315 } // for measurements 316 } // else 317 } 318 319 /** 320 * Event listener for back end related events. 321 * 322 * Automatically instantiated by Spring as a bean. 323 */ 324 @SuppressWarnings("unused") 325 private static class BackendListener implements ApplicationListener<BackendEvent>{ 326 327 @Override 328 public void onApplicationEvent(BackendEvent event) { 329 service.tut.pori.backends.datatypes.BackendEvent.EventType type = event.getType(); 330 if(type == service.tut.pori.backends.datatypes.BackendEvent.EventType.BACKEND_REMOVED && event.getSource().equals(BackendsCore.class)){ 331 Long backendId = event.getBackendId(); 332 LOGGER.debug("Detected event of type "+type.name()+", removing all data generated by back end, id: "+backendId); 333 334 SensorsDAO dao = ServiceInitializer.getDAOHandler().getDAO(SensorsDAO.class); 335 dao.deleteMeasurements(backendId); 336 337 List<String> guids = dao.getFileGUIDs(backendId); 338 if(guids != null){ 339 LOGGER.debug("Removing files associated with back end, id: "+backendId); 340 dao.deleteFiles(backendId); 341 for(String guid : guids){ 342 FilesCore.removeFile(guid); 343 } 344 } // if 345 346 LOGGER.debug("Back end data removed, back end id: "+backendId); 347 } 348 } 349 } // class BackendListener 350}