001/**
002 * Copyright 2015 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.EnumSet;
019import java.util.Iterator;
020import java.util.List;
021
022import org.apache.commons.lang3.StringUtils;
023import org.apache.log4j.Logger;
024import org.quartz.JobExecutionContext;
025import org.quartz.JobExecutionException;
026
027import service.tut.pori.contentanalysis.AnalysisBackend.Capability;
028import service.tut.pori.contentanalysis.AsyncTask;
029import service.tut.pori.contentanalysis.BackendStatus;
030import service.tut.pori.contentanalysis.BackendStatusList;
031import service.tut.pori.contentanalysis.MediaObject;
032import service.tut.pori.contentanalysis.MediaObjectDAO;
033import service.tut.pori.contentanalysis.MediaObjectList;
034import core.tut.pori.context.ServiceInitializer;
035import core.tut.pori.users.UserIdentity;
036
037/**
038 * Analysis task for video content.
039 */
040public class VideoAnalysisTask extends AsyncTask {
041  private static final Logger LOGGER = Logger.getLogger(VideoAnalysisTask.class);
042
043  @Override
044  public void execute(JobExecutionContext context) throws JobExecutionException {
045    executeAddTask(EnumSet.of(Capability.VIDEO_ANALYSIS), ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class), getTaskId(context.getMergedJobDataMap()));
046  }
047
048  /**
049   * Checks that the given response is for a valid task, that the back-end has permissions for the task, and that the given result data is valid.
050   * On valid data, media objects for the videos will be updated (if there are changes). The given response will be modified to be valid, if possible.
051   * 
052   * @param response
053   * @throws IllegalArgumentException
054   */
055  public static void taskFinished(VideoTaskResponse response) throws IllegalArgumentException {
056    Integer backendId = response.getBackendId();
057    Long taskId = response.getTaskId();
058
059    VideoTaskDAO taskDAO = ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class);
060    BackendStatus backendStatus = taskDAO.getBackendStatus(backendId, taskId);
061    if(backendStatus == null){
062      LOGGER.warn("Backend, id: "+backendId+" returned results for task, not given to the backend. TaskId: "+taskId);
063      throw new IllegalArgumentException("This task is not given for backend, id: "+backendId);
064    }
065
066    TaskStatus status = response.getStatus();
067    if(status == null){
068      LOGGER.warn("Task status not available.");
069      status = TaskStatus.UNKNOWN;
070    }
071    backendStatus.setStatus(status);
072
073    try{
074      VideoList results = response.getVideoList();
075      if(VideoList.isEmpty(results)){
076        LOGGER.warn("No results returned by the backendId: "+backendId);
077        return;
078      }
079
080      if(!VideoList.isValid(results)){
081        LOGGER.warn("Invalid "+Definitions.ELEMENT_VIDEOLIST+".");
082      }
083
084      VideoDAO videoDAO = ServiceInitializer.getDAOHandler().getSolrDAO(VideoDAO.class);
085      if(!videoDAO.setOwners(results)){
086        LOGGER.warn("Could not get owner information for all videos.");
087      }
088
089      VideoList associations = new VideoList();
090      MediaObjectList insert = new MediaObjectList();
091      MediaObjectList update = new MediaObjectList();
092      MediaObjectDAO vdao = ServiceInitializer.getDAOHandler().getSolrDAO(MediaObjectDAO.class);
093      for(Iterator<Video> videoIter = results.getVideos().iterator(); videoIter.hasNext();){
094        Video video = videoIter.next();
095        String guid = video.getGUID();
096        UserIdentity userId = video.getOwnerUserId();
097        if(!UserIdentity.isValid(userId)){  // if this photo does not exist, there won't be userId
098          LOGGER.warn("Ignoring non-existing video, GUID: "+guid+" from backend, id: "+backendId);
099          videoIter.remove();
100          continue;
101        }
102        BackendStatusList c = video.getBackendStatus();
103        if(BackendStatusList.isEmpty(c)){
104          LOGGER.debug("Backend status not available for video, GUID: "+guid);
105        }else if(c.getCombinedStatus() == TaskStatus.ERROR){
106          LOGGER.warn("Error condition detected for video, GUID: "+guid);
107        }else{
108          List<BackendStatus> sList = c.getBackendStatuses();
109          if(sList.size() > 1){
110            backendStatus.setStatus(TaskStatus.ERROR);
111            throw new IllegalArgumentException("Multiple back-end statuses.");
112          }
113          if(!backendId.equals(sList.get(0).getBackendId())){
114            backendStatus.setStatus(TaskStatus.ERROR);
115            throw new IllegalArgumentException("Invalid back-end status.");
116          }
117        }
118        MediaObjectList vObjects = video.getMediaObjects();
119        if(!MediaObjectList.isEmpty(vObjects)){  // make sure all objects have proper user
120          for(MediaObject mediaObject : vObjects.getMediaObjects()){ // check that the objects are valid
121            if(!backendId.equals(mediaObject.getBackendId())){
122              LOGGER.warn("Task backend id: "+backendId+" does not match the back-end id: "+mediaObject.getBackendId()+" given for media object, objectId: "+mediaObject.getObjectId());
123              mediaObject.setBackendId(backendId);
124            }
125            mediaObject.setOwnerUserId(userId);
126          }
127          vdao.resolveObjectIds(vObjects); // resolve ids for update/insert sort
128          Video iVideo = null;
129          for(MediaObject vo : vObjects.getMediaObjects()){ // re-sort to to updated and new
130            if(StringUtils.isBlank(vo.getMediaObjectId())){ // no media object id, this is a new one
131              if(iVideo == null){
132                associations.getVideo(guid); // get target photo for insertion
133                if(iVideo == null){
134                  iVideo = new Video(guid);
135                  associations.addVideo(iVideo);
136                }
137              }       
138              iVideo.addMediaObject(vo);
139              insert.addMediaObject(vo);
140            }else{
141              update.addMediaObject(vo);
142            }
143          } // for
144        }else{
145          LOGGER.warn("Ignored video without objects, GUID: "+guid);
146          videoIter.remove();
147        }
148      }
149
150      if(MediaObjectList.isEmpty(insert)){
151        LOGGER.debug("Nothing to insert.");
152      }else if(!MediaObjectList.isValid(insert)){
153        backendStatus.setStatus(TaskStatus.ERROR);
154        throw new IllegalArgumentException("Invalid media object list.");
155      }else if(!videoDAO.insert(insert)){
156        LOGGER.warn("Failed to insert new objects.");   
157      }else{
158        videoDAO.associate(associations);
159      }
160
161      if(MediaObjectList.isEmpty(update)){
162        LOGGER.debug("Nothing to update");
163      }else if(!MediaObjectList.isValid(update)){
164        backendStatus.setStatus(TaskStatus.ERROR);
165        throw new IllegalArgumentException("Invalid media object list.");
166      }else if(!videoDAO.update(update)){
167        LOGGER.warn("Failed to update objects.");
168      }
169
170      taskDAO.updateMediaStatus(results.getVideos(), taskId); 
171      VideoContentCore.scheduleBackendFeedback(backendId, results, taskId);
172    } finally {
173      taskDAO.updateTaskStatus(backendStatus, taskId);
174      ServiceInitializer.getEventHandler().publishEvent(new AsyncTaskEvent(backendId, VideoAnalysisTask.class, status, taskId, TaskType.ANALYSIS));
175    }
176  }
177}