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.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.commons.lang3.StringUtils;
024import org.apache.log4j.Logger;
025import org.quartz.JobBuilder;
026
027import service.tut.pori.contentanalysis.AccessDetails;
028import service.tut.pori.contentanalysis.AccessDetails.Permission;
029import service.tut.pori.contentanalysis.AnalysisBackend;
030import service.tut.pori.contentanalysis.AnalysisBackend.Capability;
031import service.tut.pori.contentanalysis.AsyncTask.TaskStatus;
032import service.tut.pori.contentanalysis.AsyncTask.TaskType;
033import service.tut.pori.contentanalysis.BackendDAO;
034import service.tut.pori.contentanalysis.BackendStatus;
035import service.tut.pori.contentanalysis.BackendStatusList;
036import service.tut.pori.contentanalysis.CAContentCore;
037import service.tut.pori.contentanalysis.PhotoTaskDAO;
038import service.tut.pori.contentanalysis.CAContentCore.ServiceType;
039import service.tut.pori.contentstorage.ContentStorageCore;
040import core.tut.pori.context.ServiceInitializer;
041import core.tut.pori.http.RedirectResponse;
042import core.tut.pori.http.parameters.DataGroups;
043import core.tut.pori.http.parameters.Limits;
044import core.tut.pori.users.UserIdentity;
045
046/**
047 * Video content analysis core methods.
048 */
049public final class VideoContentCore {
050  /** default capabilities for video tasks */
051  public static final EnumSet<Capability> DEFAULT_CAPABILITIES = EnumSet.of(Capability.USER_FEEDBACK, Capability.VIDEO_ANALYSIS, Capability.BACKEND_FEEDBACK);
052  private static final Logger LOGGER = Logger.getLogger(VideoContentCore.class);
053  
054  /**
055   * 
056   */
057  private VideoContentCore(){
058    // nothing needed
059  }
060
061  /**
062   * 
063   * @param guid
064   * @param type
065   * @return redirection URL for the given GUID and type or null if either one the given values was null
066   */
067  public static String generateRedirectUrl(String guid, ServiceType type){
068    if(type == null || StringUtils.isBlank(guid)){
069      LOGGER.error("GUID or service type was null.");
070      return null;
071    }
072    return ServiceInitializer.getPropertyHandler().getRESTBindContext()+Definitions.SERVICE_VCA+"/"+service.tut.pori.contentanalysis.Definitions.METHOD_REDIRECT+"?"+service.tut.pori.contentanalysis.Definitions.PARAMETER_GUID+"="+guid+"&"+service.tut.pori.contentanalysis.Definitions.PARAMETER_SERVICE_ID+"="+type.getServiceId();
073  }
074
075  /**
076   * resolves dynamic /rest/r? redirection URL to static access URL
077   * 
078   * @param authenticatedUser
079   * @param serviceType
080   * @param guid
081   * @return redirection to static URL referenced by the given parameters
082   */
083  public static RedirectResponse generateTargetUrl(UserIdentity authenticatedUser, ServiceType serviceType, String guid) {    
084    AccessDetails details = ServiceInitializer.getDAOHandler().getSolrDAO(VideoDAO.class).getAccessDetails(authenticatedUser, guid);
085    if(details == null){
086      throw new IllegalArgumentException("Not Found.");
087    }
088    Permission access = details.getPermission();
089    if(access == Permission.NO_ACCESS){
090      LOGGER.debug("Access denied for GUID: "+guid+" for userId: "+(UserIdentity.isValid(authenticatedUser) ? authenticatedUser.getUserId() : "none"));
091      throw new IllegalArgumentException("Not Found.");
092    }
093    LOGGER.debug("Granting access with "+Permission.class.toString()+" : "+access.name());
094    
095    String url = ContentStorageCore.getContentStorage(false, serviceType).getTargetUrl(details);
096    if(url == null){
097      throw new IllegalArgumentException("Not Found.");
098    }else{
099      return new RedirectResponse(url);
100    }
101  }
102
103  /**
104   * 
105   * This method is called by back-ends to retrieve a list of videos to be analyzed.
106   * To query tasks status from back-end use queryTaskStatus.
107   * 
108   * @param backendId
109   * @param taskId
110   * @param dataGroups
111   * @param limits
112   * @return the task or null if not found
113   */
114  public static VideoTaskDetails queryTaskDetails(Integer backendId, Long taskId, DataGroups dataGroups, Limits limits) {
115    return ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class).getTask(backendId, dataGroups, limits, taskId);
116  }
117
118  /**
119   * 
120   * @param response
121   * @throws IllegalArgumentException
122   */
123  public static void taskFinished(VideoTaskResponse response) throws IllegalArgumentException{
124    CAContentCore.validateTaskResponse(response);
125
126    LOGGER.debug("TaskId: "+response.getTaskId()+", backendId: "+response.getBackendId());
127
128    switch(response.getTaskType()){
129      case BACKEND_FEEDBACK:
130        LOGGER.debug("Using "+VideoFeedbackTask.class.toString()+" for task of type "+TaskType.BACKEND_FEEDBACK);
131      case FEEDBACK:
132        VideoFeedbackTask.taskFinished(response);
133        break;
134      case ANALYSIS:
135        VideoAnalysisTask.taskFinished(response);
136        break;
137      default:
138        throw new IllegalArgumentException("Unsupported "+service.tut.pori.contentanalysis.Definitions.ELEMENT_TASK_TYPE);
139    }
140  }
141
142  /**
143   * 
144   * @param authenticatedUser
145   * @param guids
146   * @param dataGroups
147   * @param limits
148   * @param serviceTypes
149   * @param userIdFilters
150   * @return list of videos or null if none was found with the given parameters
151   */
152  public static VideoList getVideos(UserIdentity authenticatedUser, List<String> guids, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilters) {
153    return ServiceInitializer.getDAOHandler().getSolrDAO(VideoDAO.class).search(authenticatedUser, dataGroups, guids, limits, null, serviceTypes, userIdFilters);
154  }
155  
156  /**
157   * Note: if the details already contain a taskId, the task will NOT be re-added to the database, but simply re-scheduled.
158   * 
159   * If the details contains no back-ends, default back-ends will be added. See {@link #DEFAULT_CAPABILITIES}
160   * 
161   * @param details
162   * @return task id of the generated task, null if task could not be created
163   */
164  public static Long scheduleTask(VideoTaskDetails details) {
165    JobBuilder builder = getBuilder(details.getTaskType());
166    Long taskId = details.getTaskId();
167    if(taskId != null){
168      LOGGER.debug("Task id already present for task, id: "+taskId);
169    }else{
170      BackendStatusList backends = details.getBackends();
171      if(BackendStatusList.isEmpty(backends)){
172        LOGGER.debug("No back-ends given, using defaults...");
173        List<AnalysisBackend> ends = ServiceInitializer.getDAOHandler().getSQLDAO(BackendDAO.class).getBackends(DEFAULT_CAPABILITIES);
174        if(ends == null){
175          LOGGER.warn("Aborting task, no capable back-ends.");
176          return null;
177        }
178        
179        backends = new BackendStatusList();
180        backends.setBackendStatus(ends, TaskStatus.NOT_STARTED);
181        details.setBackends(backends);
182      }
183      
184      taskId = ServiceInitializer.getDAOHandler().getSQLDAO(VideoTaskDAO.class).insertTask(details);
185      if(taskId == null){
186        LOGGER.error("Task schedule failed: failed to insert new video task.");
187        return null;
188      }
189    }
190
191    if(CAContentCore.scheduleTask(builder, taskId)){
192      return taskId;
193    }else{
194      LOGGER.error("Failed to schedule new task.");
195      return null;
196    }
197  }
198  
199  /**
200   * 
201   * @param type
202   * @return new builder for the given type
203   * @throws UnsupportedOperationException on unsupported type
204   * @throws IllegalArgumentException on bad type
205   */
206  private static JobBuilder getBuilder(TaskType type) throws UnsupportedOperationException, IllegalArgumentException{
207    if(type == null){
208      throw new IllegalArgumentException("Null type.");
209    }
210    switch (type) {
211      case BACKEND_FEEDBACK:
212        return JobBuilder.newJob(VideoBackendFeedbackTask.class);
213      case FEEDBACK:
214        return JobBuilder.newJob(VideoFeedbackTask.class);
215      case ANALYSIS:
216        return JobBuilder.newJob(VideoAnalysisTask.class);
217      default:
218        throw new UnsupportedOperationException("Unsupported TaskType: "+type.name());
219    }
220  }
221
222  /**
223   * Create and schedule the task for all capable back-ends included in the task designated by the task Id. The given back-end Id will not participate in the feedback task.
224   * 
225   * @param backendId the back-end that send the task finished call, this back-end is automatically omitted from the list of target back-ends
226   * @param videos videos returned in task finished call
227   * @param taskId the id of the finished analysis task
228   */
229  public static void scheduleBackendFeedback(Integer backendId, VideoList videos, Long taskId) {
230    if(VideoList.isEmpty(videos)){
231      LOGGER.debug("Not scheduling back-end feedback: empty photo list.");
232      return;
233    }
234    
235    BackendStatusList tBackends = ServiceInitializer.getDAOHandler().getSQLDAO(PhotoTaskDAO.class).getBackendStatus(taskId, null);
236    if(BackendStatusList.isEmpty(tBackends)){
237      LOGGER.warn("No back-ends for the given task, or the task does not exist. Task id: "+taskId);
238      return;
239    }
240    
241    List<AnalysisBackend> backends = ServiceInitializer.getDAOHandler().getSQLDAO(BackendDAO.class).getBackends(Capability.BACKEND_FEEDBACK); // get list of back-ends with compatible capabilities
242    if(backends == null){
243      LOGGER.debug("No capable back-ends for back-end feedback.");
244      return;
245    }
246
247    BackendStatusList statuses = new BackendStatusList();
248    for(AnalysisBackend backend : backends){
249      Integer id = backend.getBackendId();
250      if(id.equals(backendId)){ // ignore the given back-end id
251        LOGGER.debug("Ignoring the back-end id of task results, back-end id: "+backendId+", task, id: "+taskId);
252      }else if(tBackends.getBackendStatus(id) != null){ // and all back-ends not part of the task
253        statuses.setBackendStatus(new BackendStatus(backend, TaskStatus.NOT_STARTED));
254      }
255    }
256    if(BackendStatusList.isEmpty(statuses)){
257      LOGGER.debug("No capable back-ends for back-end feedback.");
258      return;
259    }
260    
261    VideoTaskDetails details = (new VideoBackendFeedbackTask.FeedbackTaskBuilder(TaskType.BACKEND_FEEDBACK))
262        .setBackends(statuses)
263        .addVideos(videos)
264        .build(); 
265    Map<String, String> metadata = new HashMap<>(1);
266    metadata.put(service.tut.pori.contentanalysis.Definitions.METADATA_RELATED_TASK_ID, taskId.toString());
267    details.setMetadata(metadata);
268    
269    scheduleTask(details);
270  }
271}