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.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Date; 022import java.util.EnumSet; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Set; 029import java.util.UUID; 030 031import org.apache.commons.lang3.ArrayUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.log4j.Logger; 034import org.apache.solr.client.solrj.response.QueryResponse; 035import org.apache.solr.client.solrj.response.UpdateResponse; 036import org.apache.solr.common.SolrException; 037import org.springframework.beans.factory.annotation.Autowired; 038 039import service.tut.pori.contentanalysis.CAContentCore.ServiceType; 040import service.tut.pori.contentanalysis.CAContentCore.Visibility; 041import core.tut.pori.dao.SQLSelectBuilder.OrderDirection; 042import core.tut.pori.dao.SimpleSolrTemplate; 043import core.tut.pori.dao.SolrDAO; 044import core.tut.pori.dao.SolrQueryBuilder; 045import core.tut.pori.dao.filter.AbstractQueryFilter; 046import core.tut.pori.dao.filter.AndQueryFilter; 047import core.tut.pori.dao.filter.AndSubQueryFilter; 048import core.tut.pori.dao.filter.OrQueryFilter; 049import core.tut.pori.http.parameters.DataGroups; 050import core.tut.pori.http.parameters.Limits; 051import core.tut.pori.http.parameters.SortOptions; 052import core.tut.pori.users.UserIdentity; 053import core.tut.pori.utils.MediaUrlValidator.MediaType; 054 055/** 056 * This class can be used to add, remove and modify photo metadata, and also to associate the photos with media objects. 057 */ 058public class PhotoDAO extends SolrDAO{ 059 private static final String BEAN_ID_SOLR_SERVER = "solrServerPhotos"; 060 private static final SortOptions DEFAULT_SORT_OPTIONS; 061 static{ 062 DEFAULT_SORT_OPTIONS = new SortOptions(); 063 DEFAULT_SORT_OPTIONS.addSortOption(new SortOptions.Option(SOLR_FIELD_ID, OrderDirection.ASCENDING, Definitions.ELEMENT_PHOTOLIST)); 064 } 065 private static final String[] FIELDS_DATA_GROUP_DEFAULTS = new String[]{SOLR_FIELD_ID, Definitions.SOLR_FIELD_SERVICE_ID, Definitions.SOLR_FIELD_USER_ID}; 066 private static final String FIELDS_DATA_GROUP_VISIBILITY = Definitions.SOLR_FIELD_VISIBILITY; 067 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); 068 private static final String[] FIELDS_GET_ACCESS_DETAILS = new String[]{Definitions.SOLR_FIELD_USER_ID, Definitions.SOLR_FIELD_VISIBILITY, SOLR_FIELD_ID}; 069 private static final String[] FIELDS_SET_OWNERS = new String[]{SOLR_FIELD_ID, Definitions.SOLR_FIELD_USER_ID}; 070 private static final String[] FIELDS_UPDATE = new String[]{Definitions.SOLR_FIELD_USER_ID, SOLR_FIELD_ID}; 071 private static final Logger LOGGER = Logger.getLogger(PhotoDAO.class); 072 private static final EnumSet<MediaType> MEDIA_TYPES = EnumSet.of(MediaType.PHOTO); 073 @Autowired 074 private AssociationDAO _associationDAO = null; 075 @Autowired 076 private PhotoTaskDAO _photoTaskDAO = null; 077 @Autowired 078 private MediaObjectDAO _mediaObjectDAO = null; 079 080 /** 081 * Inserts the objects and sets all media types to {@link core.tut.pori.utils.MediaUrlValidator.MediaType#PHOTO} for all objects with {@link core.tut.pori.utils.MediaUrlValidator.MediaType#UNKNOWN} or null media type- 082 * @param objects 083 * @return true on success 084 * @see service.tut.pori.contentanalysis.MediaObjectDAO#insert(MediaObjectList) 085 */ 086 public boolean insert(MediaObjectList objects){ 087 if(MediaObjectList.isEmpty(objects)){ 088 LOGGER.debug("Empty list ignored."); 089 return true; 090 } 091 for(MediaObject object : objects.getMediaObjects()){ 092 MediaType mediaType = object.getMediaType(); 093 if(mediaType == null || MediaType.UNKNOWN.equals(mediaType)){ 094 object.setMediaType(MediaType.PHOTO); //set all media object MediaType to PHOTO 095 } 096 } 097 return _mediaObjectDAO.insert(objects); 098 } 099 100 /** 101 * Update the objects and sets all media types to {@link core.tut.pori.utils.MediaUrlValidator.MediaType#PHOTO} for all objects with {@link core.tut.pori.utils.MediaUrlValidator.MediaType#UNKNOWN} or null media type- 102 * @param objects 103 * @return true on success 104 * @see service.tut.pori.contentanalysis.MediaObjectDAO#update(MediaObjectList) 105 */ 106 public boolean update(MediaObjectList objects){ 107 if(MediaObjectList.isEmpty(objects)){ 108 LOGGER.debug("Empty list ignored."); 109 return true; 110 } 111 for(MediaObject object : objects.getMediaObjects()){ 112 MediaType mediaType = object.getMediaType(); 113 if(mediaType == null || MediaType.UNKNOWN.equals(mediaType)){ 114 object.setMediaType(MediaType.PHOTO); //set all media object MediaType to PHOTO 115 } 116 } 117 return _mediaObjectDAO.update(objects); 118 } 119 120 /** 121 * 122 * @param photo 123 * @return true on success 124 */ 125 public boolean insert(Photo photo){ 126 return insert(PhotoList.getPhotoList(Arrays.asList(photo), null)); 127 } 128 129 /** 130 * 131 * @param photos 132 * @return true on success 133 */ 134 public boolean insert(PhotoList photos){ 135 if(PhotoList.isEmpty(photos)){ 136 LOGGER.debug("No photos given."); 137 return false; 138 } 139 LOGGER.debug("Adding photos..."); 140 List<Photo> p = photos.getPhotos(); 141 Date updated = new Date(); 142 MediaObjectList combined = new MediaObjectList(); 143 for(Photo photo : p){ 144 if(photo.getUpdated() == null){ 145 photo.setUpdated(updated); 146 } 147 148 String guid = UUID.randomUUID().toString(); 149 if(photo.getGUID() != null){ 150 LOGGER.warn("Replacing GUID for photo with existing GUID: "+photo.getGUID()+", new GUID: "+guid); 151 } 152 photo.setGUID(guid); 153 154 MediaObjectList objects = photo.getMediaObjects(); 155 Visibility visibility = photo.getVisibility(); 156 UserIdentity userId = photo.getOwnerUserId(); 157 if(!MediaObjectList.isEmpty(objects)){ 158 for(Iterator<MediaObject> vIter = objects.getMediaObjects().iterator(); vIter.hasNext();){ 159 MediaObject object = vIter.next(); 160 if(!UserIdentity.equals(userId, object.getOwnerUserId())){ 161 LOGGER.warn("Invalid user identity for media object in photo, GUID: "+guid); 162 return false; 163 } 164 if(object.getVisibility() == null){ 165 LOGGER.debug("Object missing visibility value, using photo's visibility."); 166 object.setVisibility(visibility); 167 } 168 combined.addMediaObject(object); 169 } // for 170 } 171 } 172 if(!PhotoList.isValid(photos)){ // check validity after ids have been generated 173 LOGGER.warn("Tried to add invalid photo list."); 174 return false; 175 } 176 SimpleSolrTemplate template = getSolrTemplate(BEAN_ID_SOLR_SERVER); 177 UpdateResponse response = template.addBeans(p); 178 179 if(response.getStatus() != SolrException.ErrorCode.UNKNOWN.code){ 180 LOGGER.warn("Failed to add photos."); 181 return false; 182 } 183 184 if(insert(combined)){ 185 associate(photos); 186 }else{ 187 LOGGER.warn("Insert failed for combined photo list."); 188 return false; 189 } 190 return true; 191 } 192 193 /** 194 * Note: content added through ContentStorage MUST be removed through ContentStorage, removing the metadata directly using this method may cause undefined behavior. 195 * 196 * @param guids 197 * @see service.tut.pori.contentstorage.ContentStorageCore 198 */ 199 public void remove(Collection<String> guids){ 200 if(guids == null || guids.isEmpty()){ 201 LOGGER.debug("Ignored empty GUIDs list."); 202 return; 203 } 204 SimpleSolrTemplate template = getSolrTemplate(BEAN_ID_SOLR_SERVER); 205 if(template.deleteById(guids).getStatus() != SolrException.ErrorCode.UNKNOWN.code){ 206 LOGGER.warn("Failed to delete by GUID."); 207 }else{ 208 Map<String, Set<String>> guidVoidMap = _associationDAO.getAssociationsForGUIDs(guids); 209 if(guidVoidMap == null){ 210 LOGGER.debug("No media objects for the GUID."); 211 }else{ 212 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 213 if(!_mediaObjectDAO.remove(e.getValue())){ // we do not need to de-associate, this will automatically cleanup the association table (by media object dao) 214 LOGGER.warn("Failed to remove media objects for GUID: "+e.getKey()); 215 } 216 } 217 } 218 _photoTaskDAO.remove(guids); 219 } 220 } 221 222 /** 223 * 224 * @param dataGroups optional filter 225 * @param guids optional filter 226 * @param limits optional filter 227 * @param serviceTypes optional filter 228 * @param userIdFilter optional filter 229 * @return list of photos or null if none 230 */ 231 public PhotoList getPhotos(DataGroups dataGroups, Collection<String> guids, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilter){ 232 return getPhotoList(dataGroups, guids, limits, serviceTypes, userIdFilter); 233 } 234 235 /** 236 * 237 * @param dataGroups 238 * @param guids 239 * @param limits 240 * @param serviceTypes 241 * @param userIdFilter 242 * @return list of photos or null if none was found 243 */ 244 private PhotoList getPhotoList(DataGroups dataGroups, Collection<String> guids, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilter){ 245 SolrQueryBuilder solr = new SolrQueryBuilder(null); 246 if(guids != null && !guids.isEmpty()){ 247 LOGGER.debug("Adding guid filter..."); 248 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 249 } 250 if(!ServiceType.isEmpty(serviceTypes)){ 251 LOGGER.debug("Adding service type filter..."); 252 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_SERVICE_ID, ServiceType.toIdArray(serviceTypes))); 253 } 254 255 if(!ArrayUtils.isEmpty(userIdFilter)){ 256 LOGGER.debug("Adding user id filter..."); 257 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_USER_ID, userIdFilter)); 258 } 259 260 solr.setLimits(limits); 261 solr.setSortOptions(DEFAULT_SORT_OPTIONS); 262 setDataGroups(dataGroups, solr); 263 264 QueryResponse response = getSolrTemplate(BEAN_ID_SOLR_SERVER).query(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST)); 265 List<Photo> photos = SimpleSolrTemplate.getList(response, Photo.class); 266 if(photos == null){ 267 LOGGER.debug("No photos"); 268 return null; 269 } 270 271 ResultInfo info = null; 272 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 273 LOGGER.debug("Resolving result info for the requested photos."); 274 info = new ResultInfo(limits.getStartItem(Definitions.ELEMENT_PHOTOLIST), limits.getEndItem(Definitions.ELEMENT_PHOTOLIST), response.getResults().getNumFound()); 275 } 276 277 PhotoList photoList = PhotoList.getPhotoList(photos, info); 278 Map<String, Set<String>> guidVoidMap = _associationDAO.getAssociationsForGUIDs(photoList.getGUIDs()); 279 if(guidVoidMap == null){ 280 LOGGER.debug("No objects for the photos."); 281 }else{ 282 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 283 MediaObjectList objects = _mediaObjectDAO.getMediaObjects(dataGroups, limits, MEDIA_TYPES, null, e.getValue(), null); // do NOT give serviceTypes as filter, we are searching photos with specific serviceTypes, not mediaObjects 284 if(MediaObjectList.isEmpty(objects)){ 285 LOGGER.warn("Could not retrieve objects for GUID: "+e.getKey()); 286 }else{ 287 photoList.getPhoto(e.getKey()).addMediaObjects(objects); 288 } 289 } 290 } 291 292 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_STATUS, dataGroups)){ 293 _photoTaskDAO.getMediaStatus(photoList.getPhotos()); 294 } 295 296 return photoList; 297 } 298 299 /** 300 * 301 * @param dataGroups 302 * @param solr 303 */ 304 private void setDataGroups(DataGroups dataGroups, SolrQueryBuilder solr){ 305 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups)){ 306 LOGGER.debug("Data group "+DataGroups.DATA_GROUP_ALL+" found, will not set field list."); 307 }else if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_BASIC, dataGroups)){ 308 solr.addFields(FIELDS_DATA_GROUP_BASIC); 309 }else{ 310 boolean hasGroup = DataGroups.hasDataGroup(Definitions.DATA_GROUP_VISIBILITY, dataGroups); 311 if(hasGroup){ 312 solr.addField(FIELDS_DATA_GROUP_VISIBILITY); 313 } 314 315 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_DEFAULTS, dataGroups) || DataGroups.isEmpty(dataGroups)){ // if defaults are explicitly given or there are not datagroups 316 solr.addFields(FIELDS_DATA_GROUP_DEFAULTS); 317 }else if(!hasGroup){ 318 LOGGER.debug("No valid data groups, using "+DataGroups.DATA_GROUP_DEFAULTS); 319 solr.addFields(FIELDS_DATA_GROUP_DEFAULTS); 320 } 321 } 322 } 323 324 /** 325 * 326 * @param authenticatedUser optional filter 327 * @param dataGroups optional filter 328 * @param guids optional filter 329 * @param limits optional filter 330 * @param objects optional filter 331 * @param serviceTypes optional filter 332 * @param userIdFilter optional filter 333 * @return the results, or null if none. 334 * @throws IllegalArgumentException on bad search terms 335 */ 336 public PhotoList search(UserIdentity authenticatedUser, DataGroups dataGroups, Collection<String> guids, Limits limits, MediaObjectList objects, EnumSet<ServiceType> serviceTypes, long[] userIdFilter) throws IllegalArgumentException{ 337 SolrQueryBuilder solr = new SolrQueryBuilder(); 338 setDataGroups(dataGroups, solr); 339 solr.setLimits(limits); 340 341 if(guids != null && !guids.isEmpty()){ 342 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 343 } 344 345 Map<String, Set<String>> guidVoidMap = null; 346 if(!MediaObjectList.isEmpty(objects)){ // if media objects have been given as a search term, do a media object look-up first 347 List<String> mediaObjectIds = _mediaObjectDAO.getMediaObjectIds(authenticatedUser, dataGroups, null, null, userIdFilter, objects); // do NOT give serviceTypes as filter, we are searching photos with specific serviceTypes, not mediaObjects 348 if(mediaObjectIds == null){ 349 LOGGER.debug("No objects found."); 350 return null; 351 } 352 353 guidVoidMap = _associationDAO.getAssociationsForMediaObjectIds(mediaObjectIds); 354 if(guidVoidMap == null){ 355 LOGGER.debug("No photos associated with the media object results."); 356 return null; 357 } 358 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guidVoidMap.keySet())); 359 } 360 361 if(!ServiceType.isEmpty(serviceTypes)){ 362 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_SERVICE_ID, ServiceType.toIdArray(serviceTypes))); 363 } 364 365 if(!ArrayUtils.isEmpty(userIdFilter)){ 366 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_USER_ID, userIdFilter)); 367 } 368 369 if(!UserIdentity.isValid(authenticatedUser)){ 370 LOGGER.debug("Invalid authenticated user, limiting search to public content."); 371 solr.addCustomFilter(new AndQueryFilter(Definitions.SOLR_FIELD_VISIBILITY, Visibility.PUBLIC.toInt())); 372 }else{ 373 solr.addCustomFilter(new AndSubQueryFilter(new AbstractQueryFilter[]{new OrQueryFilter(Definitions.SOLR_FIELD_USER_ID, authenticatedUser.getUserId()), new OrQueryFilter(Definitions.SOLR_FIELD_VISIBILITY, Visibility.PUBLIC.toInt())})); 374 } 375 376 solr.setSortOptions(DEFAULT_SORT_OPTIONS); 377 QueryResponse response = getSolrTemplate(BEAN_ID_SOLR_SERVER).query(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST)); 378 List<Photo> photos = SimpleSolrTemplate.getList(response, Photo.class); 379 if(photos == null){ 380 LOGGER.debug("No photos"); 381 return null; 382 } 383 384 ResultInfo info = null; 385 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 386 LOGGER.debug("Resolving result info for the requested photos."); 387 info = new ResultInfo(limits.getStartItem(Definitions.ELEMENT_PHOTOLIST), limits.getEndItem(Definitions.ELEMENT_PHOTOLIST), response.getResults().getNumFound()); 388 } 389 390 PhotoList photoList = PhotoList.getPhotoList(photos, info); 391 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 392 guidVoidMap = _associationDAO.getAssociationsForGUIDs(photoList.getGUIDs()); 393 } 394 395 if(guidVoidMap == null){ 396 LOGGER.debug("No photo-media object associations..."); 397 }else{ 398 LOGGER.debug("Retrieving media objects for the list of photos, if needed..."); 399 for(Entry<String, Set<String>> e : guidVoidMap.entrySet()){ 400 MediaObjectList photoObject = _mediaObjectDAO.getMediaObjects(dataGroups, limits, MEDIA_TYPES, null, e.getValue(), null); // do NOT give serviceTypes as filter, we are searching photos with specific serviceTypes, not mediaObjects 401 if(MediaObjectList.isEmpty(photoObject)){ 402 LOGGER.debug("Could not retrieve objects for GUID: "+e.getKey()); 403 }else{ 404 Photo photo = photoList.getPhoto(e.getKey()); 405 if(photo == null){ 406 LOGGER.warn("Could not find photo, GUID: "+e.getKey()); 407 }else{ 408 photo.addMediaObjects(photoObject); 409 } 410 } 411 } 412 } 413 414 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_STATUS, dataGroups)){ 415 _photoTaskDAO.getMediaStatus(photoList.getPhotos()); 416 } 417 418 return photoList; 419 } 420 421 /** 422 * Sets the owner details (userId) to the given photos, requires that GUID has been set to the photo object 423 * 424 * @param photos 425 * @return true on success, Note: the failed photos will have userId of null, thus, this method can also be used to check the existence of the given photos. 426 */ 427 public boolean setOwners(PhotoList photos){ 428 if(PhotoList.isEmpty(photos)){ 429 LOGGER.debug("Ignored empty "+PhotoList.class.toString()); 430 return true; 431 } 432 433 List<String> guids = photos.getGUIDs(); 434 if(guids == null){ 435 LOGGER.debug("No guids."); 436 return false; 437 } 438 439 SolrQueryBuilder solr = new SolrQueryBuilder(); 440 solr.addFields(FIELDS_SET_OWNERS); 441 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 442 PhotoList found = PhotoList.getPhotoList(getSolrTemplate(BEAN_ID_SOLR_SERVER).queryForList(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST), Photo.class),null); 443 if(PhotoList.isEmpty(found)){ 444 LOGGER.debug("No photos found."); 445 for(Photo photo : photos.getPhotos()){ // null all user ids 446 photo.setOwnerUserId(null); 447 } 448 }else{ 449 for(Photo photo : photos.getPhotos()){ 450 Photo foundPhoto = found.getPhoto(photo.getGUID()); 451 if(foundPhoto != null){ 452 photo.setOwnerUserId(foundPhoto.getOwnerUserId()); 453 }else{ 454 photo.setOwnerUserId(null); 455 } 456 } // for 457 } 458 return true; 459 } 460 461 /** 462 * Note: media objects, if present on the photos, will be updated/inserted, old objects will never be removed 463 * (i.e. this will NOT SET object, but only updates the object details). If you want to remove previously existing 464 * media objects, use {@link service.tut.pori.contentanalysis.MediaObjectDAO}. 465 * 466 * update the photo details IF the given userId has the permission to do so 467 * @param authenticatedUser 468 * @param onlyNewer if true, only the objects which are newer will be added to the database. If the passed object does not have updated set, it will be ignored if onlyNewer is true. 469 * @param photos 470 * @return true on success 471 */ 472 private boolean updatePhotos(UserIdentity authenticatedUser, boolean onlyNewer, PhotoList photos){ 473 if(!UserIdentity.isValid(authenticatedUser)){ 474 LOGGER.warn("Cannot update for null user."); 475 return false; 476 } 477 if(PhotoList.isEmpty(photos)){ 478 LOGGER.debug("Ignored empty photo list."); 479 return true; 480 } 481 if(!PhotoList.isValid(photos)){ 482 LOGGER.warn("Tried update with invalid photo list."); 483 return false; 484 } 485 486 LOGGER.debug("Updating photos..."); 487 List<String> guids = photos.getGUIDs(); 488 SolrQueryBuilder solr = new SolrQueryBuilder(); 489 solr.addFields(FIELDS_UPDATE); 490 if(onlyNewer){ 491 solr.addField(Definitions.SOLR_FIELD_UPDATED); 492 } 493 List<Photo> photoList = photos.getPhotos(); // the original list of photos 494 List<Photo> checkPhotos = new LinkedList<>(photoList); // make copy to preserve the original, this is used to validate that no bad data is left over 495 List<Photo> update = new ArrayList<>(photoList.size()); // contains the photos that will actual be updated 496 Date updated = new Date(); 497 SimpleSolrTemplate t = getSolrTemplate(BEAN_ID_SOLR_SERVER); 498 499 solr.clearCustomFilters(); 500 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 501 PhotoList references = PhotoList.getPhotoList(t.queryForList(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST), Photo.class), null); 502 if(PhotoList.isEmpty(references)){ 503 LOGGER.warn("Could not find the photos requested for update."); 504 return false; 505 } 506 for(Iterator<Photo> iter = checkPhotos.iterator(); iter.hasNext();){ 507 Photo check = iter.next(); 508 String cguid = check.getGUID(); 509 Photo reference = references.getPhoto(cguid); 510 if(reference != null){ 511 UserIdentity rUserId = reference.getOwnerUserId(); 512 if(UserIdentity.equals(rUserId, check.getOwnerUserId()) && UserIdentity.equals(rUserId, authenticatedUser)){ // check that the user is not trying to change the userId of the photo, and that he/she actually owns the photo 513 iter.remove(); // in any case, remove the photo 514 Date cUpdated = check.getUpdated(); 515 if(onlyNewer){ 516 if(cUpdated == null){ 517 LOGGER.warn("Ignored photo without updated timestamp, guid: "+cguid); 518 continue; 519 }else if(cUpdated.before(reference.getUpdated())){ 520 LOGGER.debug("Ignored photo with older timestamp, guid: "+cguid); 521 continue; 522 } 523 }else if(cUpdated == null){ 524 check.setUpdated(updated); // make sure there is an updated timestamp 525 } 526 update.add(check); 527 }else{ // reference photo was found, but user ids did not match 528 LOGGER.warn("Permission denied for user: "+authenticatedUser.getUserId()); 529 return false; 530 } 531 } // do not break, in case there are duplicates in the list 532 } //for 533 534 if(!checkPhotos.isEmpty()){ // something was left over 535 LOGGER.warn("Tried to update non-existing photos, or access was denied for user, id: "+authenticatedUser.getUserId()); 536 return false; 537 } 538 539 if(update.isEmpty()){ 540 LOGGER.debug("Nothing to update."); 541 return true; 542 } 543 544 if(!insertOrUpdateMediaObjects(photos)){ 545 LOGGER.warn("Failed to insert/update media objects."); 546 return false; 547 } 548 549 if(t.addBeans(update).getStatus() == SolrException.ErrorCode.UNKNOWN.code){ 550 return true; 551 }else{ 552 LOGGER.warn("Update failed."); 553 return false; 554 } 555 } 556 557 /** 558 * Update the photos only the given photos have newer timestamps than the ones in the database 559 * 560 * Note: media objects, if present on the photos, will be updated/inserted, old objects will never be removed 561 * (i.e. this will NOT SET object, but only updates the object details). If you want to remove previously existing 562 * media objects, use {@link service.tut.pori.contentanalysis.MediaObjectDAO}. 563 * 564 * @param authenticatedUser 565 * @param photos 566 * @return true on success, note that <i>nothing updated</i> also equals to true if no other errors occur 567 */ 568 public boolean updatePhotosIfNewer(UserIdentity authenticatedUser, PhotoList photos){ 569 return updatePhotos(authenticatedUser, true, photos); 570 } 571 572 /** 573 * Update the photos only the given photos have newer timestamps than the ones in the database 574 * 575 * Note: media objects, if present on the photos, will be updated/inserted, old objects will never be removed 576 * (i.e. this will NOT SET object, but only updates the object details). If you want to remove previously existing 577 * media objects, use {@link service.tut.pori.contentanalysis.MediaObjectDAO}. 578 * 579 * @param authenticatedUser 580 * @param photos 581 * @return true on success 582 */ 583 public boolean updatePhotos(UserIdentity authenticatedUser, PhotoList photos) { 584 return updatePhotos(authenticatedUser, false, photos); 585 } 586 587 /** 588 * resolves ids for the given list of objects and performs insert for non-existing objects and update for others. 589 * 590 * This is a helper method for update methods. 591 * 592 * @param photos 593 * @return true on success 594 */ 595 private boolean insertOrUpdateMediaObjects(PhotoList photos){ 596 MediaObjectList combined = new MediaObjectList(); 597 List<Photo> photoList = photos.getPhotos(); 598 for(Photo photo : photoList){ 599 MediaObjectList objects = photo.getMediaObjects(); 600 if(!MediaObjectList.isEmpty(objects)){ 601 combined.addMediaObjects(objects); 602 } 603 } 604 605 if(MediaObjectList.isEmpty(combined)){ 606 LOGGER.debug("No objects to update or insert."); 607 return true; 608 } 609 610 _mediaObjectDAO.resolveObjectIds(combined); // resolve ids to find out what needs to be updated 611 MediaObjectList update = new MediaObjectList(); 612 MediaObjectList insert = new MediaObjectList(); 613 for(Photo photo : photoList){ 614 MediaObjectList objects = photo.getMediaObjects(); 615 if(!MediaObjectList.isEmpty(objects)){ 616 UserIdentity userId = photo.getOwnerUserId(); 617 for(MediaObject object : objects.getMediaObjects()){ 618 Visibility visibility = photo.getVisibility(); 619 UserIdentity oUserId = object.getOwnerUserId(); 620 if(oUserId == null){ 621 object.setOwnerUserId((oUserId = userId)); 622 } 623 if(!UserIdentity.equals(userId, oUserId)){ 624 LOGGER.warn("For update photo and media object must have the same user."); 625 return false; 626 }else if(StringUtils.isBlank(object.getMediaObjectId())){ // a new one 627 insert.addMediaObject(object); 628 }else{ // old one 629 update.addMediaObject(object); 630 } // else 631 if(object.getVisibility() == null){ 632 LOGGER.debug("Object visibility missing, using photo's visibility."); 633 object.setVisibility(visibility); 634 } 635 } // for 636 } // if 637 } 638 639 boolean associate = true; 640 if(MediaObjectList.isEmpty(insert)){ 641 LOGGER.debug("No new objects to insert."); 642 associate = false; 643 }else if(!insert(insert)){ 644 LOGGER.warn("Failed to insert new objects."); 645 return false; 646 } 647 648 if(MediaObjectList.isEmpty(update)){ 649 associate = (associate ? true : false); 650 LOGGER.debug("No objects to update."); 651 }else if(!_mediaObjectDAO.update(update)){ 652 LOGGER.warn("Failed to update objects."); 653 return false; 654 } 655 656 if(associate){ 657 LOGGER.debug("Associating..."); 658 associate(photos); 659 }else{ 660 LOGGER.debug("Skipping association: not needed."); 661 } 662 663 return true; 664 } 665 666 /** 667 * 668 * @param authenticatedUser 669 * @param guids 670 * @return access details for the photos, or null if none of the photo exist 671 */ 672 public List<AccessDetails> getAccessDetails(UserIdentity authenticatedUser, Collection<String> guids){ 673 SolrQueryBuilder solr = new SolrQueryBuilder(); 674 solr.addFields(FIELDS_GET_ACCESS_DETAILS); 675 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guids)); 676 677 List<Photo> photos = getSolrTemplate(BEAN_ID_SOLR_SERVER).queryForList(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST), Photo.class); 678 if(photos == null){ 679 LOGGER.debug("Guids do not exist."); 680 return null; 681 } 682 List<AccessDetails> details = new ArrayList<>(photos.size()); 683 for(Photo photo : photos){ 684 details.add(AccessDetails.getAccessDetails(authenticatedUser, photo)); 685 } 686 return details; 687 } 688 689 /** 690 * 691 * @param authenticatedUser 692 * @param guid 693 * @return access details for the photo, or null if the photo does not exist 694 */ 695 public AccessDetails getAccessDetails(UserIdentity authenticatedUser, String guid) { 696 SolrQueryBuilder solr = new SolrQueryBuilder(); 697 solr.addFields(FIELDS_GET_ACCESS_DETAILS); 698 solr.addCustomFilter(new AndQueryFilter(SOLR_FIELD_ID, guid)); 699 700 List<Photo> photos = getSolrTemplate(BEAN_ID_SOLR_SERVER).queryForList(solr.toSolrQuery(Definitions.ELEMENT_PHOTOLIST), Photo.class); 701 if(photos == null){ 702 LOGGER.debug("GUID does not exist: "+guid); 703 return null; 704 } 705 return AccessDetails.getAccessDetails(authenticatedUser, photos.iterator().next()); 706 } 707 708 /** 709 * Note: this will NOT remove the media object, and it will only remove the association between the given GUID and media object id. 710 * Use {@link service.tut.pori.contentanalysis.MediaObjectDAO} if you want to remove the media objects. 711 * 712 * @param guid if null, the given media object will be de-associated from all GUIDs 713 * @param mediaObjectId if null, all media objects for the given GUID will be de-associated 714 */ 715 public void deassociate(String guid, String mediaObjectId) { 716 _associationDAO.deassociate(guid, mediaObjectId); 717 } 718 719 /** 720 * create photo-media object associations from the given photo list 721 * 722 * @param photos 723 */ 724 public void associate(PhotoList photos){ 725 _associationDAO.associate(photos.getPhotos()); 726 } 727}