001/**
002 * Copyright 2016 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.apilta.sensors.reference;
017
018import java.time.Instant;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Date;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Random;
025import java.util.Set;
026import java.util.TreeMap;
027import java.util.UUID;
028
029import org.apache.commons.collections4.IterableUtils;
030import org.apache.commons.lang3.ArrayUtils;
031import org.apache.commons.lang3.RandomUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.commons.text.RandomStringGenerator;
034import org.apache.log4j.Logger;
035
036import core.tut.pori.http.parameters.DataGroups;
037import core.tut.pori.http.parameters.DateIntervalParameter.Interval;
038import core.tut.pori.http.parameters.Limits;
039import core.tut.pori.users.UserIdentity;
040import service.tut.pori.apilta.sensors.Definitions;
041import service.tut.pori.apilta.sensors.datatypes.Condition;
042import service.tut.pori.apilta.sensors.datatypes.DataPoint;
043import service.tut.pori.apilta.sensors.datatypes.Measurement;
044import service.tut.pori.apilta.sensors.datatypes.MeasurementList;
045import service.tut.pori.apilta.sensors.datatypes.Output;
046import service.tut.pori.apilta.sensors.datatypes.SensorTask;
047import service.tut.pori.tasks.datatypes.Task;
048import service.tut.pori.tasks.datatypes.Task.State;
049import service.tut.pori.tasks.datatypes.Task.Visibility;
050import service.tut.pori.tasks.datatypes.TaskBackend;
051import service.tut.pori.tasks.datatypes.TaskBackend.Status;
052
053/**
054 * 
055 * Class that can be used to created example objects/object lists.
056 *
057 */
058public class SensorsXMLObjectCreator {
059  private static final Logger LOGGER = Logger.getLogger(SensorsXMLObjectCreator.class);
060  private static final int MAX_CONDITIONS = 5;
061  private static final int MAX_OUTPUTS = 5;
062  private static final int TEXT_LENGTH = 64;
063  private Random _random = null;
064  private RandomStringGenerator _stringGenerator = null;
065  
066  /**
067   * 
068   * @param seed for random generator, or null to use default (system time in nanoseconds)
069   */
070  public SensorsXMLObjectCreator(Long seed){
071    if(seed == null){
072      seed = System.nanoTime();
073    }
074    _random = new Random(seed);
075    _stringGenerator = new RandomStringGenerator.Builder().withinRange('a', 'z').build();
076  }
077
078  /**
079   * @return the random
080   */
081  public Random getRandom() {
082    return _random;
083  }
084  
085  /**
086   * 
087   * @param backendIdFilter 
088   * @param createdFilter 
089   * @param dataGroups 
090   * @param limits
091   * @param measurementIdFilter 
092   * @return list of measurement objects
093   */
094  public List<Measurement> generateMeasurementList(long[] backendIdFilter, Set<Interval> createdFilter, DataGroups dataGroups, Limits limits, List<String> measurementIdFilter){
095    int count = limits.getMaxItems(service.tut.pori.apilta.sensors.datatypes.Definitions.ELEMENT_MEASUREMENT_LIST);
096    if(count < 1){
097      LOGGER.warn("count < 1");
098      return null;
099    }else if(count >= Limits.DEFAULT_MAX_ITEMS){
100      LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1.");
101      count = 1;
102    }
103    boolean hasMeasurementIdFilter = (measurementIdFilter != null && !measurementIdFilter.isEmpty());
104    if(hasMeasurementIdFilter){
105      int filterSize = measurementIdFilter.size();
106      if(hasMeasurementIdFilter && filterSize > count){
107        LOGGER.debug("Not enough measurement ids in the filter for limit count, restricting count to: "+filterSize);
108        count = filterSize;
109      }
110    }
111    
112    List<Measurement> list = new ArrayList<>();
113    for(int i=0;i<count;++i) {
114      Measurement measurement = generateMeasurementData(backendIdFilter, createdFilter, dataGroups, limits);
115      if(hasMeasurementIdFilter){ // if filter given ...
116        measurement.setMeasurementId(measurementIdFilter.get(i)); // ... modify ids to match the filter
117      }
118      list.add(measurement);
119    }
120    return (list.isEmpty() ? null : list);
121  }
122
123  /**
124   * 
125   * @param backendIdFilter 
126   * @param createdFilter 
127   * @param dataGroups 
128   * @param limits
129   * @return pseudo randomly generated measurement data
130   */
131  public Measurement generateMeasurementData(long[] backendIdFilter, Set<Interval> createdFilter, DataGroups dataGroups, Limits limits) {
132    Measurement data = new Measurement();
133    data.setBackendId((ArrayUtils.isEmpty(backendIdFilter) ? Math.abs(_random.nextLong()) : backendIdFilter[_random.nextInt(backendIdFilter.length)]));
134    if(DataGroups.hasDataGroup(DataGroups.DATA_GROUP_ALL, dataGroups) || DataGroups.hasDataGroup(Definitions.DATA_GROUP_DATA_POINTS, dataGroups)){
135      data.setDataPoints(createDataPointList(data.getMeasurementId(), limits, createdFilter));
136    }
137    data.setMeasurementId(UUID.randomUUID().toString());
138
139    return data;
140  }
141
142  /**
143   * 
144   * @param backendId
145   * @param taskIds 
146   * @param taskType
147   * @return pseudo randomly generated task details
148   * @throws IllegalArgumentException on invalid arguments
149   */
150  public SensorTask generateTaskDetails(Long backendId, Collection<String> taskIds, String taskType) throws IllegalArgumentException {
151    SensorTask task = (SensorTask) setTaskDetails(new SensorTask(), backendId, taskIds, taskType);
152    setSensorTaskDetails(task);
153    
154    return task;
155  }
156
157  /**
158   * 
159   * @param backendId 
160   * @param dataGroups
161   * @param limits
162   * @param taskIds 
163   * @param taskType
164   * @return pseudo randomly generated task results
165   * @throws IllegalArgumentException on invalid arguments
166   */
167  public SensorTask generateTaskResults(Long backendId, DataGroups dataGroups, Limits limits, Collection<String> taskIds, String taskType) throws IllegalArgumentException
168  {
169    SensorTask task = (SensorTask) setTaskDetails(new SensorTask(), _random.nextLong(), taskIds, taskType);
170    //erase a few non-needed members for this case
171    List<TaskBackend> backends = task.getBackends();
172    backends.clear();
173    TaskBackend tb = generateTaskBackend(backendId);
174    backends.add(tb);
175    task.setCreated(null);
176    task.setUpdated(null);
177    task.setUserId(null);
178    task.setName(null);
179    task.setDescription(null);
180    task.setState(null);
181    //generate & set task results
182    MeasurementList data = new MeasurementList();
183    data.setMeasurements(generateMeasurementList((backendId == null ? new long[]{tb.getBackendId()} : new long[]{backendId}), null, dataGroups, limits, null));
184    task.setMeasurements(data);
185    
186    return task;
187  }
188  
189  /**
190   * 
191   * @param measurementId
192   * @param limits
193   * @param createdFilter 
194   * @return list of datapoint objects
195   */
196  public List<DataPoint> createDataPointList(String measurementId, Limits limits, Set<Interval> createdFilter){
197    int count = limits.getMaxItems(service.tut.pori.apilta.sensors.datatypes.Definitions.ELEMENT_DATAPOINT_LIST);
198    if(count < 1){
199      LOGGER.warn("count < 1");
200      return null;
201    }else if(count >= Limits.DEFAULT_MAX_ITEMS){
202      LOGGER.debug("Count was "+Limits.DEFAULT_MAX_ITEMS+", using 1.");
203      count = 1;
204    }
205    List<DataPoint> list = new ArrayList<>();
206    for(int i=0;i<count;++i){
207      DataPoint kw = createDataPoint(createdFilter, measurementId);
208      if(kw != null){
209        list.add(kw);
210      }
211    }
212    return (list.isEmpty() ? null : list);
213  }
214  
215  /**
216   * Return pseudo randomly generated DataPoint
217   * @param createdFilter 
218   * @param measurementId
219   * @return a datapoint
220   */
221  public DataPoint createDataPoint(Set<Interval> createdFilter, String measurementId){
222    DataPoint dp = new DataPoint();
223    Date created = createDate(createdFilter, null);
224    dp.setCreated(created);
225    dp.setDataPointId(UUID.randomUUID().toString());
226    
227    if(measurementId == null){
228      measurementId = UUID.randomUUID().toString();
229    }
230    
231    dp.setMeasurementId(measurementId);
232    dp.setDescription(_stringGenerator.generate(TEXT_LENGTH));
233    dp.setKey(_stringGenerator.generate(TEXT_LENGTH));
234    dp.setValue(_stringGenerator.generate(TEXT_LENGTH));
235    
236    return dp;
237  }
238  
239  /**
240   * 
241   * @param intervals if null, the returned date will be random, if given an interval will be randomly selected amongst the given intervals and a new date will be generated that is within the interval
242   * @param start override the start time
243   * @return new date created by the given intervals
244   * @throws IllegalArgumentException on invalid interval and/or start time
245   */
246  private Date createDate(Set<Interval> intervals, Date start) throws IllegalArgumentException{
247    if(intervals != null && !intervals.isEmpty()){
248      Interval interval = IterableUtils.get(intervals, _random.nextInt(intervals.size()));
249      if(start == null){
250        start = interval.getStart();
251      }
252      Date end = interval.getEnd();
253      if(end.before(start)){
254        throw new IllegalArgumentException("Cannot create valid date based on the given start time.");
255      }
256      
257      return new Date(RandomUtils.nextLong(start.getTime(), end.getTime()));
258    }else if(start == null){
259      return new Date(RandomUtils.nextLong(0, System.currentTimeMillis()));
260    }else{
261      return new Date(RandomUtils.nextLong(start.getTime(), System.currentTimeMillis()));
262    }
263  }
264  
265  /**
266   * 
267   * @param task
268   * @param backendId
269   * @param taskIds optional task identifiers (if null or empty, one id will be randomly generated)
270   * @param taskType
271   * @return the populated base class task
272   */
273  public Task setTaskDetails(Task task, long backendId, Collection<String> taskIds, String taskType){
274    if(task == null){
275      return null;
276    }
277    
278    ArrayList<TaskBackend> backends = new ArrayList<>(1);
279    TaskBackend backend = new TaskBackend();
280    backend.setBackendId(backendId);
281    backends.add(backend);
282    task.setBackends(backends);
283    
284    task.setCreated(new Date(System.currentTimeMillis() - Math.abs(_random.nextLong() % 31536000000L)));
285    if(taskIds == null || taskIds.isEmpty()) {
286      task.addTaskId(UUID.randomUUID().toString());
287    }else {
288      for(String taskId : taskIds) {
289        task.addTaskId(taskId);
290      }
291    }
292    
293    HashSet<String> taskTypes = new HashSet<>(1);
294    if(StringUtils.isEmpty(taskType)){
295      taskTypes.add(_stringGenerator.generate(TEXT_LENGTH));
296    }else{
297      taskTypes.add(taskType);
298    }
299    task.setTaskTypes(taskTypes);
300    task.setUpdated(new Date(task.getCreated().getTime() + Math.abs(_random.nextLong() % 31536000000L)));
301    task.setUserId(new UserIdentity(Math.abs(_random.nextLong())));
302    task.setName(_stringGenerator.generate(TEXT_LENGTH));
303    task.setDescription(_stringGenerator.generate(TEXT_LENGTH));
304    task.setDataVisibility(generateVisibility());
305    task.setState(generateState());
306    
307    return task;
308  }
309  
310  /**
311   * 
312   * @return random task visibility
313   */
314  public Visibility generateVisibility() {
315    Visibility[] visibilities = Visibility.values();
316    return visibilities[_random.nextInt(visibilities.length)];
317  }
318  
319  /**
320   * 
321   * @return random task state
322   */
323  public State generateState() {
324    State[] values = State.values();
325    return values[_random.nextInt(values.length)];
326  }
327  
328  /**
329   * 
330   * @param backendId if null, random id is generated
331   * @return random task back end
332   */
333  public TaskBackend generateTaskBackend(Long backendId) {
334    TaskBackend tb = new TaskBackend();
335    tb.setBackendId((backendId == null ? Math.abs(_random.nextLong()) : backendId));
336    tb.setMessage(_stringGenerator.generate(TEXT_LENGTH));
337    Status[] values = Status.values();
338    tb.setStatus(values[_random.nextInt(values.length)]);
339    return tb;
340  }
341  
342  /**
343   * @param task
344   * @return return populated sensor task
345   */
346  public Task setSensorTaskDetails(SensorTask task){
347    List<Output> outputs = new ArrayList<>();
348    for(int i=-1, count=_random.nextInt(MAX_OUTPUTS); i<count; ++i){
349      Output output = new Output();
350      output.setFeature(_stringGenerator.generate(5));
351      outputs.add(output);
352    }
353    task.setOutput(outputs);
354    
355    task.setConditions(createConditionList());
356    return task;
357  }
358  
359  /**
360   * 
361   * @return list of generated random conditions
362   */
363  public List<Condition> createConditionList(){
364    List<Condition> list = new ArrayList<>();
365    for(int i=-1, count=_random.nextInt(MAX_CONDITIONS);i<count;++i){
366      Condition condition = createCondition();
367      if(condition != null){
368        list.add(condition);
369      }
370    }
371    return (list.isEmpty() ? null : list);
372  }
373  
374  /**
375   * 
376   * @return return randomized condition
377   */
378  public Condition createCondition(){
379    Condition condition = new Condition();
380    TreeMap<String, String> conditions = new TreeMap<>();
381    for(int i=-1, count=_random.nextInt(MAX_CONDITIONS); i<count; ++i){
382      conditions.put(_stringGenerator.generate(5), _stringGenerator.generate(10));
383    }
384    conditions.put("time/validFromToRange", Instant.now().minusSeconds(Math.abs(_random.nextLong() % 31536000L)).toString() +"/"+Instant.now().plusSeconds(Math.abs(_random.nextLong() % 31536000L)).toString());
385    condition.setConditions(conditions);
386    return condition;
387  }
388
389  /**
390   * 
391   * @return random GUID
392   */
393  public String createGUID() {
394    return UUID.randomUUID().toString();
395  }
396}