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 service.tut.pori.contentanalysis.video; 017 018import java.util.Collection; 019import java.util.EnumSet; 020import java.util.Iterator; 021import java.util.List; 022 023import org.apache.commons.lang3.StringUtils; 024import org.apache.log4j.Logger; 025import org.quartz.JobExecutionContext; 026import org.quartz.JobExecutionException; 027 028import service.tut.pori.contentanalysis.AnalysisBackend; 029import service.tut.pori.contentanalysis.AnalysisBackend.Capability; 030import service.tut.pori.contentanalysis.AsyncTask; 031import service.tut.pori.contentanalysis.BackendStatus; 032import service.tut.pori.contentanalysis.BackendStatusList; 033import service.tut.pori.contentanalysis.MediaObject; 034import service.tut.pori.contentanalysis.MediaObjectDAO; 035import service.tut.pori.contentanalysis.MediaObjectList; 036import core.tut.pori.context.ServiceInitializer; 037import core.tut.pori.users.UserIdentity; 038 039 040/** 041 * An implementation of ASyncTask, meant for executing a feedback task. 042 * 043 * Requires a valid taskId for execution, provided in a JobExecutionContext. 044 * 045 */ 046public class VideoFeedbackTask extends AsyncTask{ 047 private static final Logger LOGGER = Logger.getLogger(VideoFeedbackTask.class); 048 049 /** 050 * 051 * @param response 052 * @throws IllegalArgumentException 053 */ 054 public static void taskFinished(VideoTaskResponse response) throws IllegalArgumentException{ 055 Integer backendId = response.getBackendId(); 056 Long taskId = response.getTaskId(); 057 058 VideoTaskDAO taskDAO = ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class); 059 BackendStatus taskStatus = taskDAO.getBackendStatus(backendId, taskId); 060 if(taskStatus == null){ 061 LOGGER.warn("Backend, id: "+backendId+" returned results for task, not given to the backend. TaskId: "+taskId); 062 throw new IllegalArgumentException("This task is not given for backend, id: "+backendId); 063 } 064 065 TaskStatus status = response.getStatus(); 066 if(status == null){ 067 LOGGER.warn("Task status not available."); 068 status = TaskStatus.UNKNOWN; 069 } 070 taskStatus.setStatus(status); 071 072 try{ 073 VideoList results = response.getVideoList(); 074 if(VideoList.isEmpty(results)){ 075 LOGGER.warn("No results returned by the backendId: "+backendId); 076 return; 077 } 078 079 if(!VideoList.isValid(results)){ 080 LOGGER.warn("Invalid "+Definitions.ELEMENT_VIDEOLIST+"."); 081 } 082 083 VideoDAO videoDAO = ServiceInitializer.getDAOHandler().getSolrDAO(VideoDAO.class); 084 if(!videoDAO.setOwners(results)){ 085 LOGGER.warn("Could not get owner information for all videos."); 086 } 087 088 VideoList associations = new VideoList(); 089 MediaObjectList insert = new MediaObjectList(); 090 MediaObjectList update = new MediaObjectList(); 091 MediaObjectDAO vdao = ServiceInitializer.getDAOHandler().getSolrDAO(MediaObjectDAO.class); 092 for(Iterator<Video> videoIter = results.getVideos().iterator(); videoIter.hasNext();){ 093 Video video = videoIter.next(); 094 String guid = video.getGUID(); 095 UserIdentity userId = video.getOwnerUserId(); 096 if(!UserIdentity.isValid(userId)){ // if this video does not exist, there won't be userId 097 LOGGER.warn("Ignoring non-existing video, GUID: "+guid+" from backend, id: "+backendId); 098 continue; 099 } 100 BackendStatusList c = video.getBackendStatus(); 101 if(BackendStatusList.isEmpty(c)){ 102 LOGGER.debug("Backend status not available for video, GUID: "+guid); 103 }else if(c.getCombinedStatus() == TaskStatus.ERROR){ 104 LOGGER.warn("Error condition detected for video, GUID: "+guid); 105 }else{ 106 List<BackendStatus> sList = c.getBackendStatuses(); 107 if(sList.size() > 1){ 108 status = TaskStatus.ERROR; 109 throw new IllegalArgumentException("Multiple backend statuses."); 110 } 111 if(!backendId.equals(sList.get(0).getBackendId())){ 112 status = TaskStatus.ERROR; 113 throw new IllegalArgumentException("Invalid backend status."); 114 } 115 } 116 MediaObjectList vObjects = video.getMediaObjects(); 117 if(!MediaObjectList.isEmpty(vObjects)){ // make sure all objects have proper user 118 for(MediaObject mediaObject : vObjects.getMediaObjects()){ // check that the objects are valid 119 if(!backendId.equals(mediaObject.getBackendId())){ 120 LOGGER.warn("Task backend id "+backendId+" does not match the backend id "+mediaObject.getBackendId()+" given for media object, objectId: "+mediaObject.getObjectId()); 121 mediaObject.setBackendId(backendId); 122 } 123 mediaObject.setOwnerUserId(userId); 124 } 125 vdao.resolveObjectIds(vObjects); // resolve ids for update/insert sort 126 Video iVideo = null; 127 for(MediaObject vo : vObjects.getMediaObjects()){ // re-sort to to updated and new 128 if(StringUtils.isBlank(vo.getMediaObjectId())){ // no media object id, this is a new one 129 if(iVideo == null){ 130 associations.getVideo(guid); // get target video for insertion 131 if(iVideo == null){ 132 iVideo = new Video(guid); 133 associations.addVideo(iVideo); 134 } 135 } 136 iVideo.addMediaObject(vo); 137 insert.addMediaObject(vo); 138 }else{ 139 update.addMediaObject(vo); 140 } 141 } // for 142 } // for objects 143 } 144 145 if(MediaObjectList.isEmpty(insert)){ 146 LOGGER.debug("Nothing to insert."); 147 }else if(!MediaObjectList.isValid(insert)){ 148 status = TaskStatus.ERROR; 149 throw new IllegalArgumentException("Invalid media object list."); 150 }else if(!videoDAO.insert(insert)){ 151 LOGGER.warn("Failed to insert new objects."); 152 }else{ 153 videoDAO.associate(associations); 154 } 155 156 if(MediaObjectList.isEmpty(update)){ 157 LOGGER.debug("Nothing to update"); 158 }else if(!MediaObjectList.isValid(update)){ 159 status = TaskStatus.ERROR; 160 throw new IllegalArgumentException("Invalid media object list."); 161 }else if(!vdao.update(update)){ 162 LOGGER.warn("Failed to update objects."); 163 } 164 165 taskDAO.updateMediaStatus(results.getVideos(), taskId); 166 taskDAO.updateTaskStatus(taskStatus, taskId); 167 } finally { 168 ServiceInitializer.getEventHandler().publishEvent(new AsyncTaskEvent(backendId, VideoFeedbackTask.class, status, taskId, TaskType.FEEDBACK)); 169 } 170 } 171 172 @Override 173 public void execute(JobExecutionContext context) throws JobExecutionException { 174 executeAddTask(EnumSet.of(Capability.VIDEO_ANALYSIS, Capability.USER_FEEDBACK), ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class), getTaskId(context.getMergedJobDataMap())); 175 } 176 177 /** 178 * A helper class building VideoTaskDetails usable with {@link VideoFeedbackTask} and executable using {@link service.tut.pori.contentanalysis.video.VideoContentCore#scheduleTask(VideoTaskDetails)} 179 * @see service.tut.pori.contentanalysis.video.VideoContentCore 180 * @see service.tut.pori.contentanalysis.video.VideoTaskDetails 181 */ 182 public static class FeedbackTaskBuilder{ 183 private VideoTaskDetails _details = null; 184 185 /** 186 * 187 * @param taskType {@link service.tut.pori.contentanalysis.AsyncTask.TaskType#FEEDBACK} 188 * @throws IllegalArgumentException on unsupported/invalid task type 189 */ 190 public FeedbackTaskBuilder(TaskType taskType) throws IllegalArgumentException { 191 if(taskType != TaskType.FEEDBACK){ 192 throw new IllegalArgumentException("Invalid task type."); 193 } 194 _details = new VideoTaskDetails(taskType); 195 } 196 197 /** 198 * Add video to feedback task if the given video has (valid) changes 199 * 200 * @param video 201 * @return this 202 */ 203 public FeedbackTaskBuilder addVideo(Video video){ 204 if(video == null){ 205 LOGGER.warn("Ignored null video."); 206 }else{ 207 _details.addVideo(video); 208 } 209 return this; 210 } 211 212 /** 213 * 214 * @param videos 215 * @return this 216 */ 217 public FeedbackTaskBuilder addVideos(VideoList videos){ 218 if(VideoList.isEmpty(videos)){ 219 LOGGER.warn("Ignored empty video list."); 220 }else{ 221 for(Video p : videos.getVideos()){ 222 addVideo(p); 223 } 224 } 225 return this; 226 } 227 228 /** 229 * 230 * @param video 231 * @return this 232 * @throws IllegalArgumentException 233 */ 234 public FeedbackTaskBuilder addDeletedVideo(Video video) throws IllegalArgumentException{ 235 if(video == null){ 236 LOGGER.warn("Ignored null video."); 237 return this; 238 }else if(StringUtils.isBlank(video.getGUID())){ 239 throw new IllegalArgumentException("No GUID."); 240 } 241 _details.addDeletedVideo(video); 242 return this; 243 } 244 245 /** 246 * 247 * @param guids 248 * @return this 249 */ 250 public FeedbackTaskBuilder addDeletedVideos(Collection<String> guids){ 251 if(guids == null || guids.isEmpty()){ 252 LOGGER.warn("Ignored empty deleted video list."); 253 return this; 254 } 255 for(String guid : guids){ 256 addDeletedVideo(new Video(guid)); 257 } 258 return this; 259 } 260 261 /** 262 * 263 * @param videos 264 * @return this 265 */ 266 public FeedbackTaskBuilder addDeletedVideos(DeletedVideoList videos){ 267 if(DeletedVideoList.isEmpty(videos)){ 268 LOGGER.warn("Ignored empty deleted video list."); 269 return this; 270 } 271 DeletedVideoList deleted = _details.getDeletedVideoList(); 272 if(DeletedVideoList.isEmpty(deleted)){ 273 _details.setDeletedVideoList(videos); 274 }else{ 275 deleted.addVideos(videos); 276 } 277 return this; 278 } 279 280 /** 281 * 282 * @param userId 283 * @return this 284 */ 285 public FeedbackTaskBuilder setUser(UserIdentity userId){ 286 _details.setUserId(userId); 287 return this; 288 } 289 290 /** 291 * 292 * @param end 293 * @return this 294 * @throws IllegalArgumentException on null or invalid back-end 295 */ 296 public FeedbackTaskBuilder addBackend(AnalysisBackend end) throws IllegalArgumentException{ 297 if(end == null || !end.hasCapability(Capability.USER_FEEDBACK)){ 298 throw new IllegalArgumentException("The given back-end, id: "+end.getBackendId()+" does not have the required capability: "+Capability.USER_FEEDBACK.name()); 299 } 300 _details.setBackend(new BackendStatus(end, TaskStatus.NOT_STARTED)); 301 return this; 302 } 303 304 /** 305 * This will automatically filter out back-end with inadequate capabilities 306 * 307 * @param backendStatusList 308 * @return this 309 */ 310 public FeedbackTaskBuilder setBackends(BackendStatusList backendStatusList){ 311 if(BackendStatusList.isEmpty(backendStatusList)){ 312 LOGGER.debug("Empty backend status list."); 313 backendStatusList = null; 314 }else if((backendStatusList = BackendStatusList.getBackendStatusList(backendStatusList.getBackendStatuses(EnumSet.of(Capability.USER_FEEDBACK)))) == null){ // filter out back-ends with invalid capabilities 315 LOGGER.warn("List contains no back-ends with valid capability "+Capability.USER_FEEDBACK.name()+"for task type "+TaskType.FEEDBACK.name()); 316 } 317 _details.setBackends(backendStatusList); 318 return this; 319 } 320 321 /** 322 * 323 * @return this 324 */ 325 public FeedbackTaskBuilder clearDeletedVideos(){ 326 _details.setDeletedVideoList(null); 327 return this; 328 } 329 330 /** 331 * 332 * @return this 333 */ 334 public FeedbackTaskBuilder clearVideos(){ 335 _details.setVideoList(null); 336 return this; 337 } 338 339 /** 340 * 341 * @return this 342 * @throws IllegalArgumentException 343 */ 344 private VideoTaskDetails buildFeedback() throws IllegalArgumentException { 345 boolean hasDeleted = !VideoList.isEmpty(_details.getDeletedVideoList()); 346 VideoList videoList = _details.getVideoList(); 347 boolean hasVideos = !VideoList.isEmpty(videoList); 348 349 // check for validity: 350 if(hasDeleted){ 351 if(hasVideos){ 352 throw new IllegalArgumentException("Deleted videos must appear alone."); 353 } // no need to validate the deleted video list, it only requires GUIDs 354 }else if(!hasVideos){ 355 LOGGER.debug("No content."); 356 return null; 357 } 358 359 return _details; 360 } 361 362 /** 363 * 364 * @return video task details created from the given values 365 * @throws IllegalArgumentException on invalid value combination 366 */ 367 private VideoTaskDetails buildBackendFeedback() throws IllegalArgumentException { 368 VideoList videoList = _details.getVideoList(); 369 if(VideoList.isEmpty(videoList)){ 370 throw new IllegalArgumentException("Back-end feedback must contain video list."); 371 }else if(!DeletedVideoList.isEmpty(_details.getDeletedVideoList())){ 372 throw new IllegalArgumentException("Back-end feedback can only contain video list."); 373 } 374 375 return _details; 376 } 377 378 /** 379 * 380 * @return new task details based on the given data or null if no data was given 381 */ 382 public VideoTaskDetails build() { 383 if(_details.getTaskType() == TaskType.FEEDBACK){ 384 return buildFeedback(); 385 }else{ 386 return buildBackendFeedback(); 387 } 388 } 389 } // class FeedbackTaskBuilder 390}