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}