diff --git a/pom.xml b/pom.xml index a5b2adf..3bb0ee2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.gcube.accounting accounting-analytics - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT accounting-analytics diff --git a/src/main/java/org/gcube/accounting/analytics/Filter.java b/src/main/java/org/gcube/accounting/analytics/Filter.java index 17d8aa0..a6d09f3 100644 --- a/src/main/java/org/gcube/accounting/analytics/Filter.java +++ b/src/main/java/org/gcube/accounting/analytics/Filter.java @@ -7,7 +7,7 @@ package org.gcube.accounting.analytics; * @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/ * */ -public class Filter { +public class Filter implements Comparable { protected String key; protected String value; @@ -49,4 +49,53 @@ public class Filter { this.value = value; } + @Override + public String toString(){ + return String.format("{ \"%s\" : \"%s\" }", key, value); + } + + /** {@inheritDoc} */ + @Override + public int compareTo(Filter filter) { + int compareResult = this.key.compareTo(filter.key); + if(compareResult == 0){ + compareResult = this.value.compareTo(filter.value); + } + return compareResult; + } + + + /** {@inheritDoc} */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Filter other = (Filter) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + } diff --git a/src/main/java/org/gcube/accounting/analytics/ResourceRecordQuery.java b/src/main/java/org/gcube/accounting/analytics/ResourceRecordQuery.java index b35445d..602a85f 100644 --- a/src/main/java/org/gcube/accounting/analytics/ResourceRecordQuery.java +++ b/src/main/java/org/gcube/accounting/analytics/ResourceRecordQuery.java @@ -6,7 +6,6 @@ package org.gcube.accounting.analytics; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -29,139 +28,156 @@ import org.slf4j.LoggerFactory; * @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/ */ public class ResourceRecordQuery { - - private static Logger logger = LoggerFactory.getLogger(ResourceRecordQuery.class); - + + private static Logger logger = LoggerFactory + .getLogger(ResourceRecordQuery.class); + protected static Map, Set> resourceRecords = null; - + /** - * Return a Map containing a set of required fields for each Resource + * Return a Map containing a set of required fields for each Resource * Records Types + * * @return the Map */ public static synchronized Map, Set> getResourceRecordsTypes() { - if(resourceRecords==null){ + if (resourceRecords == null) { resourceRecords = new HashMap, Set>(); - Collection> resourceRecordsTypes = RecordUtility.getRecordClassesFound().values(); - for(Class resourceRecordsType : resourceRecordsTypes){ + Collection> resourceRecordsTypes = RecordUtility + .getRecordClassesFound().values(); + for (Class resourceRecordsType : resourceRecordsTypes) { try { Record record = resourceRecordsType.newInstance(); - resourceRecords.put(resourceRecordsType, record.getRequiredFields()); + resourceRecords.put(resourceRecordsType, + record.getRequiredFields()); } catch (InstantiationException | IllegalAccessException e) { - logger.error(String.format("Unable to correctly istantiate %s", resourceRecordsType.getSimpleName()), 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 + * + * @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(); + public ResourceRecordQuery() throws NoAvailableScopeException, + NoUsableAccountingPersistenceQueryFound { + this.accountingPersistenceQuery = AccountingPersistenceBackendQueryFactory + .getInstance(); } - - protected static JSONObject getPaddingJSONObject(Map unpaddedResults) throws JSONException{ + + protected static JSONObject getPaddingJSONObject( + Map unpaddedResults) throws JSONException { Info auxInfo = new ArrayList(unpaddedResults.values()).get(0); - JSONObject auxJsonObject = auxInfo.getValue(); + JSONObject auxJsonObject = auxInfo.getValue(); @SuppressWarnings("unchecked") Iterator keys = auxJsonObject.keys(); - + JSONObject jsonObject = new JSONObject(); - while(keys.hasNext()){ + 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 + * + * @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 + * @throws Exception + * if fails */ - public static List getPaddedResults(Map unpaddedData, + public static List getPaddedResults(Map unpaddedData, TemporalConstraint temporalConstraint) throws Exception { JSONObject jsonObject = getPaddingJSONObject(unpaddedData); - + List paddedResults = new ArrayList(); List sequence = temporalConstraint.getCalendarSequence(); - for(Calendar progressTime : sequence){ - if(unpaddedData.get(progressTime)!=null){ + for (Calendar progressTime : sequence) { + if (unpaddedData.get(progressTime) != null) { paddedResults.add(unpaddedData.get(progressTime)); - }else{ + } else { Info info = new Info(progressTime, jsonObject); paddedResults.add(info); } } - + return paddedResults; } - + /** * Return results with padding if pad is set to true. - * @param usageRecordType 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 + * + * @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 getInfo(@SuppressWarnings("rawtypes") Class usageRecordType, - TemporalConstraint temporalConstraint, List filters, boolean pad) throws Exception { - Map unpaddedResults = accountingPersistenceQuery.query(usageRecordType, temporalConstraint, filters); - if(!pad){ + public List getInfo( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, List filters, + boolean pad) throws Exception { + + Map unpaddedResults = accountingPersistenceQuery + .getTimeSeries(aggregatedRecordClass, temporalConstraint, + filters); + + if (!pad) { return new ArrayList(unpaddedResults.values()); } + return getPaddedResults(unpaddedResults, temporalConstraint); } - + /** * Return unpadded results - * @param usageRecordType 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 + * + * @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 getInfo(@SuppressWarnings("rawtypes") Class usageRecordType, - TemporalConstraint temporalConstraint, List filters) throws Exception{ - return getInfo(usageRecordType, temporalConstraint, filters, false); + public List getInfo( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, List filters) + throws Exception { + + return getInfo(aggregatedRecordClass, temporalConstraint, filters, + false); + } - - /** - * Return the list of key valid for queries a certain usage record type - * @param usageRecordType the usage record type - * @return a set containing the list of key - * @throws Exception if fails - */ - public List getKeys(@SuppressWarnings("rawtypes") Class usageRecordType) throws Exception{ - Set keys = accountingPersistenceQuery.getKeys(usageRecordType); - List toSort = new ArrayList(keys); - Collections.sort(toSort); - return toSort; - } - - public List getPossibleValuesForKey(@SuppressWarnings("rawtypes") Class usageRecordType, String key) throws Exception { - Set keys = accountingPersistenceQuery.getPossibleValuesForKey(usageRecordType, key); - List toSort = new ArrayList(keys); - Collections.sort(toSort); - return toSort; - } - + } diff --git a/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceBackendQuery.java b/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceBackendQuery.java index 8e32ff6..5509b51 100644 --- a/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceBackendQuery.java +++ b/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceBackendQuery.java @@ -6,88 +6,98 @@ package org.gcube.accounting.analytics.persistence; import java.util.Calendar; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; import org.gcube.accounting.analytics.Filter; import org.gcube.accounting.analytics.Info; import org.gcube.accounting.analytics.TemporalConstraint; import org.gcube.documentstore.records.AggregatedRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/ - * */ public abstract class AccountingPersistenceBackendQuery { - private static final Logger logger = LoggerFactory.getLogger(AccountingPersistenceBackendQuery.class); - public static final int KEY_VALUES_LIMIT = 25; protected abstract void prepareConnection(AccountingPersistenceBackendQueryConfiguration configuration) throws Exception; - protected abstract Map reallyQuery(@SuppressWarnings("rawtypes") Class usageRecordType, - TemporalConstraint temporalConstraint, List filters) throws Exception; - /** * Query the persistence obtaining a Map where the date is the key and * the #Info is the value. The result is relative to an Usage Record Type, * respect a TemporalConstraint and can be applied one or more filters. - * @param recordClass the Usage Record Type of interest + * @param aggregatedRecordClass the Record Class of interest * @param temporalConstraint the TemporalConstraint (interval and aggregation) - * @param filters the filter for the query. If null or empty string get all - * data. The filters are evaluated in the order the are presented and are - * considered in AND + * @param filters list of filter to obtain the time series. If null or + * empty list get all data for the interested Record Class with the applying + * temporal constraint. All Filter must have not null and not empty key and + * value. + * The filters are must be related to different keys and are in AND. + * If the list contains more than one filter with the same key an Exception + * is thrown. * @return the Map containing for each date in the required interval the * requested data * @throws Exception if fails */ - public Map query(@SuppressWarnings("rawtypes") Class recordClass, - TemporalConstraint temporalConstraint, List filters) throws Exception{ - logger.trace("Request query: RecordClass={}, {}={}, {}s={}", recordClass.newInstance().getRecordType(), - TemporalConstraint.class.getSimpleName(), temporalConstraint.toString(), - Filter.class.getSimpleName(), filters); - return reallyQuery(recordClass, temporalConstraint, filters); - } - - /** - * Return the list of key valid for queries a certain usage record type - * @param recordClass the usage record class - * @return a set containing the list of key - * @throws Exception if fails - */ - public abstract Set getKeys(@SuppressWarnings("rawtypes") Class recordClass) throws Exception; + public abstract SortedMap getTimeSeries( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, + List filters) throws Exception; /** - * Return the list of possible values for a key for a certain usage record - * type. - * The result are limited to {@link #KEY_VALUES_LIMIT} value. - * If you want a different limit please use the - * {@link #getPossibleValuesForKey(Class, String, int)} function. - * Invoking this function has the same effect of invoking - * {@link #getPossibleValuesForKey(Class, String, int)} function passing - * {@link #KEY_VALUES_LIMIT} has third argument. - * @param recordClass the usage record type - * @param key the key + * Return a SortedMap containing the TimeSeries for top values for a + * certain key taking in account all Filters. The key is identified + * adding a Filter with a null value. Only one Filter with null value is + * allowed otherwise an Exception is thrown. + * The values are ordered from the most occurred value. + * @param aggregatedRecordClass the Usage Record Class of interest + * @param temporalConstraint the TemporalConstraint (interval and aggregation) + * @param filters list of filter to obtain the time series. If null or + * empty list get all data for the interested Record Class with the applying + * temporal constraint. All Filter (except one) must have not null and not + * empty key and value. One Filter must have not null and not + * empty key and a null value. + * The filters are must be related to different keys and are in AND. + * If the list contains more than one filter with the same key an Exception + * is thrown. + * If the list contains more than one filter with null value an Exception + * is thrown. * @return a set containing the list of possible values * @throws Exception if fails */ - public abstract Set getPossibleValuesForKey(@SuppressWarnings("rawtypes") Class recordClass, String key) throws Exception; + public abstract SortedMap> getTopValues( + Class> recordClass, + TemporalConstraint temporalConstraint, List filters) + throws Exception; + /** - * Return the list of possible values for a key for a certain usage record - * type. - * The result are limited to limit value. When limit is <= 0 this means - * no limit. - * @param recordClass the usage record type - * @param key the key - * @param limit limit of result to return. + * Return the list of possible values for a key for a certain usageRecord + * taking in account all Filters. The value for a certain key is identified + * adding a Filter with a null value. Only one Filter with null value is + * allowed otherwise an Exception is thrown. + * The values are ordered from the most occurred value. + * @param aggregatedRecordClass the Usage Record Class of interest + * @param temporalConstraint the TemporalConstraint (interval and aggregation) + * @param filters list of filter to obtain the time series. If null or + * empty list get all data for the interested Record Class with the applying + * temporal constraint. All Filter (except one) must have not null and not + * empty key and value. One Filter must have not null and not + * empty key and a null value. + * The filters are must be related to different keys and are in AND. + * If the list contains more than one filter with the same key an Exception + * is thrown. + * If the list contains more than one filter with null value an Exception + * is thrown. * @return a set containing the list of possible values * @throws Exception if fails */ - public abstract Set getPossibleValuesForKey(@SuppressWarnings("rawtypes") Class recordClass, String key, int limit) throws Exception; + public abstract SortedSet getNextPossibleValues( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, + List filters) throws Exception; /** * Close the connection to persistence diff --git a/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceQuery.java b/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceQuery.java index cb64479..eae42bc 100644 --- a/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceQuery.java +++ b/src/main/java/org/gcube/accounting/analytics/persistence/AccountingPersistenceQuery.java @@ -6,78 +6,94 @@ package org.gcube.accounting.analytics.persistence; import java.util.Calendar; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; import org.gcube.accounting.analytics.Filter; import org.gcube.accounting.analytics.Info; import org.gcube.accounting.analytics.TemporalConstraint; +import org.gcube.accounting.datamodel.UsageRecord; import org.gcube.documentstore.records.AggregatedRecord; +import org.gcube.documentstore.records.Record; /** * @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/ - * + * */ public class AccountingPersistenceQuery { private static final AccountingPersistenceQuery accountingPersistenceQuery; - - private AccountingPersistenceQuery(){} - + + private AccountingPersistenceQuery() { + } + static { accountingPersistenceQuery = new AccountingPersistenceQuery(); } - - protected static synchronized AccountingPersistenceQuery getInstance(){ + + protected static synchronized AccountingPersistenceQuery getInstance() { return accountingPersistenceQuery; } - - - /** - * Query the persistence obtaining a Map where the date is the key and - * the #Info is the value. The result is relative to an Usage Record Type, - * respect a TemporalConstraint and can be applied one or more filters. - * @param recordClass the Usage Record Type of interest - * @param temporalConstraint the TemporalConstraint (interval and aggregation) - * @param filters the filter for the query. If null or empty string get all - * data. The filters are evaluated in the order the are presented and are - * considered in AND - * @return the Map containing for each date in the required interval the - * requested data - * @throws Exception if fails - */ - public Map query(@SuppressWarnings("rawtypes") Class recordClass, - TemporalConstraint temporalConstraint, List filters) throws Exception{ - return AccountingPersistenceBackendQueryFactory.getInstance().query(recordClass, temporalConstraint, filters); + + public static SortedSet getQuerableKeys( + @SuppressWarnings("rawtypes") AggregatedRecord instance) + throws Exception { + SortedSet properties = new TreeSet<>( + instance.getRequiredFields()); + + properties.removeAll(instance.getAggregatedFields()); + properties.removeAll(instance.getComputedFields()); + properties.remove(Record.ID); + properties.remove(Record.CREATION_TIME); + properties.remove(Record.RECORD_TYPE); + properties.remove(UsageRecord.SCOPE); + + return properties; } - - /** - * Return the list of key valid for queries a certain usage record type - * @param recordClass the usage record type - * @return a set containing the list of key - * @throws Exception if fails - */ - public Set getKeys(@SuppressWarnings("rawtypes") Class recordClass) throws Exception { - return AccountingPersistenceBackendQueryFactory.getInstance().getKeys(recordClass); + + public static SortedSet getQuerableKeys( + Class> aggregatedRecordClass) + throws Exception { + AggregatedRecord instance = aggregatedRecordClass.newInstance(); + return getQuerableKeys(instance); } - - - /** - * Return the list of possible values for a key for a certain usage record type - * @param usageRecordType the usage record type - * @param key the key - * @return a set containing the list of possible values - * @throws Exception if fails - */ - public Set getPossibleValuesForKey(@SuppressWarnings("rawtypes") Class usageRecordType, String key) throws Exception { - return AccountingPersistenceBackendQueryFactory.getInstance().getPossibleValuesForKey(usageRecordType, key); + + public Map getTimeSeries( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, List filters) + throws Exception { + return AccountingPersistenceBackendQueryFactory.getInstance() + .getTimeSeries(aggregatedRecordClass, temporalConstraint, + filters); } - + + public static SortedMap> getTopValues( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, List filters) + throws Exception { + return AccountingPersistenceBackendQueryFactory.getInstance() + .getTopValues(aggregatedRecordClass, temporalConstraint, + filters); + } + + public static SortedSet getNextPossibleValues( + Class> aggregatedRecordClass, + TemporalConstraint temporalConstraint, List filters) + throws Exception { + return AccountingPersistenceBackendQueryFactory.getInstance() + .getNextPossibleValues(aggregatedRecordClass, + temporalConstraint, filters); + } + /** * Close the connection to persistence - * @throws Exception if the close fails + * + * @throws Exception + * if the close fails */ public void close() throws Exception { AccountingPersistenceBackendQueryFactory.getInstance().close(); } - + }