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.Arrays; 019import java.util.Collection; 020import java.util.Date; 021import java.util.EnumSet; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Set; 027import java.util.UUID; 028 029import org.apache.commons.lang3.ArrayUtils; 030import org.apache.log4j.Logger; 031import org.apache.solr.client.solrj.response.QueryResponse; 032import org.apache.solr.client.solrj.response.UpdateResponse; 033import org.apache.solr.common.SolrException; 034import org.springframework.beans.factory.annotation.Autowired; 035 036import service.tut.pori.contentanalysis.AccessDetails; 037import service.tut.pori.contentanalysis.AssociationDAO; 038import service.tut.pori.contentanalysis.CAContentCore.ServiceType; 039import service.tut.pori.contentanalysis.CAContentCore.Visibility; 040import service.tut.pori.contentanalysis.ResultInfo; 041import service.tut.pori.contentanalysis.MediaObject; 042import service.tut.pori.contentanalysis.MediaObjectDAO; 043import service.tut.pori.contentanalysis.MediaObjectList; 044import core.tut.pori.dao.SQLSelectBuilder.OrderDirection; 045import core.tut.pori.dao.SimpleSolrTemplate; 046import core.tut.pori.dao.SolrDAO; 047import core.tut.pori.dao.SolrQueryBuilder; 048import core.tut.pori.dao.filter.AbstractQueryFilter; 049import core.tut.pori.dao.filter.AndQueryFilter; 050import core.tut.pori.dao.filter.AndSubQueryFilter; 051import core.tut.pori.dao.filter.OrQueryFilter; 052import core.tut.pori.http.parameters.DataGroups; 053import core.tut.pori.http.parameters.Limits; 054import core.tut.pori.http.parameters.SortOptions; 055import core.tut.pori.users.UserIdentity; 056import core.tut.pori.utils.MediaUrlValidator.MediaType; 057 058/** 059 * The DAO for storing and retrieving video objects. 060 */ 061public class VideoDAO extends SolrDAO { 062 private static final String BEAN_ID_SOLR_SERVER = "solrServerVideos"; 063 private static final SortOptions DEFAULT_SORT_OPTIONS; 064 static{ 065 DEFAULT_SORT_OPTIONS = new SortOptions(); 066 DEFAULT_SORT_OPTIONS.addSortOption(new SortOptions.Option(SOLR_FIELD_ID, OrderDirection.ASCENDING, Definitions.ELEMENT_VIDEOLIST)); 067 } 068 private static final String[] FIELDS_DATA_GROUP_DEFAULTS = new String[]{SOLR_FIELD_ID, Definitions.SOLR_FIELD_SERVICE_ID, Definitions.SOLR_FIELD_USER_ID}; 069 private static final String FIELDS_DATA_GROUP_VISIBILITY = Definitions.SOLR_FIELD_VISIBILITY; 070 private static final String[] FIELDS_DATA_GROUP_BASIC = ArrayUtils.addAll(FIELDS_DATA_GROUP_DEFAULTS, FIELDS_DATA_GROUP_VISIBILITY, Definitions.SOLR_FIELD_CREDITS, Definitions.SOLR_FIELD_NAME, Definitions.SOLR_FIELD_DESCRIPTION); 071 private static final String[] FIELDS_GET_ACCESS_DETAILS = new String[]{Definitions.SOLR_FIELD_USER_ID, Definitions.SOLR_FIELD_VISIBILITY, SOLR_FIELD_ID}; 072 private static final String[] FIELDS_SET_OWNERS = new String[]{SOLR_FIELD_ID, Definitions.SOLR_FIELD_USER_ID}; 073 private static final Logger LOGGER = Logger.getLogger(VideoDAO.class); 074 private static final EnumSet<MediaType> MEDIA_TYPES = EnumSet.of(MediaType.VIDEO); 075 @Autowired 076 private AssociationDAO _associationDAO = null; 077 @Autowired 078 private VideoTaskDAO _videoTaskDAO = null; 079 @Autowired 080 private MediaObjectDAO _mediaObjectDAO = null; 081 082 /** 083 * Inserts the objects and sets all media types to {@link core.tut.pori.utils.MediaUrlValidator.MediaType#VIDEO} for objects with {@link core.tut.pori.utils.MediaUrlValidator.MediaType#UNKNOWN} or null media type. 084 * @param objects 085 * @return true on success 086 * @see service.tut.pori.contentanalysis.MediaObjectDAO#insert(MediaObjectList) 087 */ 088 public boolean insert(MediaObjectList objects){ 089 if(MediaObjectList.isEmpty(objects)){ 090 LOGGER.debug("Empty list ignored."); 091 return true; 092 } 093 for(MediaObject object : objects.getMediaObjects()){ 094 MediaType mediaType = object.getMediaType(); 095 if(mediaType == null || MediaType.UNKNOWN.equals(mediaType)){ 096 object.setMediaType(MediaType.VIDEO); //set all media object MediaType to VIDEO 097 } 098 } 099 return _mediaObjectDAO.insert(objects); 100 } 101 102 /** 103 * Update the objects and sets all media types to {@link core.tut.pori.utils.MediaUrlValidator.MediaType#VIDEO} for objects with {@link core.tut.pori.utils.MediaUrlValidator.MediaType#UNKNOWN} or null media type. 104 * @param objects 105 * @return true on success 106 * @see service.tut.pori.contentanalysis.MediaObjectDAO#update(MediaObjectList) 107 */ 108 public boolean update(MediaObjectList objects){ 109 if(MediaObjectList.isEmpty(objects)){ 110 LOGGER.debug("Empty list ignored."); 111 return true; 112 } 113 for(MediaObject object : objects.getMediaObjects()){ 114 MediaType mediaType = object.getMediaType(); 115 if(mediaType == null || MediaType.UNKNOWN.equals(mediaType)){ 116 object.setMediaType(MediaType.VIDEO); //set all media object MediaType to VIDEO 117 } 118 } 119 return _mediaObjectDAO.update(objects); 120 } 121 122 /** 123 * 124 * @param video 125 * @return true on success 126 */ 127 public boolean insert(Video video){ 128 VideoList vl = new VideoList(); 129 vl.setVideos(Arrays.asList(video)); 130 return insert(vl); 131 } 132 133 /** 134 * 135 * @param videos 136 * @return true on success 137 */ 138 public boolean insert(VideoList videos){ 139 if(VideoList.isEmpty(videos)){ 140 LOGGER.debug("No videos given."); 141 return false; 142 } 143 LOGGER.debug("Adding videos..."); 144 List<Video> v = videos.getVideos(); 145 Date updated = new Date(); 146 MediaObjectList combined = new MediaObjectList(); 147 for(Video video : v){ 148 if(video.getUpdated() == null){ 149 video.setUpdated(updated); 150 } 151 152 String guid = UUID.randomUUID().toString(); 153 if(video.getGUID() != null){ 154 LOGGER.warn("Replacing GUID for video with existing GUID: "+video.getGUID()+", new GUID: "+guid); 155 } 156 video.setGUID(guid); 157 158 MediaObjectList objects = video.getMediaObjects(); 159 Visibility visibility = video.getVisibility(); 160 UserIdentity userId = video.getOwnerUserId(); 161 if(!MediaObjectList.isEmpty(objects)){ 162 for(Iterator<MediaObject> vIter = objects.getMediaObjects().iterator(); vIter.hasNext();){ 163 MediaObject object = vIter.next(); 164 if(!UserIdentity.equals(userId, object.getOwnerUserId())){ 165 LOGGER.warn("Invalid user identity for media object in video, GUID: "+guid); 166 return false; 167 } 168 MediaType mediaType = object.getMediaType(); 169 if(!MediaType.VIDEO.equals(mediaType)){ 170 LOGGER.debug("Replacing unsupported/incompatible media type: "+mediaType); 171 object.setMediaType(MediaType.VIDEO); 172 } 173 if(object.getVisibility() == null){ 174 LOGGER.debug("Object missing visibility value, using video's visibility."); 175 object.setVisibility(visibility); 176 } 177 combined.addMediaObject(object); 178 } // for 179 } 180 } 181 if(!VideoList.isValid(videos)){ // check validity after ids have been generated 182 LOGGER.warn("Tried to add invalid video list."); 183 return false; 184 } 185 SimpleSolrTemplate template = getSolrTemplate(BEAN_ID_SOLR_SERVER); 186 UpdateResponse response = template.addBeans(v); 187 188 if(response.getStatus() != SolrException.ErrorCode.UNKNOWN.code){ 189 LOGGER.warn("Failed to add videos."); 190 return false; 191 } 192 193 if(insert(combined)){ 194 associate(videos); 195 }else{ 196 LOGGER.warn("Insert failed for combined video list."); 197 return false; 198 } 199 return true; 200 } 201 202 /** 203 * 204 * @param authenticatedUser 205 * @param guid 206 * @return access details for the photo, or null if the photo does not exist 207 */ 208 public AccessDetails getAccessDetails(UserIdentity authenticatedUser, String guid) { 209 SolrQueryBuilder solr = new SolrQueryBuilder(); 210 solr.addFields(FIELDS_GET_ACCESS_DETAILS); 211 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guid)); 212 213 List<Video> videos = getSolrTemplate(BEAN_ID_SOLR_SERVER).queryForList(solr.toSolrQuery(Definitions.ELEMENT_VIDEOLIST), Video.class); 214 if(videos == null){ 215 LOGGER.debug("GUID does not exist: "+guid); 216 return null; 217 } 218 return AccessDetails.getAccessDetails(authenticatedUser, videos.iterator().next()); 219 } 220 221 /** 222 * 223 * @param dataGroups optional filter 224 * @param guids optional filter 225 * @param limits optional filter 226 * @param serviceTypes optional filter 227 * @param userIdFilter optional filter 228 * @return list of videos or null if none 229 */ 230 public VideoList getVideos(DataGroups dataGroups, Collection<String> guids, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilter){ 231 return getVideoList(dataGroups, guids, limits, serviceTypes, userIdFilter); 232 } 233 234 /** 235 * 236 * @param dataGroups 237 * @param guids 238 * @param limits 239 * @param serviceTypes 240 * @param userIdFilter 241 * @return list of videos or null if none was found 242 */ 243 private VideoList getVideoList(DataGroups dataGroups, Collection<String> guids, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilter){ 244 SolrQueryBuilder solr = new SolrQueryBuilder(null); 245 if(guids != null && !guids.isEmpty()){ 246 LOGGER.debug("Adding GUID filter..."); 247 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 248 } 249 if(!ServiceType.isEmpty(serviceTypes)){ 250 LOGGER.debug("Adding service type filter..."); 251 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_SERVICE_ID, ServiceType.toIdArray(serviceTypes))); 252 } 253 254 if(!ArrayUtils.isEmpty(userIdFilter)){ 255 LOGGER.debug("Adding user id filter..."); 256 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_USER_ID, userIdFilter)); 257 } 258 259 solr.setLimits(limits); 260 solr.setSortOptions(DEFAULT_SORT_OPTIONS); 261 setDataGroups(dataGroups, solr); 262 263 QueryResponse response = getSolrTemplate(BEAN_ID_SOLR_SERVER).query(solr.toSolrQuery(Definitions.ELEMENT_VIDEOLIST)); 264 List<Video> videos = SimpleSolrTemplate.getList(response, Video.class); 265 if(videos == null){ 266 LOGGER.debug("No videos"); 267 return null; 268 } 269 270 ResultInfo info = null; 271 if(DataGroups.hasDataGroup(service.tut.pori.contentanalysis.Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 272 LOGGER.debug("Resolving result info for the requested videos."); 273 info = new ResultInfo(limits.getStartItem(Definitions.ELEMENT_VIDEOLIST), limits.getEndItem(Definitions.ELEMENT_VIDEOLIST), response.getResults().getNumFound()); 274 } 275 276 VideoList videoList = VideoList.getVideoList(videos, info); 277 Map<String, Set<String>> guidVoidMap = _associationDAO.getAssociationsForGUIDs(videoList.getGUIDs()); 278 if(guidVoidMap == null){ 279 LOGGER.debug("No objects for the videos."); 280 }else{ 281 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 282 MediaObjectList objects = _mediaObjectDAO.getMediaObjects(dataGroups, limits, MEDIA_TYPES, null, e.getValue(), null); // do NOT give serviceTypes as filter, we are searching videos with specific serviceTypes, not mediaObjects 283 if(MediaObjectList.isEmpty(objects)){ 284 LOGGER.warn("Could not retrieve objects for guid: "+e.getKey()); 285 }else{ 286 videoList.getVideo(e.getKey()).addMediaObjects(objects); 287 } 288 } 289 } 290 291 if(DataGroups.hasDataGroup(service.tut.pori.contentanalysis.Definitions.DATA_GROUP_STATUS, dataGroups)){ 292 _videoTaskDAO.getMediaStatus(videoList.getVideos()); 293 } 294 295 return videoList; 296 } 297 298 /** 299 * 300 * @param dataGroups 301 * @param solr 302 */ 303 private void setDataGroups(DataGroups dataGroups, SolrQueryBuilder solr){ 304 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups)){ 305 LOGGER.debug("Data group "+DataGroups.DATA_GROUP_ALL+" found, will not set field list."); 306 }else if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_BASIC, dataGroups)){ 307 solr.addFields(FIELDS_DATA_GROUP_BASIC); 308 }else{ 309 boolean hasGroup = DataGroups.hasDataGroup(service.tut.pori.contentanalysis.Definitions.DATA_GROUP_VISIBILITY, dataGroups); 310 if(hasGroup){ 311 solr.addField(FIELDS_DATA_GROUP_VISIBILITY); 312 } 313 314 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_DEFAULTS, dataGroups) || DataGroups.isEmpty(dataGroups)){ // if defaults are explicitly given or there are not datagroups 315 solr.addFields(FIELDS_DATA_GROUP_DEFAULTS); 316 }else if(!hasGroup){ 317 LOGGER.debug("No valid data groups, using "+DataGroups.DATA_GROUP_DEFAULTS); 318 solr.addFields(FIELDS_DATA_GROUP_DEFAULTS); 319 } 320 } 321 } 322 323 /** 324 * 325 * @param authenticatedUser optional filter 326 * @param dataGroups optional filter 327 * @param guids optional filter 328 * @param limits optional filter 329 * @param objects optional filter 330 * @param serviceTypes optional filter 331 * @param userIdFilter optional filter 332 * @return the results, or null if none. 333 * @throws IllegalArgumentException on bad search terms 334 */ 335 public VideoList search(UserIdentity authenticatedUser, DataGroups dataGroups, Collection<String> guids, Limits limits, MediaObjectList objects, EnumSet<ServiceType> serviceTypes, long[] userIdFilter) { 336 SolrQueryBuilder solr = new SolrQueryBuilder(); 337 setDataGroups(dataGroups, solr); 338 solr.setLimits(limits); 339 340 if(guids != null && !guids.isEmpty()){ 341 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 342 } 343 344 Map<String, Set<String>> guidVoidMap = null; 345 if(!MediaObjectList.isEmpty(objects)){ // if media objects have been given as a search term, do a media object look-up first 346 List<String> mediaObjectIds = _mediaObjectDAO.getMediaObjectIds(authenticatedUser, dataGroups, null, null, userIdFilter, objects); // do NOT give serviceTypes as filter, we are searching videos with specific serviceTypes, not mediaObjects 347 if(mediaObjectIds == null){ 348 LOGGER.debug("No objects found."); 349 return null; 350 } 351 352 guidVoidMap = _associationDAO.getAssociationsForMediaObjectIds(mediaObjectIds); 353 if(guidVoidMap == null){ 354 LOGGER.debug("No videos associated with the media object results."); 355 return null; 356 } 357 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guidVoidMap.keySet())); 358 } 359 360 if(!ServiceType.isEmpty(serviceTypes)){ 361 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_SERVICE_ID, ServiceType.toIdArray(serviceTypes))); 362 } 363 364 if(!ArrayUtils.isEmpty(userIdFilter)){ 365 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_USER_ID, userIdFilter)); 366 } 367 368 if(!UserIdentity.isValid(authenticatedUser)){ 369 LOGGER.debug("Invalid authenticated user, limiting search to public content."); 370 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_VISIBILITY, Visibility.PUBLIC.toInt())); 371 }else{ 372 solr.addCustomFilter(new AndSubQueryFilter(new AbstractQueryFilter[]{new OrQueryFilter(Definitions.SOLR_FIELD_USER_ID, authenticatedUser.getUserId()), new OrQueryFilter(Definitions.SOLR_FIELD_VISIBILITY, Visibility.PUBLIC.toInt())})); 373 } 374 375 solr.setSortOptions(DEFAULT_SORT_OPTIONS); 376 QueryResponse response = getSolrTemplate(BEAN_ID_SOLR_SERVER).query(solr.toSolrQuery(Definitions.ELEMENT_VIDEOLIST)); 377 List<Video> videos = SimpleSolrTemplate.getList(response, Video.class); 378 if(videos == null){ 379 LOGGER.debug("No videos"); 380 return null; 381 } 382 383 ResultInfo info = null; 384 if(DataGroups.hasDataGroup(service.tut.pori.contentanalysis.Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 385 LOGGER.debug("Resolving result info for the requested videos."); 386 info = new ResultInfo(limits.getStartItem(Definitions.ELEMENT_VIDEOLIST), limits.getEndItem(Definitions.ELEMENT_VIDEOLIST), response.getResults().getNumFound()); 387 } 388 389 VideoList videoList = VideoList.getVideoList(videos, info); 390 if(guidVoidMap == null){ // resolve media object relations if not resolved already, depending on the data groups given we may not even need the media objects, but let's ignore it for now 391 guidVoidMap = _associationDAO.getAssociationsForGUIDs(videoList.getGUIDs()); 392 } 393 394 if(guidVoidMap == null){ 395 LOGGER.debug("No video-media object associations..."); 396 }else{ 397 LOGGER.debug("Retrieving media objects for the list of videos, if needed..."); 398 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 399 MediaObjectList videoObject = _mediaObjectDAO.getMediaObjects(dataGroups, limits, MEDIA_TYPES, null, e.getValue(), null); // do NOT give serviceTypes as filter, we are searching videos with specific serviceTypes, not mediaObjects 400 if(MediaObjectList.isEmpty(videoObject)){ 401 LOGGER.debug("Could not retrieve objects for GUID: "+e.getKey()); 402 }else{ 403 Video video = videoList.getVideo(e.getKey()); 404 if(video == null){ 405 LOGGER.warn("Could not find video, GUID: "+e.getKey()); 406 }else{ 407 video.addMediaObjects(videoObject); 408 } 409 } 410 } 411 } 412 413 if(DataGroups.hasDataGroup(service.tut.pori.contentanalysis.Definitions.DATA_GROUP_STATUS, dataGroups)){ 414 _videoTaskDAO.getMediaStatus(videoList.getVideos()); 415 } 416 417 return videoList; 418 } 419 420 /** 421 * Sets the owner details (userId) to the given videos, requires that GUID has been set to the video object 422 * 423 * @param videos 424 * @return true on success, Note: the failed videos will have userId of null, thus, this method can also be used to check the existence of the given videos. 425 */ 426 public boolean setOwners(VideoList videos) { 427 if(VideoList.isEmpty(videos)){ 428 LOGGER.debug("Ignored empty "+VideoList.class.toString()); 429 return true; 430 } 431 432 List<String> guids = videos.getGUIDs(); 433 if(guids == null){ 434 LOGGER.debug("No GUIDs."); 435 return false; 436 } 437 438 SolrQueryBuilder solr = new SolrQueryBuilder(); 439 solr.addFields(FIELDS_SET_OWNERS); 440 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 441 VideoList found = VideoList.getVideoList(getSolrTemplate(BEAN_ID_SOLR_SERVER).queryForList(solr.toSolrQuery(Definitions.ELEMENT_VIDEOLIST), Video.class),null); 442 if(VideoList.isEmpty(found)){ 443 LOGGER.debug("No videos found."); 444 for(Video video : videos.getVideos()){ // null all user ids 445 video.setOwnerUserId(null); 446 } 447 }else{ 448 for(Video video : videos.getVideos()){ 449 Video foundVideo = found.getVideo(video.getGUID()); 450 if(foundVideo != null){ 451 video.setOwnerUserId(foundVideo.getOwnerUserId()); 452 }else{ 453 video.setOwnerUserId(null); 454 } 455 } // for 456 } 457 return true; 458 } 459 460 /** 461 * create video-media object associations from the given video list 462 * 463 * @param videos 464 */ 465 public void associate(VideoList videos){ 466 _associationDAO.associate(videos.getVideos()); 467 } 468 469 /** 470 * Note: content added through ContentStorage MUST be removed through ContentStorage, removing the metadata directly using this method may cause undefined behavior. 471 * 472 * @param guids 473 * @see service.tut.pori.contentstorage.ContentStorageCore 474 */ 475 public void remove(Collection<String> guids) { 476 if(guids == null || guids.isEmpty()){ 477 LOGGER.debug("Ignored empty GUIDs list."); 478 return; 479 } 480 SimpleSolrTemplate template = getSolrTemplate(BEAN_ID_SOLR_SERVER); 481 if(template.deleteById(guids).getStatus() != SolrException.ErrorCode.UNKNOWN.code){ 482 LOGGER.warn("Failed to delete by GUID."); 483 }else{ 484 Map<String, Set<String>> guidVoidMap = _associationDAO.getAssociationsForGUIDs(guids); 485 if(guidVoidMap == null){ 486 LOGGER.debug("No media objects for the GUID."); 487 }else{ 488 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 489 if(!_mediaObjectDAO.remove(e.getValue())){ // we do not need to de-associate, this will automatically cleanup the association table (by media object dao) 490 LOGGER.warn("Failed to remove media objects for GUID: "+e.getKey()); 491 } 492 } 493 } 494 _videoTaskDAO.remove(guids); 495 } 496 } 497}