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}