refs #2253: Accounting Manager - Top N Active Users
https://support.d4science.org/issues/2253 Reorganized Library git-svn-id: https://svn.d4science.research-infrastructures.eu/gcube/trunk/accounting/accounting-analytics@125496 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
parent
31e6f90591
commit
79c9373eae
|
@ -1,183 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.gcube.accounting.analytics;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.gcube.accounting.analytics.exception.NoAvailableScopeException;
|
|
||||||
import org.gcube.accounting.analytics.exception.NoUsableAccountingPersistenceQueryFound;
|
|
||||||
import org.gcube.accounting.analytics.persistence.AccountingPersistenceBackendQuery;
|
|
||||||
import org.gcube.accounting.analytics.persistence.AccountingPersistenceBackendQueryFactory;
|
|
||||||
import org.gcube.documentstore.records.AggregatedRecord;
|
|
||||||
import org.gcube.documentstore.records.Record;
|
|
||||||
import org.gcube.documentstore.records.RecordUtility;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/
|
|
||||||
*/
|
|
||||||
public class ResourceRecordQuery {
|
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory
|
|
||||||
.getLogger(ResourceRecordQuery.class);
|
|
||||||
|
|
||||||
protected static Map<Class<? extends Record>, Set<String>> resourceRecords = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a Map containing a set of required fields for each Resource
|
|
||||||
* Records Types
|
|
||||||
*
|
|
||||||
* @return the Map
|
|
||||||
*/
|
|
||||||
public static synchronized Map<Class<? extends Record>, Set<String>> getResourceRecordsTypes() {
|
|
||||||
if (resourceRecords == null) {
|
|
||||||
resourceRecords = new HashMap<Class<? extends Record>, Set<String>>();
|
|
||||||
Collection<Class<? extends Record>> resourceRecordsTypes = RecordUtility
|
|
||||||
.getRecordClassesFound().values();
|
|
||||||
for (Class<? extends Record> resourceRecordsType : resourceRecordsTypes) {
|
|
||||||
try {
|
|
||||||
Record record = resourceRecordsType.newInstance();
|
|
||||||
resourceRecords.put(resourceRecordsType,
|
|
||||||
record.getRequiredFields());
|
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
|
||||||
logger.error(String.format(
|
|
||||||
"Unable to correctly istantiate %s",
|
|
||||||
resourceRecordsType.getSimpleName()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return resourceRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AccountingPersistenceBackendQuery accountingPersistenceQuery;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate the ResourceRecord for the current scope
|
|
||||||
*
|
|
||||||
* @throws NoAvailableScopeException
|
|
||||||
* if there is not possible to query in the current scope
|
|
||||||
* @throws NoUsableAccountingPersistenceQueryFound
|
|
||||||
* if there is no available instance which can query in that
|
|
||||||
* scope
|
|
||||||
*/
|
|
||||||
public ResourceRecordQuery() throws NoAvailableScopeException,
|
|
||||||
NoUsableAccountingPersistenceQueryFound {
|
|
||||||
this.accountingPersistenceQuery = AccountingPersistenceBackendQueryFactory
|
|
||||||
.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static JSONObject getPaddingJSONObject(
|
|
||||||
Map<Calendar, Info> unpaddedResults) throws JSONException {
|
|
||||||
Info auxInfo = new ArrayList<Info>(unpaddedResults.values()).get(0);
|
|
||||||
JSONObject auxJsonObject = auxInfo.getValue();
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Iterator<String> keys = auxJsonObject.keys();
|
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
while (keys.hasNext()) {
|
|
||||||
String key = keys.next();
|
|
||||||
jsonObject.put(key, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pad the data
|
|
||||||
*
|
|
||||||
* @param unpaddedData
|
|
||||||
* the data to be pad
|
|
||||||
* @param temporalConstraint
|
|
||||||
* temporalConstraint the temporal interval and the granularity
|
|
||||||
* of the data to pad
|
|
||||||
* @return the data padded taking in account the TemporalConstraint
|
|
||||||
* @throws Exception
|
|
||||||
* if fails
|
|
||||||
*/
|
|
||||||
public static List<Info> getPaddedResults(Map<Calendar, Info> unpaddedData,
|
|
||||||
TemporalConstraint temporalConstraint) throws Exception {
|
|
||||||
JSONObject jsonObject = getPaddingJSONObject(unpaddedData);
|
|
||||||
|
|
||||||
List<Info> paddedResults = new ArrayList<Info>();
|
|
||||||
List<Calendar> sequence = temporalConstraint.getCalendarSequence();
|
|
||||||
|
|
||||||
for (Calendar progressTime : sequence) {
|
|
||||||
if (unpaddedData.get(progressTime) != null) {
|
|
||||||
paddedResults.add(unpaddedData.get(progressTime));
|
|
||||||
} else {
|
|
||||||
Info info = new Info(progressTime, jsonObject);
|
|
||||||
paddedResults.add(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paddedResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return results with padding if pad is set to true.
|
|
||||||
*
|
|
||||||
* @param aggregatedRecordClass
|
|
||||||
* the UsageRecord type to query
|
|
||||||
* @param temporalConstraint
|
|
||||||
* the temporal interval and the granularity
|
|
||||||
* @param filters
|
|
||||||
* the list keys to filter (in AND)
|
|
||||||
* @param pad
|
|
||||||
* indicate is the results have to be padded with zeros when
|
|
||||||
* there is no data available at certain data points of sequence
|
|
||||||
* @return the requested list of Info
|
|
||||||
* @throws Exception
|
|
||||||
* if fails
|
|
||||||
*/
|
|
||||||
public List<Info> getInfo(
|
|
||||||
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
|
||||||
TemporalConstraint temporalConstraint, List<Filter> filters,
|
|
||||||
boolean pad) throws Exception {
|
|
||||||
|
|
||||||
Map<Calendar, Info> unpaddedResults = accountingPersistenceQuery
|
|
||||||
.getTimeSeries(aggregatedRecordClass, temporalConstraint,
|
|
||||||
filters);
|
|
||||||
|
|
||||||
if (!pad) {
|
|
||||||
return new ArrayList<Info>(unpaddedResults.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPaddedResults(unpaddedResults, temporalConstraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return unpadded results
|
|
||||||
*
|
|
||||||
* @param aggregatedRecordClass
|
|
||||||
* the UsageRecord type to query
|
|
||||||
* @param temporalConstraint
|
|
||||||
* the temporal interval and the granularity
|
|
||||||
* @param filters
|
|
||||||
* the list keys to filter (in AND)
|
|
||||||
* @return the requested list of Info
|
|
||||||
* @throws Exception
|
|
||||||
* if fails
|
|
||||||
*/
|
|
||||||
public List<Info> getInfo(
|
|
||||||
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
|
||||||
TemporalConstraint temporalConstraint, List<Filter> filters)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
return getInfo(aggregatedRecordClass, temporalConstraint, filters,
|
|
||||||
false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -6,9 +6,12 @@ package org.gcube.accounting.analytics;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.SortedSet;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -152,8 +155,8 @@ public class TemporalConstraint {
|
||||||
return getAlignedCalendar(endTime, aggregationMode);
|
return getAlignedCalendar(endTime, aggregationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Calendar> getCalendarSequence(){
|
public SortedSet<Calendar> getCalendarSequence(){
|
||||||
List<Calendar> sequence = new ArrayList<Calendar>();
|
SortedSet<Calendar> sequence = new TreeSet<>();
|
||||||
|
|
||||||
CalendarEnum[] calendarValues = CalendarEnum.values();
|
CalendarEnum[] calendarValues = CalendarEnum.values();
|
||||||
int calendarValue = calendarValues[aggregationMode.ordinal()].getCalendarValue();
|
int calendarValue = calendarValues[aggregationMode.ordinal()].getCalendarValue();
|
||||||
|
@ -183,7 +186,7 @@ public class TemporalConstraint {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<String> getSequenceAsStringList(List<Calendar> sequence){
|
public static List<String> getSequenceAsStringList(Collection<Calendar> sequence){
|
||||||
List<String> stringSequence = new ArrayList<String>();
|
List<String> stringSequence = new ArrayList<String>();
|
||||||
for(Calendar calendar : sequence){
|
for(Calendar calendar : sequence){
|
||||||
stringSequence.add(timeInMillisToString(calendar.getTimeInMillis()));
|
stringSequence.add(timeInMillisToString(calendar.getTimeInMillis()));
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
*/
|
*/
|
||||||
package org.gcube.accounting.analytics.persistence;
|
package org.gcube.accounting.analytics.persistence;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
@ -17,6 +20,8 @@ import org.gcube.accounting.datamodel.UsageRecord;
|
||||||
import org.gcube.accounting.datamodel.aggregation.AggregatedStorageUsageRecord;
|
import org.gcube.accounting.datamodel.aggregation.AggregatedStorageUsageRecord;
|
||||||
import org.gcube.documentstore.records.AggregatedRecord;
|
import org.gcube.documentstore.records.AggregatedRecord;
|
||||||
import org.gcube.documentstore.records.Record;
|
import org.gcube.documentstore.records.Record;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/
|
* @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/
|
||||||
|
@ -26,6 +31,8 @@ public class AccountingPersistenceQuery {
|
||||||
|
|
||||||
private static final AccountingPersistenceQuery accountingPersistenceQuery;
|
private static final AccountingPersistenceQuery accountingPersistenceQuery;
|
||||||
|
|
||||||
|
public static final int DEFAULT_LIMIT_RESULT_NUMBER = 5;
|
||||||
|
|
||||||
private AccountingPersistenceQuery() {
|
private AccountingPersistenceQuery() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +69,18 @@ public class AccountingPersistenceQuery {
|
||||||
|
|
||||||
public SortedMap<Calendar, Info> getTimeSeries(
|
public SortedMap<Calendar, Info> getTimeSeries(
|
||||||
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
||||||
TemporalConstraint temporalConstraint, List<Filter> filters)
|
TemporalConstraint temporalConstraint, List<Filter> filters, boolean pad)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
return AccountingPersistenceBackendQueryFactory.getInstance()
|
SortedMap<Calendar, Info> ret =
|
||||||
|
AccountingPersistenceBackendQueryFactory.getInstance()
|
||||||
.getTimeSeries(aggregatedRecordClass, temporalConstraint,
|
.getTimeSeries(aggregatedRecordClass, temporalConstraint,
|
||||||
filters);
|
filters);
|
||||||
|
|
||||||
|
if(pad){
|
||||||
|
ret = padMap(ret, temporalConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getDefaultOrderingProperties(Class<? extends AggregatedRecord<?, ?>> recordClass){
|
public static String getDefaultOrderingProperties(Class<? extends AggregatedRecord<?, ?>> recordClass){
|
||||||
|
@ -76,8 +90,77 @@ public class AccountingPersistenceQuery {
|
||||||
return AggregatedRecord.OPERATION_COUNT;
|
return AggregatedRecord.OPERATION_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static JSONObject getPaddingJSONObject(
|
||||||
|
Map<Calendar, Info> unpaddedResults) throws JSONException {
|
||||||
|
Info auxInfo = new ArrayList<Info>(unpaddedResults.values()).get(0);
|
||||||
|
JSONObject auxJsonObject = auxInfo.getValue();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Iterator<String> keys = auxJsonObject.keys();
|
||||||
|
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
jsonObject.put(key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pad the data
|
||||||
|
*
|
||||||
|
* @param unpaddedData
|
||||||
|
* the data to be pad
|
||||||
|
* @param temporalConstraint
|
||||||
|
* temporalConstraint the temporal interval and the granularity
|
||||||
|
* of the data to pad
|
||||||
|
* @return the data padded taking in account the TemporalConstraint
|
||||||
|
* @throws Exception
|
||||||
|
* if fails
|
||||||
|
*/
|
||||||
|
public static SortedMap<Calendar, Info> padMap(
|
||||||
|
SortedMap<Calendar, Info> unpaddedData,
|
||||||
|
TemporalConstraint temporalConstraint) throws Exception {
|
||||||
|
|
||||||
|
JSONObject jsonObject = getPaddingJSONObject(unpaddedData);
|
||||||
|
SortedSet<Calendar> sequence = temporalConstraint.getCalendarSequence();
|
||||||
|
for (Calendar progressTime : sequence) {
|
||||||
|
Info info = unpaddedData.get(progressTime);
|
||||||
|
if (info == null) {
|
||||||
|
info = new Info(progressTime, jsonObject);
|
||||||
|
unpaddedData.put(progressTime, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unpaddedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SortedMap<NumberedFilter, SortedMap<Calendar, Info>> getTopValues(
|
||||||
|
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
||||||
|
TemporalConstraint temporalConstraint, List<Filter> filters,
|
||||||
|
String orderingProperty, boolean pad, int limit) throws Exception {
|
||||||
|
|
||||||
|
SortedMap<NumberedFilter, SortedMap<Calendar, Info>> ret;
|
||||||
|
|
||||||
|
if(orderingProperty==null){
|
||||||
|
orderingProperty = getDefaultOrderingProperties(aggregatedRecordClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = AccountingPersistenceBackendQueryFactory.getInstance()
|
||||||
|
.getTopValues(aggregatedRecordClass, temporalConstraint,
|
||||||
|
filters, orderingProperty);
|
||||||
|
|
||||||
|
|
||||||
|
if(pad){
|
||||||
|
int count = ret.size() > limit ? limit : ret.size();
|
||||||
|
while(--count >= 0){
|
||||||
|
for(NumberedFilter nf : ret.keySet()){
|
||||||
|
padMap(ret.get(nf), temporalConstraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public static SortedMap<NumberedFilter, SortedMap<Calendar, Info>> getTopValues(
|
public static SortedMap<NumberedFilter, SortedMap<Calendar, Info>> getTopValues(
|
||||||
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
Class<? extends AggregatedRecord<?,?>> aggregatedRecordClass,
|
||||||
|
@ -96,7 +179,8 @@ public class AccountingPersistenceQuery {
|
||||||
.getNextPossibleValues(aggregatedRecordClass,
|
.getNextPossibleValues(aggregatedRecordClass,
|
||||||
temporalConstraint, filters);
|
temporalConstraint, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the connection to persistence
|
* Close the connection to persistence
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.gcube.accounting.analytics;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.gcube.accounting.datamodel.BasicUsageRecord;
|
|
||||||
import org.gcube.accounting.datamodel.usagerecords.JobUsageRecord;
|
|
||||||
import org.gcube.accounting.datamodel.usagerecords.PortletUsageRecord;
|
|
||||||
import org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord;
|
|
||||||
import org.gcube.accounting.datamodel.usagerecords.StorageUsageRecord;
|
|
||||||
import org.gcube.accounting.datamodel.usagerecords.TaskUsageRecord;
|
|
||||||
import org.gcube.documentstore.records.Record;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/
|
|
||||||
*/
|
|
||||||
public class ResourceRecordQueryTest {
|
|
||||||
|
|
||||||
public class TestUsageRecord extends BasicUsageRecord {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generated Serial Version UID
|
|
||||||
*/
|
|
||||||
private static final long serialVersionUID = 1939161386352514727L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String giveMeRecordType() {
|
|
||||||
return TestUsageRecord.class.getSimpleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<Class<? extends Record>> getExpectedResourceRecordsTypes(){
|
|
||||||
Set<Class<? extends Record>> expected = new HashSet<Class<? extends Record>>();
|
|
||||||
expected.add(ServiceUsageRecord.class);
|
|
||||||
expected.add(StorageUsageRecord.class);
|
|
||||||
expected.add(JobUsageRecord.class);
|
|
||||||
expected.add(TaskUsageRecord.class);
|
|
||||||
expected.add(PortletUsageRecord.class);
|
|
||||||
return expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetResourceRecordsTypes(){
|
|
||||||
Set<Class<? extends Record>> expected = getExpectedResourceRecordsTypes();
|
|
||||||
Set<Class<? extends Record>> found = ResourceRecordQuery.getResourceRecordsTypes().keySet();
|
|
||||||
Assert.assertTrue(expected.containsAll(found));
|
|
||||||
Assert.assertTrue(found.containsAll(expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetResourceRecordsTypesWithFakeClass(){
|
|
||||||
Set<Class<? extends Record>> expected = getExpectedResourceRecordsTypes();
|
|
||||||
expected.add(TestUsageRecord.class);
|
|
||||||
Set<Class<? extends Record>> found = ResourceRecordQuery.getResourceRecordsTypes().keySet();
|
|
||||||
Assert.assertTrue(expected.containsAll(found));
|
|
||||||
Assert.assertFalse(found.containsAll(expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@
|
||||||
package org.gcube.accounting.analytics;
|
package org.gcube.accounting.analytics;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.gcube.accounting.analytics.TemporalConstraint.AggregationMode;
|
import org.gcube.accounting.analytics.TemporalConstraint.AggregationMode;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -82,7 +82,7 @@ public class TemporalConstraintTest {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Calendar> sequence = temporalConstraint.getCalendarSequence();
|
Collection<Calendar> sequence = temporalConstraint.getCalendarSequence();
|
||||||
if(aggregationMode.ordinal()<=AggregationMode.HOURLY.ordinal()){
|
if(aggregationMode.ordinal()<=AggregationMode.HOURLY.ordinal()){
|
||||||
logger.debug("{} generate the following sequence (size {}) {}",
|
logger.debug("{} generate the following sequence (size {}) {}",
|
||||||
temporalConstraint, sequence.size(),
|
temporalConstraint, sequence.size(),
|
||||||
|
|
Loading…
Reference in New Issue