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.reference; 017 018import java.io.IOException; 019import java.util.EnumSet; 020import java.util.Iterator; 021import java.util.List; 022 023import org.apache.commons.lang3.ArrayUtils; 024import org.apache.commons.lang3.StringUtils; 025import org.apache.http.client.methods.HttpPost; 026import org.apache.http.entity.StringEntity; 027import org.apache.http.impl.client.BasicResponseHandler; 028import org.apache.http.impl.client.CloseableHttpClient; 029import org.apache.http.impl.client.HttpClients; 030import org.apache.log4j.Logger; 031 032import service.tut.pori.contentanalysis.AnalysisBackend; 033import service.tut.pori.contentanalysis.AsyncTask.TaskStatus; 034import service.tut.pori.contentanalysis.AsyncTask.TaskType; 035import service.tut.pori.contentanalysis.BackendStatus; 036import service.tut.pori.contentanalysis.BackendStatusList; 037import service.tut.pori.contentanalysis.CAContentCore.ServiceType; 038import service.tut.pori.contentanalysis.CAContentCore.Visibility; 039import service.tut.pori.contentanalysis.Definitions; 040import service.tut.pori.contentanalysis.DeletedPhotoList; 041import service.tut.pori.contentanalysis.DissimilarPhotoList; 042import service.tut.pori.contentanalysis.MediaObject; 043import service.tut.pori.contentanalysis.MediaObjectList; 044import service.tut.pori.contentanalysis.Photo; 045import service.tut.pori.contentanalysis.PhotoFeedbackList; 046import service.tut.pori.contentanalysis.PhotoList; 047import service.tut.pori.contentanalysis.PhotoParameters; 048import service.tut.pori.contentanalysis.PhotoParameters.AnalysisType; 049import service.tut.pori.contentanalysis.PhotoTaskDetails; 050import service.tut.pori.contentanalysis.PhotoTaskResponse; 051import service.tut.pori.contentanalysis.ReferencePhotoList; 052import service.tut.pori.contentanalysis.ResultInfo; 053import service.tut.pori.contentanalysis.SimilarPhotoList; 054import service.tut.pori.contentanalysis.VisualShape; 055import core.tut.pori.context.ServiceInitializer; 056import core.tut.pori.http.RedirectResponse; 057import core.tut.pori.http.Response; 058import core.tut.pori.http.parameters.DataGroups; 059import core.tut.pori.http.parameters.Limits; 060import core.tut.pori.users.UserIdentity; 061import core.tut.pori.utils.XMLFormatter; 062 063/** 064 * The reference implementations for Content Analysis Service. 065 * 066 */ 067public final class CAReferenceCore { 068 private static final CAXMLObjectCreator CREATOR = new CAXMLObjectCreator(null); 069 private static final Limits DEFAULT_LIMITS = new Limits(0, 0); // default number of limits for references 070 private static final DataGroups DATAGROUPS_ALL = new DataGroups(DataGroups.DATA_GROUP_ALL); 071 private static final DataGroups DATAGROUPS_BACKEND_RESPONSE = new DataGroups(CAXMLObjectCreator.DATA_GROUP_BACKEND_RESPONSE); // data groups for add task callback 072 private static final String EXAMPLE_URI = "http://otula.pori.tut.fi/d2i/leijona_album_art.jpg"; 073 private static final Logger LOGGER = Logger.getLogger(CAReferenceCore.class); 074 075 /** 076 * 077 */ 078 private CAReferenceCore(){ 079 // nothing needed 080 } 081 082 /** 083 * This performs a trivial check for the task contents, checking for the presence of a few key values. 084 * The validity of the actual task contents will not checked. 085 * 086 * @param response 087 */ 088 public static void taskFinished(PhotoTaskResponse response) { 089 Integer tBackendId = response.getBackendId(); 090 if(tBackendId == null){ 091 throw new IllegalArgumentException("Invalid backendId: "+tBackendId); 092 } 093 Long tTaskId = response.getTaskId(); 094 if(tTaskId == null){ 095 throw new IllegalArgumentException("Invalid taskId: "+tTaskId); 096 } 097 098 TaskStatus status = response.getStatus(); 099 if(status == null){ 100 throw new IllegalArgumentException("TaskStatus is invalid or missing."); 101 } 102 103 TaskType type = response.getTaskType(); 104 if(type == null){ 105 throw new IllegalArgumentException("TaskType is invalid or missing."); 106 } 107 108 try{ 109 switch(type){ 110 case ANALYSIS: 111 PhotoList pl = response.getPhotoList(); 112 if(!PhotoList.isEmpty(pl)){ 113 if(!PhotoList.isValid(pl)){ 114 LOGGER.warn("Invalid "+Definitions.ELEMENT_PHOTOLIST); 115 } 116 for(Photo p : response.getPhotoList().getPhotos()){ 117 MediaObjectList vObjects = p.getMediaObjects(); 118 if(MediaObjectList.isEmpty(vObjects)){ 119 LOGGER.info("No media objects for photo, GUID: "+p.getGUID()); 120 }else if(!MediaObjectList.isValid(vObjects)){ 121 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_MEDIA_OBJECTLIST); 122 } 123 } 124 } 125 break; 126 case BACKEND_FEEDBACK: // should not have any content, so accept anything 127 break; 128 case FEEDBACK: 129 if(!PhotoList.isValid(response.getPhotoList())){ 130 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_PHOTOLIST); 131 } 132 break; 133 case SEARCH: 134 default: 135 throw new IllegalArgumentException("Tasks of type: "+type.name()+" are not supported by this validator."); 136 } 137 }catch(ClassCastException ex){ 138 LOGGER.debug(ex, ex); 139 throw new IllegalArgumentException("Task content data was not of the expected type."); 140 } 141 } 142 143 /** 144 * This performs a trivial check for the task contents, checking for the presence of a few key values. 145 * The validity of the actual task contents will not be checked. 146 * 147 * @param taskDetails 148 */ 149 public static void addTask(PhotoTaskDetails taskDetails) { 150 Integer tBackendId = taskDetails.getBackendId(); 151 if(tBackendId == null){ 152 throw new IllegalArgumentException("Invalid backendId: "+tBackendId); 153 } 154 Long tTaskId = taskDetails.getTaskId(); 155 if(tTaskId == null){ 156 throw new IllegalArgumentException("Invalid taskId: "+tTaskId); 157 } 158 159 String uri = taskDetails.getCallbackUri(); 160 if(StringUtils.isBlank(uri)){ 161 throw new IllegalArgumentException("Invalid callbackUri: "+uri); 162 } 163 164 TaskType type = taskDetails.getTaskType(); 165 if(type == null){ 166 throw new IllegalArgumentException("TaskType is invalid or missing."); 167 } 168 169 switch(type){ 170 case BACKEND_FEEDBACK: 171 if(!PhotoList.isValid(taskDetails.getPhotoList())){ 172 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_PHOTOLIST); 173 } 174 addTaskAsyncCallback(taskDetails, null); 175 break; 176 case ANALYSIS: 177 PhotoList photoList = taskDetails.getPhotoList(); 178 if(!PhotoList.isValid(photoList)){ 179 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_PHOTOLIST); 180 } 181 if(taskDetails.getSimilarPhotoList() != null || taskDetails.getDissimilarPhotoList() != null || taskDetails.getDeletedPhotoList() != null){ 182 throw new IllegalArgumentException(Definitions.ELEMENT_SIMILAR_PHOTOLIST+", "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST+" or "+Definitions.ELEMENT_DELETED_PHOTOLIST+" cannot appear in tasks of type: "+TaskType.ANALYSIS.name()+" or "+TaskType.SEARCH.name()); 183 } 184 PhotoParameters ap = taskDetails.getTaskParameters(); 185 for(Photo photo : photoList.getPhotos()){ 186 MediaObjectList mediaObjects = CREATOR.createMediaObjectList((ap == null ? null : ap.getAnalysisTypes()), DATAGROUPS_BACKEND_RESPONSE, DEFAULT_LIMITS, null); 187 for(service.tut.pori.contentanalysis.MediaObject o : mediaObjects.getMediaObjects()){ 188 o.setOwnerUserId(null); 189 o.setBackendId(tBackendId); 190 o.setMediaObjectId(null); 191 o.setServiceType(null); 192 } 193 photo.addackendStatus(new BackendStatus(new AnalysisBackend(tBackendId), TaskStatus.COMPLETED)); 194 photo.addMediaObjects(mediaObjects); 195 } 196 addTaskAsyncCallback(taskDetails, photoList); 197 break; 198 case FEEDBACK: 199 if(taskDetails.getReferencePhotoList() != null){ 200 if(taskDetails.getSimilarPhotoList() == null && taskDetails.getDissimilarPhotoList() == null){ 201 throw new IllegalArgumentException(Definitions.ELEMENT_REFERENCE_PHOTOLIST+" requires at least one of "+Definitions.ELEMENT_SIMILAR_PHOTOLIST+" or "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST); 202 } 203 if(taskDetails.getDeletedPhotoList() != null){ 204 throw new IllegalArgumentException(Definitions.ELEMENT_DELETED_PHOTOLIST+" cannot appear together with "+Definitions.ELEMENT_REFERENCE_PHOTOLIST); 205 } 206 if(taskDetails.getPhotoList() != null){ 207 throw new IllegalArgumentException(Definitions.ELEMENT_PHOTOLIST+" cannot appear together with "+Definitions.ELEMENT_REFERENCE_PHOTOLIST); 208 } 209 }else if(taskDetails.getPhotoList() != null){ 210 if(taskDetails.getSimilarPhotoList() != null || taskDetails.getDissimilarPhotoList() != null || taskDetails.getDeletedPhotoList() != null){ 211 throw new IllegalArgumentException(Definitions.ELEMENT_PHOTOLIST+" cannot appear together with "+Definitions.ELEMENT_DELETED_PHOTOLIST+", "+Definitions.ELEMENT_SIMILAR_PHOTOLIST+" or "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST); 212 } 213 }else if(taskDetails.getDeletedPhotoList() != null){ 214 if(taskDetails.getSimilarPhotoList() != null || taskDetails.getDissimilarPhotoList() != null){ 215 throw new IllegalArgumentException(Definitions.ELEMENT_DELETED_PHOTOLIST+" cannot appear together with "+Definitions.ELEMENT_SIMILAR_PHOTOLIST+" or "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST); 216 } 217 }else{ 218 throw new IllegalArgumentException("At least one of "+Definitions.ELEMENT_PHOTOLIST+", "+ Definitions.ELEMENT_REFERENCE_PHOTOLIST+", "+Definitions.ELEMENT_DELETED_PHOTOLIST+", "+Definitions.ELEMENT_SIMILAR_PHOTOLIST+" or "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST+" must be present"); 219 } 220 221 if(taskDetails.getPhotoList() != null && !PhotoList.isValid(taskDetails.getPhotoList())){ 222 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_PHOTOLIST); 223 } 224 225 if(taskDetails.getSimilarPhotoList() != null && !SimilarPhotoList.isValid(taskDetails.getSimilarPhotoList())){ 226 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_SIMILAR_PHOTOLIST); 227 } 228 229 if(taskDetails.getDissimilarPhotoList() != null && !DissimilarPhotoList.isValid(taskDetails.getDissimilarPhotoList())){ 230 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_DISSIMILAR_PHOTOLIST); 231 } 232 233 if(taskDetails.getDeletedPhotoList() != null && !DeletedPhotoList.isValid(taskDetails.getDeletedPhotoList())){ 234 throw new IllegalArgumentException("Invalid "+Definitions.ELEMENT_DELETED_PHOTOLIST); 235 } 236 addTaskAsyncCallback(taskDetails, null); 237 break; 238 case SEARCH: 239 LOGGER.warn("Accepting task of type "+TaskType.SEARCH.name()+" without validation."); 240 break; 241 default: 242 throw new IllegalArgumentException("Tasks of type: "+type.name()+" are not supported by this validator."); 243 } 244 } 245 246 /** 247 * Call asynchronously the callback given in the details, returning an example task response 248 * 249 * @param details 250 * @param photoList 251 * @see service.tut.pori.contentanalysis.PhotoTaskResponse 252 */ 253 public static void addTaskAsyncCallback(PhotoTaskDetails details, PhotoList photoList) { 254 HttpPost post = new HttpPost(details.getCallbackUri()); 255 PhotoTaskResponse r = new PhotoTaskResponse(); 256 r.setBackendId(details.getBackendId()); 257 r.setTaskId(details.getTaskId()); 258 r.setStatus(TaskStatus.COMPLETED); 259 r.setTaskType(details.getTaskType()); 260 r.setPhotoList(photoList); 261 post.setEntity(new StringEntity((new XMLFormatter()).toString(r), core.tut.pori.http.Definitions.ENCODING_UTF8)); 262 executeAsyncCallback(post); 263 } 264 265 /** 266 * 267 * @param post 268 */ 269 public static void executeAsyncCallback(final HttpPost post){ 270 ServiceInitializer.getExecutorHandler().getExecutor().execute( 271 new Runnable() { 272 @Override 273 public void run() { 274 try (CloseableHttpClient client = HttpClients.createDefault()) { 275 LOGGER.debug("Waiting "+service.tut.pori.contentanalysis.reference.Definitions.ASYNC_CALLBACK_DELAY/1000+" seconds..."); 276 Thread.sleep(service.tut.pori.contentanalysis.reference.Definitions.ASYNC_CALLBACK_DELAY); 277 278 LOGGER.debug("Calling uri: "+post.getURI().toString()); 279 LOGGER.debug("Server responded: "+client.execute(post, new BasicResponseHandler())); 280 } catch (IOException | InterruptedException ex) { 281 LOGGER.error(ex, ex); 282 } 283 } 284 }); 285 } 286 287 /** 288 * 289 * @param backendId 290 * @param taskId 291 * @param dataGroups 292 * @param limits 293 * @return an example response for the given values 294 */ 295 public static Response queryTaskDetails(Integer backendId, Long taskId, DataGroups dataGroups, Limits limits) { 296 if(limits.getMaxItems(Definitions.ELEMENT_PHOTOLIST) >= Limits.DEFAULT_MAX_ITEMS){ 297 LOGGER.debug("Reseting limits for "+Definitions.ELEMENT_PHOTOLIST+": max items was "+Limits.DEFAULT_MAX_ITEMS); 298 limits.setTypeLimits(DEFAULT_LIMITS.getStartItem(Definitions.ELEMENT_PHOTOLIST), DEFAULT_LIMITS.getEndItem(Definitions.ELEMENT_PHOTOLIST), Definitions.ELEMENT_PHOTOLIST); // startItem makes no difference for random 299 } 300 301 if(limits.getMaxItems(Definitions.ELEMENT_MEDIA_OBJECTLIST) >= Limits.DEFAULT_MAX_ITEMS){ 302 LOGGER.debug("Reseting limits for "+Definitions.ELEMENT_MEDIA_OBJECTLIST+": max items was "+Limits.DEFAULT_MAX_ITEMS); 303 limits.setTypeLimits(DEFAULT_LIMITS.getStartItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), DEFAULT_LIMITS.getEndItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), Definitions.ELEMENT_MEDIA_OBJECTLIST); // startItem makes no difference for random 304 } 305 limits.setTypeLimits(-1, -1, null); // disable all other photo lists 306 return new Response(CREATOR.createPhotoTaskDetails(backendId, dataGroups, limits, taskId, TaskType.ANALYSIS)); 307 } 308 309 /** 310 * 311 * @param taskId 312 * @param dataGroups 313 * @param limits 314 * @return an example response for the given values 315 */ 316 public static Response queryTaskStatus(Long taskId, DataGroups dataGroups, Limits limits) { 317 if(limits.getMaxItems() >= Limits.DEFAULT_MAX_ITEMS){ 318 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 319 limits = DEFAULT_LIMITS; // startItem makes no difference for random 320 } 321 return new Response(CREATOR.createTaskResponse(null, dataGroups, limits, taskId, TaskType.ANALYSIS)); 322 } 323 324 /** 325 * back-end API variant 326 * @param analysisTypes 327 * @param serviceTypes 328 * @param url 329 * @param userIds 330 * @param dataGroups 331 * @param limits 332 * @return an example response for the given values 333 */ 334 public static Response searchSimilarByContent(EnumSet<AnalysisType> analysisTypes, String url, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIds) { 335 if(limits.getMaxItems() >= Limits.DEFAULT_MAX_ITEMS){ 336 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 337 limits = DEFAULT_LIMITS; // startItem makes no difference for random 338 } 339 return new Response(CREATOR.createSearchResults(null, dataGroups, limits, serviceTypes, userIds, null)); 340 } 341 342 /** 343 * Back-end API variation of search by GUID 344 * 345 * @param analysisTypes not used 346 * @param serviceTypes 347 * @param guid 348 * @param userIds 349 * @param dataGroups 350 * @param limits 351 * @return an example response for the given values 352 */ 353 public static Response searchSimilarById(EnumSet<AnalysisType> analysisTypes, String guid, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIds) { 354 if(limits.getMaxItems() >= Limits.DEFAULT_MAX_ITEMS){ 355 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 356 limits = DEFAULT_LIMITS; // startItem makes no difference for random 357 } 358 return new Response(CREATOR.createSearchResults(guid, dataGroups, limits, serviceTypes, userIds, null)); 359 } 360 361 /** 362 * 363 * @param authenticatedUser 364 * @param keywords 365 * @param dataGroups 366 * @param limits 367 * @param serviceTypes 368 * @param userIdFilters 369 * @return an example response for the given values 370 */ 371 public static Response searchByKeyword(UserIdentity authenticatedUser, List<String> keywords, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilters) { 372 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 373 if(limits.getMaxItems() >= Limits.DEFAULT_MAX_ITEMS){ 374 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 375 limits = DEFAULT_LIMITS; // startItem makes no difference for random 376 } 377 return new Response(CREATOR.createSearchResults(null, dataGroups, limits, serviceTypes, userIdFilters, MediaObjectList.getMediaObjectListFromKeywords(keywords))); 378 } 379 380 /** 381 * Client API variation of search by GUID 382 * 383 * @param authenticatedUser 384 * @param analysisTypes 385 * @param guid 386 * @param dataGroups 387 * @param limits 388 * @param serviceTypes 389 * @param userIdFilters 390 * @return an example response for the given values 391 */ 392 public static Response searchSimilarById(UserIdentity authenticatedUser, EnumSet<AnalysisType> analysisTypes, String guid, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilters) { 393 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 394 return searchSimilarById(analysisTypes, guid, dataGroups, limits, serviceTypes, userIdFilters); // we can directly call the back-end API reference implementation 395 } 396 397 /** 398 * 399 * @param authenticatedUser 400 * @param serviceId 401 * @param guid 402 * @return an example response for the given values 403 */ 404 public static RedirectResponse generateTargetUrl(UserIdentity authenticatedUser, ServiceType serviceId, String guid) { 405 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 406 return new RedirectResponse(EXAMPLE_URI); 407 } 408 409 /** 410 * 411 * @param authenticatedUser 412 * @param objects 413 * @param dataGroups 414 * @param limits 415 * @param serviceTypes 416 * @param userIdFilters 417 * @return an example response for the given values 418 */ 419 public static Response similarPhotosByObject(UserIdentity authenticatedUser, MediaObjectList objects, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilters) { 420 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 421 if(limits.getMaxItems(Definitions.ELEMENT_PHOTOLIST) >= Limits.DEFAULT_MAX_ITEMS){ 422 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 423 limits = DEFAULT_LIMITS; // startItem makes no difference for random 424 } 425 return new Response(CREATOR.createSearchResults(null, dataGroups, limits, serviceTypes, userIdFilters, objects)); 426 } 427 428 /** 429 * 430 * @param photoList 431 * @param authenticatedUser 432 */ 433 public static void updatePhotos(UserIdentity authenticatedUser, PhotoList photoList) { 434 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 435 if(!PhotoList.isValid(photoList)){ 436 throw new IllegalArgumentException("Received empty or invalid photoList."); 437 } 438 } 439 440 /** 441 * client API variant 442 * 443 * @param url 444 * @param serviceTypes 445 * @param authenticatedUser 446 * @param analysisTypes 447 * @param userIdFilters 448 * @param dataGroups 449 * @param limits 450 * @return an example response for the given values 451 */ 452 public static Response searchByContent(UserIdentity authenticatedUser, EnumSet<AnalysisType> analysisTypes, String url, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilters) { 453 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 454 if(limits.getMaxItems(Definitions.ELEMENT_PHOTOLIST) >= Limits.DEFAULT_MAX_ITEMS){ 455 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 456 limits = DEFAULT_LIMITS; // startItem makes no difference for random 457 } 458 return searchSimilarByContent(analysisTypes, url, dataGroups, limits, serviceTypes, userIdFilters); // we can use the back-end variant 459 } 460 461 /** 462 * 463 * @param authenticatedUser 464 * @param guids 465 * @param dataGroups 466 * @param limits 467 * @param serviceTypes 468 * @param userIdFilter 469 * @return an example response for the given values 470 */ 471 public static Response getPhotos(UserIdentity authenticatedUser, List<String> guids, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIdFilter) { 472 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 473 if(limits.getMaxItems(Definitions.ELEMENT_PHOTOLIST) >= Limits.DEFAULT_MAX_ITEMS){ 474 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 475 limits = DEFAULT_LIMITS; // startItem makes no difference for random 476 } 477 int userIdCount = (ArrayUtils.isEmpty(userIdFilter) ? 0 : userIdFilter.length); 478 PhotoList list = CREATOR.createPhotoList(null, dataGroups, limits, serviceTypes, null); 479 if(list != null && guids != null && !guids.isEmpty()){ 480 for(Iterator<Photo> iter = list.getPhotos().iterator();iter.hasNext();){ // remove all extra GUIDs, we could also modify the limit parameter, but for this testing method, the implementation does not matter 481 Photo photo = iter.next(); 482 if(guids.isEmpty()){ // we have used up all given GUIDs 483 iter.remove(); 484 }else{ 485 photo.setGUID(guids.remove(0)); 486 photo.setVisibility(Visibility.PUBLIC); // there could also be private photos for the authenticated user, but to make sure the results are valid, return only PUBLIC photos 487 } 488 if(userIdCount > 1){ 489 photo.setOwnerUserId(new UserIdentity(userIdFilter[CREATOR.getRandom().nextInt(userIdCount)])); 490 } 491 } // for 492 } 493 return new Response(list); 494 } 495 496 /** 497 * 498 * @param authenticatedUser 499 * @param feedbackList 500 */ 501 public static void similarityFeedback(UserIdentity authenticatedUser, PhotoFeedbackList feedbackList) { 502 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 503 if(!PhotoFeedbackList.isValid(feedbackList)){ 504 throw new IllegalArgumentException("Received empty or invalid feedbackList."); 505 } 506 } 507 508 /** 509 * 510 * @param authenticatedUser 511 * @param dataGroups 512 * @param limits 513 * @param serviceTypes 514 * @param mediaObjectIds 515 * @return an example response for the given values 516 */ 517 public static Response getMediaObjects(UserIdentity authenticatedUser, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, List<String> mediaObjectIds) { 518 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 519 520 if(limits.getMaxItems(Definitions.ELEMENT_MEDIA_OBJECTLIST) >= Limits.DEFAULT_MAX_ITEMS){ 521 LOGGER.debug("Reseting limits for "+Definitions.ELEMENT_MEDIA_OBJECTLIST+": max items was "+Limits.DEFAULT_MAX_ITEMS); 522 limits.setTypeLimits(DEFAULT_LIMITS.getStartItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), DEFAULT_LIMITS.getEndItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), Definitions.ELEMENT_MEDIA_OBJECTLIST); // startItem makes no difference for random 523 } 524 525 MediaObjectList mediaObjects = CREATOR.createMediaObjectList(null, dataGroups, limits, serviceTypes); 526 if(!MediaObjectList.isEmpty(mediaObjects) && mediaObjectIds != null && !mediaObjectIds.isEmpty()){ 527 Iterator<String> voidIter = mediaObjectIds.iterator(); 528 for(Iterator<MediaObject> voIter = mediaObjects.getMediaObjects().iterator(); voIter.hasNext();){ 529 MediaObject vo = voIter.next(); 530 if(voidIter.hasNext()){ 531 vo.setMediaObjectId(voidIter.next()); 532 }else{ 533 voIter.remove(); // we have used all available media object ids 534 } 535 } // for 536 } 537 return new Response(mediaObjects); 538 } 539 540 /** 541 * 542 * @param authenticatedUser 543 * @param guids 544 */ 545 public static void deletePhotos(UserIdentity authenticatedUser, List<String> guids) { 546 LOGGER.info((authenticatedUser == null ? "No logged in user." : "Ignoring the logged in user, id: "+authenticatedUser.getUserId())); // only notify of the logged in status 547 } 548 549 /** 550 * 551 * @param limits 552 * @return generated feedback list 553 */ 554 public static PhotoFeedbackList generateFeedbackList(Limits limits) { 555 return CREATOR.createFeedbackList(limits); 556 } 557 558 /** 559 * 560 * @param dataGroups has only effect for photo list, if null or empty, data groups {@value core.tut.pori.http.parameters.DataGroups#DATA_GROUP_ALL} 561 * @param limits 562 * @param cls 563 * @return generated photo list 564 * @throws IllegalArgumentException 565 */ 566 public static PhotoList generatePhotoList(DataGroups dataGroups, Limits limits, Class<? extends PhotoList> cls) throws IllegalArgumentException { 567 if(PhotoList.class == cls){ 568 return CREATOR.createPhotoList(null, (DataGroups.isEmpty(dataGroups) ? DATAGROUPS_ALL : dataGroups), limits, null, null); 569 }else if(DeletedPhotoList.class == cls){ 570 return CREATOR.createDeletedPhotoList(limits); 571 }else if(DissimilarPhotoList.class == cls){ 572 return CREATOR.createDissimilarPhotoList(limits); 573 }else if(SimilarPhotoList.class == cls){ 574 return CREATOR.createSimilarPhotoList(limits); 575 }else if(ReferencePhotoList.class == cls){ 576 return CREATOR.createReferencePhotoList(limits); 577 }else{ 578 throw new IllegalArgumentException("Unsupported class : "+cls); 579 } 580 } 581 582 /** 583 * 584 * @param dataGroups has only effect for photo list, if null or empty, data groups {@value core.tut.pori.http.parameters.DataGroups#DATA_GROUP_ALL} 585 * @param limits 586 * @return generated media object list 587 */ 588 public static MediaObjectList generateMediaObjectList(DataGroups dataGroups, Limits limits) { 589 return CREATOR.createMediaObjectList(null, (DataGroups.isEmpty(dataGroups) ? DATAGROUPS_ALL : dataGroups), limits, null); 590 } 591 592 /** 593 * 594 * @param limits 595 * @return generated task response 596 */ 597 public static PhotoTaskResponse generateTaskResponse(Limits limits) { 598 return CREATOR.createTaskResponse(null, DATAGROUPS_BACKEND_RESPONSE, limits, null, TaskType.ANALYSIS); 599 } 600 601 /** 602 * 603 * @param dataGroups has only effect for photo list, if null or empty, data groups {@value core.tut.pori.http.parameters.DataGroups#DATA_GROUP_ALL} 604 * @param limits 605 * @param taskType 606 * @return generated task details 607 * @throws IllegalArgumentException 608 */ 609 public static PhotoTaskDetails generatePhotoTaskDetails(DataGroups dataGroups, Limits limits, TaskType taskType) throws IllegalArgumentException { 610 switch(taskType){ 611 case ANALYSIS: 612 case BACKEND_FEEDBACK: 613 case FEEDBACK: 614 break; 615 default: 616 throw new IllegalArgumentException("Unsupported task type: "+taskType.name()); 617 } 618 limits.setTypeLimits(-1, -1, Definitions.ELEMENT_BACKEND_STATUS_LIST); // do not add back-end status list 619 return CREATOR.createPhotoTaskDetails((DataGroups.isEmpty(dataGroups) ? DATAGROUPS_ALL : dataGroups), limits, taskType); 620 } 621 622 /** 623 * 624 * @param limits 625 * @return generated back-end status list 626 */ 627 public static BackendStatusList generateBackendStatusList(Limits limits) { 628 int count = limits.getMaxItems(); 629 if(count >= Limits.DEFAULT_MAX_ITEMS){ 630 LOGGER.debug("Reseting limits: Default max items was "+Limits.DEFAULT_MAX_ITEMS); 631 count = DEFAULT_LIMITS.getMaxItems(Definitions.ELEMENT_BACKEND_STATUS_LIST); // startItem makes no difference for random 632 } 633 return CREATOR.createBackendStatusContainer(count); 634 } 635 636 /** 637 * 638 * @param dataGroups has only effect for photo list, if null or empty, data groups {@value core.tut.pori.http.parameters.DataGroups#DATA_GROUP_ALL} 639 * @return generated photo 640 */ 641 public static Photo generatePhoto(DataGroups dataGroups) { 642 return CREATOR.createPhoto(null, (DataGroups.isEmpty(dataGroups) ? DATAGROUPS_ALL : dataGroups), DEFAULT_LIMITS, null, null); 643 } 644 645 /** 646 * 647 * @return generated back-end status 648 */ 649 public static BackendStatus generateBackendStatus() { 650 return CREATOR.createBackendStatus(CREATOR.createBackendId()); 651 } 652 653 /** 654 * 655 * @return generated result info 656 */ 657 public static ResultInfo generateResultInfo() { 658 return CREATOR.createResultInfo(); 659 } 660 661 /** 662 * 663 * @param dataGroups has only effect for photo list, if null or empty, data groups {@value core.tut.pori.http.parameters.DataGroups#DATA_GROUP_ALL} 664 * @return generated media object 665 */ 666 public static MediaObject generateMediaObject(DataGroups dataGroups) { 667 return CREATOR.createMediaObject(null, (DataGroups.isEmpty(dataGroups) ? DATAGROUPS_ALL : dataGroups), null); 668 } 669 670 /** 671 * 672 * @return generated visual shape 673 */ 674 public static VisualShape generateVisualShape() { 675 return CREATOR.createVisualShape(); 676 } 677 678 /** 679 * 680 * @return generated analysis back-end 681 */ 682 public static AnalysisBackend generateAnalysisBackend() { 683 return CREATOR.createAnalysisBackend(); 684 } 685 686 /** 687 * 688 * @return randomly generated analysis parameters 689 */ 690 public static PhotoParameters generateAnalysisParameters() { 691 return CREATOR.createAnalysisParameters(); 692 } 693}