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.lang.reflect.Field; 019import java.sql.Connection; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.sql.Statement; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Date; 026import java.util.EnumSet; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.List; 030import java.util.Random; 031import java.util.UUID; 032 033import javax.sql.DataSource; 034 035import org.apache.commons.lang3.ArrayUtils; 036import org.apache.commons.lang3.RandomStringUtils; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.commons.lang3.tuple.Pair; 039import org.apache.log4j.Logger; 040import org.springframework.beans.BeansException; 041import org.springframework.context.support.ClassPathXmlApplicationContext; 042import org.springframework.jdbc.CannotGetJdbcConnectionException; 043import org.springframework.jdbc.datasource.DataSourceUtils; 044 045import service.tut.pori.contentanalysis.AbstractTaskDetails; 046import service.tut.pori.contentanalysis.AnalysisBackend; 047import service.tut.pori.contentanalysis.AnalysisBackend.Capability; 048import service.tut.pori.contentanalysis.PhotoParameters.AnalysisType; 049import service.tut.pori.contentanalysis.PhotoParameters; 050import service.tut.pori.contentanalysis.AsyncTask.TaskStatus; 051import service.tut.pori.contentanalysis.AsyncTask.TaskType; 052import service.tut.pori.contentanalysis.BackendStatus; 053import service.tut.pori.contentanalysis.BackendStatusList; 054import service.tut.pori.contentanalysis.CAContentCore.ServiceType; 055import service.tut.pori.contentanalysis.CAContentCore.Visibility; 056import service.tut.pori.contentanalysis.Definitions; 057import service.tut.pori.contentanalysis.DeletedPhotoList; 058import service.tut.pori.contentanalysis.DissimilarPhotoList; 059import service.tut.pori.contentanalysis.PhotoFeedbackList; 060import service.tut.pori.contentanalysis.Photo; 061import service.tut.pori.contentanalysis.PhotoList; 062import service.tut.pori.contentanalysis.PhotoTaskDetails; 063import service.tut.pori.contentanalysis.PhotoTaskResponse; 064import service.tut.pori.contentanalysis.ReferencePhotoList; 065import service.tut.pori.contentanalysis.ResultInfo; 066import service.tut.pori.contentanalysis.SimilarPhotoList; 067import service.tut.pori.contentanalysis.MediaObject; 068import service.tut.pori.contentanalysis.MediaObject.ConfirmationStatus; 069import service.tut.pori.contentanalysis.MediaObject.MediaObjectType; 070import service.tut.pori.contentanalysis.MediaObjectList; 071import service.tut.pori.contentanalysis.VisualShape; 072import service.tut.pori.contentanalysis.VisualShape.VisualShapeType; 073import core.tut.pori.context.ServiceInitializer; 074import core.tut.pori.http.parameters.DataGroups; 075import core.tut.pori.http.parameters.Limits; 076import core.tut.pori.users.UserIdentity; 077import core.tut.pori.utils.MediaUrlValidator.MediaType; 078 079/** 080 * 081 * Class that can be used to created example objects/object lists. 082 * 083 */ 084public class CAXMLObjectCreator { 085 /** special datagroup used for testing, creates tags with data generally returned by an analysis backend */ 086 public static final String DATA_GROUP_BACKEND_RESPONSE = "backend_response"; 087 private static final int BACKEND_STATUS_MAX = 5; 088 private static final Limits LIMITS_NO_MEDIA_OBJECTS; 089 static{ 090 LIMITS_NO_MEDIA_OBJECTS = new Limits(0,0); 091 LIMITS_NO_MEDIA_OBJECTS.setTypeLimits(0, -1, Definitions.ELEMENT_MEDIA_OBJECTLIST); 092 } 093 private static final Logger LOGGER = Logger.getLogger(CAXMLObjectCreator.class); 094 private static final int TEXT_LENGTH = 64; 095 private Random _random = null; 096 private long _seed = 0; 097 098 /** 099 * 100 * @param seed for random generator, or null to use default (system time in nanoseconds) 101 */ 102 public CAXMLObjectCreator(Long seed){ 103 if(seed == null){ 104 seed = System.nanoTime(); 105 } 106 _seed = seed; 107 _random = new Random(seed); 108 } 109 110 /** 111 * @return the seed 112 */ 113 public long getSeed() { 114 return _seed; 115 } 116 117 /** 118 * @return the random 119 */ 120 public Random getRandom() { 121 return _random; 122 } 123 124 /** 125 * TaskType will not be set if already set 126 * @param backendId 127 * @param details 128 * @param taskId 129 * @param taskType if null, generated randomly 130 */ 131 public void populateAbstractTaskDetails(Integer backendId, AbstractTaskDetails details, Long taskId, TaskType taskType){ 132 details.setTaskType((taskType == null ? createTaskType() : taskType)); 133 details.setTaskId(taskId); 134 details.setBackendId(backendId); 135 details.setUserId(createUserIdentity()); 136 } 137 138 /** 139 * 140 * @return randomly generated task type 141 */ 142 public TaskType createTaskType(){ 143 TaskType[] values = TaskType.values(); 144 return values[_random.nextInt(values.length)]; 145 } 146 147 /** 148 * 149 * @return randomly generated user identity 150 */ 151 public UserIdentity createUserIdentity(){ 152 return new UserIdentity(Math.abs(_random.nextLong())); 153 } 154 155 /** 156 * 157 * @return randomly generated task status 158 */ 159 public TaskStatus createTaskStatus(){ 160 return TaskStatus.fromInt(_random.nextInt(5)+1); 161 } 162 163 /** 164 * 165 * @return serviceType valid for photos (not facebook jazz) 166 */ 167 public ServiceType createPhotoServiceType(){ 168 ServiceType type = null; 169 ServiceType[] types = ServiceType.values(); 170 do{ 171 type = types[_random.nextInt(types.length)]; 172 }while(type != ServiceType.PICASA_STORAGE_SERVICE && type != ServiceType.FACEBOOK_PHOTO && type != ServiceType.TWITTER_PHOTO); 173 return type; 174 } 175 176 /** 177 * 178 * @param backendId 179 * @return randomly generated back-end status with the given back-end id 180 */ 181 public BackendStatus createBackendStatus(Integer backendId){ 182 BackendStatus status = new BackendStatus(createAnalysisBackend(), createTaskStatus()); 183 status.setMessage(RandomStringUtils.randomAlphanumeric(TEXT_LENGTH)); 184 return status; 185 } 186 187 /** 188 * 189 * @param statusCount 190 * @return randomly generated back-end status list with the given amount of statuses 191 */ 192 public BackendStatusList createBackendStatusContainer(int statusCount){ 193 if(statusCount < 1){ 194 LOGGER.warn("count < 1"); 195 return null; 196 } 197 BackendStatusList c = new BackendStatusList(); 198 for(int i=0;i<statusCount;++i){ 199 c.setBackendStatus(createBackendStatus(createBackendId())); 200 } 201 return c; 202 } 203 204 /** 205 * 206 * @return randomly generated back-end id 207 */ 208 public int createBackendId(){ 209 return Math.abs(_random.nextInt()); 210 } 211 212 /** 213 * 214 * @return randomly generated analysis back-end 215 */ 216 public AnalysisBackend createAnalysisBackend(){ 217 AnalysisBackend end = new AnalysisBackend(); 218 end.setBackendId(Math.abs(_random.nextInt())); 219 end.setAnalysisUri(createRandomUrl()); 220 end.setEnabled(_random.nextBoolean()); 221 end.setDescription(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 222 int count = _random.nextInt(Capability.values().length); 223 if(count > 0){ 224 EnumSet<Capability> caps = EnumSet.noneOf(Capability.class); 225 for(int i=0;i<count;++i){ 226 caps.add(createCapability()); 227 } 228 end.setCapabilities(caps); 229 } 230 DataGroups dg = new DataGroups(); 231 for(int i=0;i<TaskType.values().length;++i){ 232 for(int j=0;j<5;++j){ // take random 5 for nice not-too-small-count 233 dg.addDataGroup(createDataGroup()); 234 } 235 } 236 end.setDefaultTaskDataGroups(dg); 237 return end; 238 } 239 240 /** 241 * 242 * @return randomly generated Capability 243 */ 244 public Capability createCapability(){ 245 Capability[] values = Capability.values(); 246 return values[_random.nextInt(values.length)]; 247 } 248 249 /** 250 * 251 * @return randomly generated data group 252 */ 253 public String createDataGroup(){ 254 try { 255 Field[] fields = Definitions.class.getDeclaredFields(); 256 HashSet<String> groups = new HashSet<>(); 257 for(int i=0;i<fields.length;++i){ 258 if(fields[i].getName().contains("DATA_GROUP")){ 259 260 groups.add((String) fields[i].get(null)); 261 } 262 } 263 if(!groups.isEmpty()){ 264 int group = _random.nextInt(groups.size()); 265 Iterator<String> iter = groups.iterator(); 266 for(int i=0;i<group;++i){ 267 iter.next(); 268 } 269 return iter.next(); 270 } 271 } catch (IllegalArgumentException | IllegalAccessException ex) { 272 LOGGER.error(ex, ex); 273 } 274 275 return null; 276 } 277 278 /** 279 * 280 * @return randomly generated visual shape 281 */ 282 public VisualShape createVisualShape(){ 283 VisualShapeType type = createVisualShapeType(); 284 String value = null; 285 switch(type){ 286 case CIRCLE: 287 value = Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt()); 288 break; 289 case POLYGON: 290 case TRIANGLE: 291 value = Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt()); 292 break; 293 case RECTANGLE: 294 value = Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt())+","+ Math.abs(_random.nextInt()); 295 break; 296 default: 297 LOGGER.error("Unknown VisulaShapeType."); 298 return null; 299 } 300 return new VisualShape(type, value); 301 } 302 303 /** 304 * 305 * @return randomly generated visual shape type 306 */ 307 public VisualShapeType createVisualShapeType(){ 308 VisualShapeType[] types = VisualShapeType.values(); 309 return types[_random.nextInt(types.length)]; 310 } 311 312 /** 313 * Note that regardless of the given analysis types, this method will always return media objects of type {@link core.tut.pori.utils.MediaUrlValidator.MediaType#PHOTO}. 314 * 315 * @param analysisTypes optional analysis types the created media object should confirm to 316 * @param dataGroups optional data groups the created media object should confirm to 317 * @param serviceTypes 318 * @return new media object or null if the given parameters prevented creating one 319 */ 320 public MediaObject createMediaObject(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, EnumSet<ServiceType> serviceTypes){ 321 boolean onlyBasic = (!DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups) && DataGroups.hasDataGroup(DataGroups.DATA_GROUP_BASIC, dataGroups)); // check if basic is present, and no data group all 322 323 MediaObject object = new MediaObject(); 324 object.setMediaType(MediaType.PHOTO); 325 boolean createKeywords = (analysisTypes == null || analysisTypes.contains(AnalysisType.KEYWORD_EXTRACTION)); 326 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_KEYWORDS, dataGroups) && createKeywords ){ 327 object.setMediaObjectType(MediaObjectType.KEYWORD); 328 object.setValue(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 329 onlyBasic = false; 330 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_METADATA, dataGroups)){ 331 object.setMediaObjectType(MediaObjectType.METADATA); 332 object.setValue(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 333 object.setName(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 334 onlyBasic = false; 335 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_OBJECT, dataGroups)){ 336 LOGGER.warn("Objects of type "+MediaObjectType.OBJECT.name()+" are not supported by this method."); 337 return null; 338 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_FACE, dataGroups) && (analysisTypes == null || analysisTypes.contains(AnalysisType.FACE_DETECTION))){ 339 object.setMediaObjectType(MediaObjectType.FACE); 340 object.setVisualShape(createVisualShape()); 341 object.setValue(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 342 onlyBasic = false; 343 }else{ // default to keyword 344 object.setMediaObjectType(MediaObjectType.KEYWORD); 345 object.setValue(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 346 } 347 348 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_CANDIDATE, dataGroups)){ 349 object.setConfirmationStatus(ConfirmationStatus.CANDIDATE); 350 onlyBasic = false; 351 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_USER_CONFIRMED, dataGroups)){ 352 object.setConfirmationStatus(ConfirmationStatus.USER_CONFIRMED); 353 onlyBasic = false; 354 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_USER_REJECTED, dataGroups)){ 355 object.setConfirmationStatus(ConfirmationStatus.USER_REJECTED); 356 onlyBasic = false; 357 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_BACKEND_REMOVED, dataGroups)){ 358 object.setConfirmationStatus(ConfirmationStatus.BACKEND_REMOVED); 359 onlyBasic = false; 360 }else if(createKeywords){ 361 object.setConfirmationStatus(createConfirmationStatus()); 362 }else{ 363 LOGGER.warn("Cannot create media object with the given parameters."); 364 return null; 365 } 366 367 if(onlyBasic){ 368 LOGGER.debug("Data group "+DataGroups.DATA_GROUP_BASIC+" was given without any media object specific filters."); 369 return null; 370 } 371 372 object.setObjectId(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 373 object.setMediaObjectId(String.valueOf(Math.abs(_random.nextLong()))); 374 object.setBackendId(Math.abs(_random.nextInt())); 375 object.setRank(Math.abs(_random.nextInt())); 376 object.setConfidence(_random.nextDouble()); 377 object.setVisibility(createVisibility()); 378 if(serviceTypes == null || serviceTypes.isEmpty()){ 379 object.setServiceType(createPhotoServiceType()); 380 }else{ 381 object.setServiceType(getRandomServiceType(_random, serviceTypes)); 382 } 383 object.setOwnerUserId(createUserIdentity()); 384 385 return object; 386 } 387 388 /** 389 * @param random 390 * @param serviceTypes list of service types, not null, not empty 391 * @return random service type from the set of servicetypes 392 */ 393 public static ServiceType getRandomServiceType(Random random, EnumSet<ServiceType> serviceTypes){ 394 Iterator<ServiceType> iter = serviceTypes.iterator(); 395 int targetIndex = random.nextInt(serviceTypes.size()); 396 ServiceType retval = null; 397 for(int i=0;i<=targetIndex;++i){ 398 retval = iter.next(); 399 } 400 return retval; 401 } 402 403 /** 404 * 405 * @return randomly generated media object type 406 */ 407 public MediaObjectType createMediaObjectType(){ 408 MediaObjectType[] t = MediaObjectType.values(); 409 return t[_random.nextInt(t.length)]; 410 } 411 412 /** 413 * 414 * @return randomly generated confirmation status 415 */ 416 public ConfirmationStatus createConfirmationStatus(){ 417 ConfirmationStatus[] s = ConfirmationStatus.values(); 418 return s[_random.nextInt(s.length)]; 419 } 420 421 /** 422 * 423 * @param analysisTypes 424 * @param dataGroups 425 * @param limits 426 * @param serviceTypes 427 * @return randomly generated media object list 428 */ 429 public MediaObjectList createMediaObjectList(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes){ 430 int count = limits.getMaxItems(Definitions.ELEMENT_MEDIA_OBJECTLIST); 431 if(count < 1){ 432 LOGGER.warn("count < 1"); 433 return null; 434 }else if(count >= Limits.DEFAULT_MAX_ITEMS){ 435 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 436 count = 1; 437 } 438 MediaObjectList list = new MediaObjectList(); 439 for(int i=0;i<count;++i){ 440 MediaObject v = createMediaObject(analysisTypes, dataGroups, serviceTypes); 441 if(v != null){ 442 list.addMediaObject(v); 443 } 444 } 445 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 446 list.setResultInfo(new ResultInfo(limits.getStartItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), limits.getEndItem(Definitions.ELEMENT_MEDIA_OBJECTLIST), count)); 447 } 448 return (MediaObjectList.isEmpty(list) ? null : list); 449 } 450 451 /** 452 * @param analysisTypes 453 * @param dataGroups 454 * @param limits 455 * @param serviceTypes 456 * @param userIdentity 457 * @return randomly generated photo 458 */ 459 public Photo createPhoto(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, UserIdentity userIdentity){ 460 Photo photo = new Photo(); 461 String guid = UUID.randomUUID().toString(); 462 photo.setGUID(guid); 463 ServiceType serviceType = createPhotoServiceType(); 464 photo.setServiceType(serviceType); 465 UserIdentity userId = (UserIdentity.isValid(userIdentity) ? userIdentity : createUserIdentity()); 466 photo.setOwnerUserId(userId); 467 468 int backendStatusCount = (!DataGroups.hasDataGroup(Definitions.DATA_GROUP_BACKEND_STATUS, dataGroups) || limits == null ? 0 : limits.getMaxItems(Definitions.ELEMENT_BACKEND_STATUS_LIST)); 469 if(backendStatusCount > BACKEND_STATUS_MAX){ 470 LOGGER.warn("Back-end status count was more than "+BACKEND_STATUS_MAX+" defaulting to "+BACKEND_STATUS_MAX); 471 backendStatusCount = BACKEND_STATUS_MAX; 472 } 473 474 BackendStatusList backendStatus = null; 475 if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL,dataGroups) || DataGroups.hasDataGroup(DataGroups.DATA_GROUP_BASIC, dataGroups)){ 476 photo.setCredits(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 477 photo.setName(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 478 photo.setDescription(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 479 photo.setVisibility(createVisibility()); 480 }else if(DataGroups.hasDataGroup(DATA_GROUP_BACKEND_RESPONSE, dataGroups)){ 481 backendStatus = createBackendStatusContainer(backendStatusCount); 482 photo.setBackendStatus(backendStatus); 483 }else if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_VISIBILITY, dataGroups)){ 484 photo.setVisibility(createVisibility()); 485 } 486 487 MediaObjectList mediaObjectList = createMediaObjectList(analysisTypes, dataGroups, limits, serviceTypes); 488 if(!MediaObjectList.isEmpty(mediaObjectList)){ 489 for(Iterator<MediaObject> vIter = mediaObjectList.getMediaObjects().iterator(); vIter.hasNext();){ // make sure all the new media objects have the same user identity as the created photo 490 vIter.next().setOwnerUserId(userId); 491 } 492 photo.setMediaObjects(mediaObjectList); 493 } 494 495 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_STATUS, dataGroups)){ 496 photo.setBackendStatus((backendStatus != null ? createBackendStatusContainer(backendStatusCount) : backendStatus)); 497 } 498 499 photo.setUrl(generateRedirectUrl(guid, serviceType)); 500 501 return photo; 502 } 503 504 /** 505 * 506 * @param guid 507 * @param type 508 * @return redirection URL for the given GUID and type or null if either one the given values was null 509 */ 510 public String generateRedirectUrl(String guid, ServiceType type){ 511 if(type == null || StringUtils.isBlank(guid)){ 512 LOGGER.error("GUID or service type was null."); 513 return null; 514 } 515 return ServiceInitializer.getPropertyHandler().getRESTBindContext()+service.tut.pori.contentanalysis.reference.Definitions.SERVICE_CA_REFERENCE_CLIENT+"/"+Definitions.METHOD_REDIRECT+"?"+Definitions.PARAMETER_GUID+"="+guid+"&"+Definitions.PARAMETER_SERVICE_ID+"="+type.getServiceId(); 516 } 517 518 /** 519 * 520 * @return randomly generated visibility 521 */ 522 public Visibility createVisibility(){ 523 return Visibility.fromInt(_random.nextInt(3)); 524 } 525 526 /** 527 * 528 * @param analysisTypes 529 * @param dataGroups 530 * @param limits 531 * @param serviceTypes 532 * @param userIdentity 533 * @return randomly generated photo list 534 */ 535 public PhotoList createPhotoList(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, UserIdentity userIdentity){ 536 int photoCount = limits.getMaxItems(service.tut.pori.contentanalysis.Definitions.ELEMENT_PHOTOLIST); 537 if(photoCount < 1){ 538 LOGGER.warn("count < 1"); 539 return null; 540 }else if(photoCount >= Limits.DEFAULT_MAX_ITEMS){ 541 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 542 photoCount = 1; 543 } 544 545 PhotoList list = new PhotoList(); 546 for(int i=0;i<photoCount;++i){ 547 list.addPhoto(createPhoto(analysisTypes, dataGroups, limits, serviceTypes, userIdentity)); 548 } 549 if(DataGroups.hasDataGroup(Definitions.DATA_GROUP_RESULT_INFO, dataGroups)){ 550 list.setResultInfo(new ResultInfo(limits.getStartItem(Definitions.ELEMENT_PHOTOLIST), limits.getStartItem(Definitions.ELEMENT_PHOTOLIST), photoCount)); 551 } 552 return list; 553 } 554 555 /** 556 * 557 * @param limits 558 * @return randomly generated similar photo list 559 */ 560 public SimilarPhotoList createSimilarPhotoList(Limits limits){ 561 int photoCount = limits.getMaxItems(Definitions.ELEMENT_SIMILAR_PHOTOLIST); 562 if(photoCount < 1){ 563 LOGGER.warn("count < 1"); 564 return null; 565 }else if(photoCount >= Limits.DEFAULT_MAX_ITEMS){ 566 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 567 photoCount = 1; 568 } 569 SimilarPhotoList list = new SimilarPhotoList(); 570 for(int i=0;i<photoCount;++i){ 571 list.addPhoto(createPhoto(null, null, LIMITS_NO_MEDIA_OBJECTS, null, null)); 572 } 573 return list; 574 } 575 576 /** 577 * 578 * @param limits 579 * @return randomly generated dissimilar photo list 580 */ 581 public DissimilarPhotoList createDissimilarPhotoList(Limits limits){ 582 int photoCount = limits.getMaxItems(Definitions.ELEMENT_DISSIMILAR_PHOTOLIST); 583 if(photoCount < 1){ 584 LOGGER.warn("count < 1"); 585 return null; 586 }else if(photoCount >= Limits.DEFAULT_MAX_ITEMS){ 587 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 588 photoCount = 1; 589 } 590 DissimilarPhotoList list = new DissimilarPhotoList(); 591 for(int i=0;i<photoCount;++i){ 592 list.addPhoto(createPhoto(null, null, LIMITS_NO_MEDIA_OBJECTS, null, null)); 593 } 594 return list; 595 } 596 597 /** 598 * 599 * @param limits 600 * @return randomly generated deleted photo list 601 */ 602 public DeletedPhotoList createDeletedPhotoList(Limits limits){ 603 int photoCount = limits.getMaxItems(Definitions.ELEMENT_DELETED_PHOTOLIST); 604 if(photoCount < 1){ 605 LOGGER.warn("count < 1"); 606 return null; 607 }else if(photoCount >= Limits.DEFAULT_MAX_ITEMS){ 608 LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1."); 609 photoCount = 1; 610 } 611 DeletedPhotoList list = new DeletedPhotoList(); 612 for(int i=0;i<photoCount;++i){ 613 list.addPhoto(createPhoto(null, null, LIMITS_NO_MEDIA_OBJECTS, null, null)); 614 } 615 return list; 616 } 617 618 /** 619 * 620 * @param limits 621 * @return randomly generated feedback list 622 */ 623 public PhotoFeedbackList createFeedbackList(Limits limits){ 624 if(limits.getMaxItems(Definitions.ELEMENT_DISSIMILAR_PHOTOLIST) < 1 && limits.getMaxItems(Definitions.ELEMENT_SIMILAR_PHOTOLIST) < 1){ 625 LOGGER.warn("Similar and dissimilar count < 1."); 626 return null; 627 } 628 629 return new PhotoFeedbackList(createDissimilarPhotoList(limits), createReferencePhotoList(limits), createSimilarPhotoList(limits)); 630 } 631 632 /** 633 * 634 * @param limits 635 * @return randomly generated reference photolist 636 */ 637 public ReferencePhotoList createReferencePhotoList(Limits limits){ 638 int referenceCount = limits.getMaxItems(Definitions.ELEMENT_REFERENCE_PHOTOLIST); 639 if(referenceCount < 1 || referenceCount >= Limits.DEFAULT_MAX_ITEMS){ 640 LOGGER.debug("Reference count was less than 1 or MAX, using 1."); 641 referenceCount = 1; 642 } 643 644 ReferencePhotoList refs = new ReferencePhotoList(); 645 for(int i=0;i<referenceCount;++i){ 646 refs.addPhoto(createPhoto(null, null, LIMITS_NO_MEDIA_OBJECTS, null, null)); 647 } 648 return refs; 649 } 650 651 /** 652 * 653 * @param backendId 654 * @param dataGroups 655 * @param limits 656 * @param taskId 657 * @param taskType if null, generated randomly 658 * @return randomly generated photo task details 659 */ 660 public PhotoTaskDetails createPhotoTaskDetails(Integer backendId, DataGroups dataGroups, Limits limits, Long taskId, TaskType taskType){ 661 PhotoTaskDetails details = new PhotoTaskDetails(); 662 populateAbstractTaskDetails(backendId, details, taskId, taskType); 663 details.setPhotoList(createPhotoList(null, dataGroups,limits,null, details.getUserId())); 664 details.setSimilarPhotoList(createSimilarPhotoList(limits)); 665 details.setDissimilarPhotoList(createDissimilarPhotoList(limits)); 666 details.setDeletedPhotoList(createDeletedPhotoList(limits)); 667 details.setUserConfidence(Math.abs(_random.nextDouble())); 668 return details; 669 } 670 671 /** 672 * 673 * @param analysisTypes 674 * @param dataGroups 675 * @param limits 676 * @param taskId if null, value is randomly generated 677 * @param taskType if null, value is randomly generated 678 * @return randomly generated task response 679 */ 680 public PhotoTaskResponse createTaskResponse(Collection<AnalysisType> analysisTypes, DataGroups dataGroups, Limits limits, Long taskId, TaskType taskType){ 681 PhotoTaskResponse r = new PhotoTaskResponse(); 682 r.setTaskId((taskId == null ? Math.abs(_random.nextLong()) : taskId)); 683 Integer backendId = Math.abs(_random.nextInt()); 684 r.setBackendId(backendId); 685 r.setMessage(RandomStringUtils.randomAlphabetic(TEXT_LENGTH)); 686 r.setTaskType((taskType == null ? createTaskType() : taskType)); 687 r.setStatus(createTaskStatus()); 688 PhotoList photoList = createPhotoList(analysisTypes, dataGroups, limits, null, createUserIdentity()); 689 if(!PhotoList.isEmpty(photoList)){ 690 for(Photo p : photoList.getPhotos()){ // make sure all media objects have the same back-end id as the task has 691 MediaObjectList mediaObjectList = p.getMediaObjects(); 692 if(!MediaObjectList.isEmpty(mediaObjectList)){ 693 for(MediaObject vo : mediaObjectList.getMediaObjects()){ 694 vo.setBackendId(backendId); 695 } 696 } 697 } 698 r.setPhotoList(photoList); 699 } 700 return r; 701 } 702 703 /** 704 * Note: Java dates may go crazy if a date more than year 9000 is used 705 * 706 * @param latest the latest date possible, if null, long max will be used 707 * @param random 708 * @return random date between latest and unix epoch 709 */ 710 public static Date createRandomDate(Date latest, Random random){ 711 long latestTime = Long.MAX_VALUE; 712 if(latest != null){ 713 latestTime = latest.getTime(); 714 } 715 return new Date(1 + (long)(random.nextDouble() * latestTime)); 716 } 717 718 /** 719 * Create example search results, making sure that one of the given userIds, mediaObjects and serviceTypes is 720 * set for the photos, if userIds, mediaObjects or serviceTypes is null or empty, a random value will be generated. Note: 721 * for photos with visibility PUBLIC, any userId is OK, and the given user id will not necessarily be set. 722 * If GUID is given, the first photo in the list (when the given limits permit it) will have the given GUID. 723 * @param guid 724 * @param dataGroups 725 * @param limits 726 * @param serviceTypes 727 * @param userIds 728 * @param mediaObjects 729 * @return randomly generated photo list 730 */ 731 public PhotoList createSearchResults(String guid, DataGroups dataGroups, Limits limits, EnumSet<ServiceType> serviceTypes, long[] userIds, MediaObjectList mediaObjects){ 732 PhotoList list = createPhotoList(null, dataGroups, limits, serviceTypes, null); 733 if(list == null){ 734 return null; 735 } 736 737 boolean hasUserIds = !ArrayUtils.isEmpty(userIds); 738 739 List<ServiceType> types = null; 740 int serviceTypeCount = 0; 741 if(serviceTypes != null && !serviceTypes.isEmpty()){ 742 types = new ArrayList<>(serviceTypes); 743 serviceTypeCount = serviceTypes.size(); 744 } 745 746 int mediaObjectCount = 0; 747 if(!MediaObjectList.isEmpty(mediaObjects)){ 748 mediaObjectCount = mediaObjects.getMediaObjects().size(); 749 } 750 751 if(!hasUserIds && types == null && mediaObjectCount < 1){ 752 LOGGER.debug("No userIds, mediaObjects or types."); 753 return list; 754 } 755 756 for(Photo photo : list.getPhotos()){ 757 if(guid != null){ // if uid has been given, and has not been set already 758 photo.setGUID(guid); 759 guid = null; 760 } 761 if(hasUserIds){ 762 if(!Visibility.PUBLIC.equals(photo.getVisibility())){ // only change for photos that do not have public visibility 763 photo.setOwnerUserId(new UserIdentity(userIds[_random.nextInt(userIds.length)])); 764 } 765 }else{ 766 photo.setVisibility(Visibility.PUBLIC); // there could also be photos with visibility PRIVATE, if the user was logged in, but setting all to PUBLIC will ensure that the example result is valid 767 } 768 if(types != null){ 769 photo.setServiceType(types.get(_random.nextInt(serviceTypeCount))); // make sure there are service types only from the given set 770 } 771 if(mediaObjectCount > 0){ 772 List<MediaObject> objects = photo.getMediaObjects().getMediaObjects(); 773 if(objects != null){ // don't do anything if there are no other objects (probably filtered out by limits or datagroups) 774 objects.remove(0); // remove one just in case, to keep the limits... 775 photo.addMediaObject(mediaObjects.getMediaObjects().get(_random.nextInt(mediaObjectCount))); //... and add new one for the removed one 776 } 777 } 778 } 779 return list; 780 } 781 782 /** 783 * @return randomly generated URL 784 */ 785 public static String createRandomUrl(){ 786 return "http://example.org/"+RandomStringUtils.randomAlphabetic(20); 787 } 788 789 /** 790 * 791 * @return randomly generated task type 792 */ 793 public TaskType createPhotoTaskDetailsType(){ 794 switch(_random.nextInt(3)){ 795 case 0: 796 return TaskType.ANALYSIS; 797 case 1: 798 return TaskType.BACKEND_FEEDBACK; 799 case 2: 800 return TaskType.FEEDBACK; 801 default: // will never happen 802 return null; 803 } 804 } 805 806 /** 807 * @param dataGroups 808 * @param limits 809 * @param type ANALYSIS, BACKEND_FEEDBACK or FEEDBACK, if null, type is chosen randomly 810 * @return randomly generated photo task details 811 * @throws UnsupportedOperationException on bad type 812 */ 813 public PhotoTaskDetails createPhotoTaskDetails(DataGroups dataGroups, Limits limits, TaskType type) throws UnsupportedOperationException{ 814 if(type == null){ 815 type = createPhotoTaskDetailsType(); 816 } 817 PhotoTaskDetails details = new PhotoTaskDetails(type); 818 UserIdentity userIdentity = createUserIdentity(); 819 switch(type){ 820 case BACKEND_FEEDBACK: 821 LOGGER.debug("Using task type "+TaskType.ANALYSIS.name()+" as template for task of type "+TaskType.BACKEND_FEEDBACK.name()); 822 case ANALYSIS: 823 details.setPhotoList(createPhotoList(null, dataGroups, limits, null, userIdentity)); 824 break; 825 case FEEDBACK: 826 details.setUserConfidence(_random.nextDouble()); 827 if(_random.nextBoolean()){ // create randomly a similarity feedback 828 details.addReferencePhoto(new Photo(String.valueOf(Math.abs(_random.nextLong())))); 829 details.setDissimilarPhotoList(createDissimilarPhotoList(limits)); 830 details.setSimilarPhotoList(createSimilarPhotoList(limits)); 831 }else if(_random.nextBoolean()){ // create randomly deleted feedback 832 details.setDeletedPhotoList(createDeletedPhotoList(limits)); 833 }else{ // normal photo list 834 details.setPhotoList(createPhotoList(null, dataGroups, limits, null, userIdentity)); 835 } 836 break; 837 default: 838 throw new UnsupportedOperationException("Unsupported type: "+type.name()); 839 } 840 841 details.setBackendId(Math.abs(_random.nextInt())); 842 details.setTaskId(Math.abs(_random.nextLong())); 843 int backendStatusCount = limits.getMaxItems(Definitions.ELEMENT_BACKEND_STATUS_LIST); 844 if(backendStatusCount >= Limits.DEFAULT_MAX_ITEMS){ 845 LOGGER.debug(Definitions.ELEMENT_BACKEND_STATUS_LIST+" >= "+Limits.DEFAULT_MAX_ITEMS+" using 1."); 846 backendStatusCount = 1; 847 } 848 details.setBackends(createBackendStatusContainer(backendStatusCount)); 849 details.setUserId(userIdentity); 850 details.setCallbackUri(generateFinishedCallbackUri()); // override the default uri 851 details.setTaskParameters(createAnalysisParameters()); 852 return details; 853 } 854 855 /** 856 * 857 * @return the default task finished callback uri 858 */ 859 public String generateFinishedCallbackUri(){ 860 return ServiceInitializer.getPropertyHandler().getRESTBindContext()+service.tut.pori.contentanalysis.reference.Definitions.SERVICE_CA_REFERENCE_SERVER+"/"+Definitions.METHOD_TASK_FINISHED; 861 } 862 863 /** 864 * 865 * @return media object list with keywords that have known friendly values, or null if no values could be created 866 */ 867 public MediaObjectList createFriendlyKeywordableList(){ 868 final List<Pair<String, Integer>> values = new ArrayList<>(); 869 try (ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(core.tut.pori.properties.SystemProperty.CONFIGURATION_FILE_PATH+"database-context.xml") ; Connection connection = DataSourceUtils.getConnection((DataSource) ctx.getBean("dataSource")); Statement stmnt = connection.createStatement()){ // by-pass the dao to retrieve raw values for testing 870 ResultSet set = stmnt.executeQuery("SELECT value, backend_id FROM ca_frontend.ca_photo_friendly_keywords where friendly_value IS NOT NULL"); 871 while(set.next()){ 872 int backendId = set.getInt("backend_id"); 873 values.add(Pair.of(set.getString("value"), (set.wasNull() ? null : backendId))); 874 } 875 } catch (CannotGetJdbcConnectionException | BeansException | SQLException ex) { 876 LOGGER.error(ex, ex); 877 } 878 879 if(values.isEmpty()){ 880 LOGGER.warn("No values."); 881 return null; 882 } 883 884 MediaObjectList list = new MediaObjectList(); 885 UserIdentity userId = createUserIdentity(); 886 int mediaObjectId = 0; 887 for(Pair<String, Integer> p : values){ 888 MediaObject o = new MediaObject(MediaType.PHOTO,MediaObjectType.KEYWORD); 889 o.setValue(p.getLeft()); 890 o.setBackendId(p.getRight()); 891 o.setMediaObjectId(String.valueOf(++mediaObjectId)); 892 o.setConfirmationStatus(ConfirmationStatus.CANDIDATE); 893 o.setOwnerUserId(userId); 894 list.addMediaObject(o); 895 } 896 return list; 897 } 898 899 /** 900 * 901 * @return randomly generated result info 902 */ 903 public ResultInfo createResultInfo() { 904 long start = Math.abs(_random.nextInt()); 905 return new ResultInfo(start, start+Math.abs(_random.nextInt()), Math.abs(_random.nextLong())); 906 } 907 908 /** 909 * 910 * @return randomly generated analysis parameters 911 */ 912 public PhotoParameters createAnalysisParameters() { 913 PhotoParameters ap = new PhotoParameters(); 914 ap.setAnalysisTypes(EnumSet.of(AnalysisType.FACE_DETECTION, AnalysisType.KEYWORD_EXTRACTION, AnalysisType.VISUAL)); 915 return ap; 916 } 917}