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;
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 core.tut.pori.context.ServiceInitializer;
029import core.tut.pori.users.UserIdentity;
030
031
032/**
033 * An implementation of ASyncTask, meant for executing an analysis task.
034 * 
035 * Requires a valid taskId for execution, provided in a JobExecutionContext.
036 * 
037 */
038public class PhotoAnalysisTask extends AsyncTask{ 
039  private static final Logger LOGGER = Logger.getLogger(PhotoAnalysisTask.class);
040
041  @Override
042  public void execute(JobExecutionContext context) throws JobExecutionException {
043    executeAddTask(EnumSet.of(Capability.PHOTO_ANALYSIS), ServiceInitializer.getDAOHandler().getSQLDAO(PhotoTaskDAO.class), getTaskId(context.getMergedJobDataMap()));
044  }
045
046  /**
047   * Checks that the given response is for a valid task, that the backend has permissions for the task, and that the given result data is valid.
048   * On valid data, media objects for the photos will be updated (if there are changes). The given response will be modified to be valid, if possible.
049   * 
050   * @param response
051   * @throws IllegalArgumentException
052   */
053  public static void taskFinished(PhotoTaskResponse response) throws IllegalArgumentException {
054    Integer backendId = response.getBackendId();
055    Long taskId = response.getTaskId();
056
057    PhotoTaskDAO taskDAO = ServiceInitializer.getDAOHandler().getSQLDAO(PhotoTaskDAO.class);
058    BackendStatus backendStatus = taskDAO.getBackendStatus(backendId, taskId);
059    if(backendStatus == null){
060      LOGGER.warn("Backend, id: "+backendId+" returned results for task, not given to the backend. TaskId: "+taskId);
061      throw new IllegalArgumentException("This task is not given for backend, id: "+backendId);
062    }
063
064    TaskStatus status = response.getStatus();
065    if(status == null){
066      LOGGER.warn("Task status not available.");
067      status = TaskStatus.UNKNOWN;
068    }
069    backendStatus.setStatus(status);
070
071    try{
072      PhotoList results = response.getPhotoList();
073      if(PhotoList.isEmpty(results)){
074        LOGGER.warn("No results returned by the backendId: "+backendId);
075        return;
076      }
077
078      if(!PhotoList.isValid(results)){
079        LOGGER.warn("Invalid "+Definitions.ELEMENT_PHOTOLIST+".");
080      }
081
082      PhotoDAO photoDAO = ServiceInitializer.getDAOHandler().getSolrDAO(PhotoDAO.class);
083      if(!photoDAO.setOwners(results)){
084        LOGGER.warn("Could not get owner information for all photos.");
085      }
086
087      PhotoList associations = new PhotoList();
088      MediaObjectList insert = new MediaObjectList();
089      MediaObjectList update = new MediaObjectList();
090      MediaObjectDAO vdao = ServiceInitializer.getDAOHandler().getSolrDAO(MediaObjectDAO.class);
091      for(Iterator<Photo> photoIter = results.getPhotos().iterator(); photoIter.hasNext();){
092        Photo photo = photoIter.next();
093        String guid = photo.getGUID();
094        UserIdentity userId = photo.getOwnerUserId();
095        if(!UserIdentity.isValid(userId)){  // if this photo does not exist, there won't be userId
096          LOGGER.warn("Ignoring non-existing photo, GUID: "+guid+" from back-end, id: "+backendId);
097          photoIter.remove();
098          continue;
099        }
100        BackendStatusList c = photo.getBackendStatus();
101        if(BackendStatusList.isEmpty(c)){
102          LOGGER.debug("Backend status not available for photo, GUID: "+guid);
103        }else if(c.getCombinedStatus() == TaskStatus.ERROR){
104          LOGGER.warn("Error condition detected for photo, GUID: "+guid);
105        }else{
106          List<BackendStatus> sList = c.getBackendStatuses();
107          if(sList.size() > 1){
108            backendStatus.setStatus(TaskStatus.ERROR);
109            throw new IllegalArgumentException("Multiple back-end statuses.");
110          }
111          if(!backendId.equals(sList.get(0).getBackendId())){
112            backendStatus.setStatus(TaskStatus.ERROR);
113            throw new IllegalArgumentException("Invalid back-end status.");
114          }
115        }
116        MediaObjectList vObjects = photo.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 back-end 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          Photo iPhoto = 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(iPhoto == null){
130                associations.getPhoto(guid); // get target photo for insertion
131                if(iPhoto == null){
132                  iPhoto = new Photo(guid);
133                  associations.addPhoto(iPhoto);
134                }
135              }       
136              iPhoto.addMediaObject(vo);
137              insert.addMediaObject(vo);
138            }else{
139              update.addMediaObject(vo);
140            }
141          } // for
142        }else{
143          LOGGER.warn("Ignored photo without objects, GUID : "+guid);
144          photoIter.remove();
145        }
146      }
147
148      if(MediaObjectList.isEmpty(insert)){
149        LOGGER.debug("Nothing to insert.");
150      }else if(!MediaObjectList.isValid(insert)){
151        backendStatus.setStatus(TaskStatus.ERROR);
152        throw new IllegalArgumentException("Invalid media object list.");
153      }else if(!photoDAO.insert(insert)){
154        LOGGER.warn("Failed to insert new objects.");   
155      }else{
156        photoDAO.associate(associations);
157      }
158
159      if(MediaObjectList.isEmpty(update)){
160        LOGGER.debug("Nothing to update");
161      }else if(!MediaObjectList.isValid(update)){
162        backendStatus.setStatus(TaskStatus.ERROR);
163        throw new IllegalArgumentException("Invalid media object list.");
164      }else if(!photoDAO.update(update)){
165        LOGGER.warn("Failed to update objects.");
166      }
167
168      taskDAO.updateMediaStatus(results.getPhotos(), taskId); 
169      CAContentCore.scheduleBackendFeedback(backendId, results, taskId);
170    } finally {
171      taskDAO.updateTaskStatus(backendStatus, taskId);
172      ServiceInitializer.getEventHandler().publishEvent(new AsyncTaskEvent(backendId, PhotoAnalysisTask.class, status, taskId, TaskType.ANALYSIS));
173    }
174  }
175}