620 lines
19 KiB
Java
620 lines
19 KiB
Java
/**
|
|
*
|
|
*/
|
|
package org.gcube.accounting.datamodel;
|
|
|
|
import java.io.Serializable;
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Field;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Calendar;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
|
|
import org.gcube.accounting.datamodel.decorators.AggregatedField;
|
|
import org.gcube.accounting.datamodel.decorators.ComputedField;
|
|
import org.gcube.accounting.datamodel.decorators.FieldAction;
|
|
import org.gcube.accounting.datamodel.decorators.FieldDecorator;
|
|
import org.gcube.accounting.datamodel.decorators.RequiredField;
|
|
import org.gcube.accounting.datamodel.validations.annotations.NotEmpty;
|
|
import org.gcube.accounting.datamodel.validations.annotations.NotEmptyIfNotNull;
|
|
import org.gcube.accounting.datamodel.validations.annotations.ValidInteger;
|
|
import org.gcube.accounting.datamodel.validations.annotations.ValidLong;
|
|
import org.gcube.accounting.datamodel.validations.annotations.ValidOperationResult;
|
|
import org.gcube.accounting.exception.InvalidValueException;
|
|
import org.gcube.common.scope.api.ScopeProvider;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/
|
|
*
|
|
*/
|
|
public abstract class BasicUsageRecord implements UsageRecord, Serializable {
|
|
|
|
/**
|
|
* Generated Serial Version UID
|
|
*/
|
|
private static final long serialVersionUID = -2060728578456796388L;
|
|
|
|
private static Logger logger = LoggerFactory.getLogger(BasicUsageRecord.class);
|
|
|
|
/**
|
|
* KEY for : The unique identifier for the UsageRecord.
|
|
* The ID is automatically Created. Set the ID only if you really know what
|
|
* you are going to do.
|
|
*/
|
|
@RequiredField @NotEmpty
|
|
protected static final String ID = "id";
|
|
/**
|
|
* KEY for : The user (or the Consumer Identity, that in the S2S
|
|
* communication is another service).
|
|
*/
|
|
@RequiredField @NotEmpty
|
|
public static final String CONSUMER_ID = "consumerId";
|
|
/**
|
|
* KEY for : The instant when the UR was created. The value will be recorded
|
|
* in UTC milliseconds from the epoch.
|
|
*/
|
|
@RequiredField @ValidLong
|
|
public static final String CREATION_TIME = "creationTime";
|
|
|
|
/**
|
|
* Internal USE ONLY.
|
|
* KEY for : The Class Name of the represented {#Usage Record}
|
|
*/
|
|
@RequiredField @NotEmpty
|
|
protected static final String USAGE_RECORD_TYPE = "usageRecordType";
|
|
|
|
/**
|
|
* KEY for : The accounting scope
|
|
*/
|
|
@RequiredField @NotEmpty
|
|
public static final String SCOPE = "scope";
|
|
|
|
/**
|
|
* KEY for : The Operation Result of the accounted operation.
|
|
* The value is expressed as
|
|
* {@link #org.gcube.accounting.datamodel.UsageRecord.OperationResult}
|
|
*/
|
|
@RequiredField @ValidOperationResult
|
|
public static final String OPERATION_RESULT = "operationResult";
|
|
|
|
|
|
/** resource-specific properties */
|
|
protected Map<String, Comparable<? extends Serializable>> resourceProperties;
|
|
|
|
protected Map<String, List<FieldAction>> validation;
|
|
protected Set<String> requiredFields;
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Set<String> getRequiredFields() {
|
|
return requiredFields;
|
|
}
|
|
|
|
protected Set<String> computedFields;
|
|
|
|
/**
|
|
* KEY for : Used in aggregated record. Represent the left end of the time
|
|
* interval covered by this {#Usage Record}
|
|
* The value will be recorded in UTC milliseconds from the epoch.
|
|
*/
|
|
@AggregatedField @ValidLong
|
|
protected static final String START_TIME = "startTime";
|
|
/**
|
|
* KEY for : Used in aggregated record. Represent the right end of the time
|
|
* interval covered by this {#Usage Record}
|
|
* The value will be recorded in UTC milliseconds from the epoch.
|
|
*/
|
|
@AggregatedField @ValidLong
|
|
protected static final String END_TIME = "endTime";
|
|
/**
|
|
* Internal USE ONLY.
|
|
* KEY for : Indicate that the record is an aggregation
|
|
*/
|
|
@AggregatedField @NotEmptyIfNotNull
|
|
protected static final String AGGREGATED = "aggregated";
|
|
|
|
/**
|
|
* KEY for : Indicate The Number of Aggregated Operation
|
|
*/
|
|
@AggregatedField @ValidInteger
|
|
protected static final String OPERATION_COUNT = "operationCount";
|
|
|
|
protected Set<String> aggregatedFields;
|
|
|
|
protected static Set<Field> getAllFields(Class<?> type) {
|
|
Set<Field> fields = new HashSet<Field>();
|
|
for (Class<?> c = type; c != null; c = c.getSuperclass())
|
|
fields.addAll(Arrays.asList(c.getDeclaredFields()));
|
|
return fields;
|
|
}
|
|
|
|
protected void initializeValidation() {
|
|
//logger.trace("Initializing Field Validators");
|
|
Set<Field> fields = getAllFields(this.getClass());
|
|
|
|
for(Field field : fields){
|
|
boolean defaultAccessibility = field.isAccessible();
|
|
field.setAccessible(true);
|
|
String keyString;
|
|
try {
|
|
keyString = (String) field.get(null);
|
|
} catch (Exception e) {
|
|
continue;
|
|
}
|
|
List<FieldAction> fieldValidators = new ArrayList<FieldAction>();
|
|
validation.put(keyString, fieldValidators);
|
|
for (Annotation annotation : field.getAnnotations()){
|
|
if (annotation.annotationType().isAnnotationPresent(FieldDecorator.class)){
|
|
Class<? extends FieldAction> managedClass = ((FieldDecorator)annotation.annotationType().getAnnotation(FieldDecorator.class)).managed();
|
|
FieldAction validator;
|
|
try {
|
|
validator = managedClass.newInstance();
|
|
} catch (InstantiationException | IllegalAccessException e) {
|
|
logger.error("{} {}", keyString, annotation, e);
|
|
continue;
|
|
}
|
|
fieldValidators.add(validator);
|
|
}
|
|
if(annotation.annotationType().isAssignableFrom(RequiredField.class)){
|
|
requiredFields.add(keyString);
|
|
}
|
|
if(annotation.annotationType().isAssignableFrom(AggregatedField.class)){
|
|
aggregatedFields.add(keyString);
|
|
}
|
|
if(annotation.annotationType().isAssignableFrom(ComputedField.class)){
|
|
computedFields.add(keyString);
|
|
}
|
|
}
|
|
field.setAccessible(defaultAccessibility);
|
|
}
|
|
/*
|
|
logger.trace("Required Fields {}", requiredFields);
|
|
logger.trace("Aggregated Fields {}", aggregatedFields);
|
|
logger.trace("Computed Fields {}", computedFields);
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Initialize variable
|
|
*/
|
|
private void init() {
|
|
this.validation = new HashMap<String, List<FieldAction>>();
|
|
this.requiredFields = new HashSet<String>();
|
|
this.aggregatedFields = new HashSet<String>();
|
|
this.computedFields = new HashSet<String>();
|
|
this.resourceProperties = new HashMap<String, Comparable<? extends Serializable>>();
|
|
initializeValidation();
|
|
try {
|
|
this.setScope(ScopeProvider.instance.get());
|
|
} catch(Exception e) {
|
|
logger.warn("Unable to automaticcally set the scope using scope provider. The record will not be valid if the scope will not be explicitly set.");
|
|
}
|
|
}
|
|
|
|
public BasicUsageRecord(){
|
|
init();
|
|
this.resourceProperties.put(ID, UUID.randomUUID().toString());
|
|
this.setUsageRecordType();
|
|
Calendar calendar = Calendar.getInstance();
|
|
this.resourceProperties.put(CREATION_TIME, calendar.getTimeInMillis());
|
|
}
|
|
|
|
public BasicUsageRecord(Map<String, Comparable<? extends Serializable>> properties) throws InvalidValueException {
|
|
init();
|
|
setResourceProperties(properties);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String getUsageRecordType() {
|
|
return (String) this.resourceProperties.get(USAGE_RECORD_TYPE);
|
|
}
|
|
|
|
protected abstract String giveMeUsageRecordType();
|
|
|
|
protected void setUsageRecordType(){
|
|
this.resourceProperties.put(USAGE_RECORD_TYPE, this.giveMeUsageRecordType());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String getId() {
|
|
return (String) this.resourceProperties.get(ID);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setId(String id) throws InvalidValueException {
|
|
setResourceProperty(ID, id);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String getConsumerId() {
|
|
return (String) this.resourceProperties.get(CONSUMER_ID);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setConsumerId(String consumerId) throws InvalidValueException {
|
|
setResourceProperty(CONSUMER_ID, consumerId);
|
|
}
|
|
|
|
protected Calendar timestampStringToCalendar(long millis){
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.setTimeInMillis(millis);
|
|
return calendar;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Calendar getCreationTime() {
|
|
long millis = (Long) this.resourceProperties.get(CREATION_TIME);
|
|
return timestampStringToCalendar(millis);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setCreationTime(Calendar creationTime) throws InvalidValueException {
|
|
setResourceProperty(CREATION_TIME, creationTime.getTimeInMillis());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public String getScope() {
|
|
return (String) this.resourceProperties.get(SCOPE);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setScope(String scope) throws InvalidValueException {
|
|
setResourceProperty(SCOPE, scope);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Map<String, Comparable<? extends Serializable>> getResourceProperties() {
|
|
return new HashMap<String, Comparable<? extends Serializable>>(this.resourceProperties);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setResourceProperties(Map<String, Comparable<? extends Serializable>> properties) throws InvalidValueException {
|
|
Map<String, Comparable<? extends Serializable>> validated = validateProperties(properties);
|
|
this.resourceProperties = new HashMap<String, Comparable<? extends Serializable>>(validated);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public Comparable<? extends Serializable> getResourceProperty(String key) {
|
|
return this.resourceProperties.get(key);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void setResourceProperty(String key, Comparable<? extends Serializable> value) throws InvalidValueException {
|
|
Comparable<? extends Serializable> checkedValue = validateField(key, value);
|
|
if(checkedValue == null){
|
|
this.resourceProperties.remove(key);
|
|
}else{
|
|
this.resourceProperties.put(key, checkedValue);
|
|
}
|
|
}
|
|
|
|
// AGGREGATION
|
|
/* --------------------------------------- */
|
|
/**
|
|
* Return the left end of the time interval covered by this {#UsageRecord}
|
|
* @return Start Time
|
|
*/
|
|
protected long getStartTimeInMillis() {
|
|
return (Long) this.resourceProperties.get(START_TIME);
|
|
}
|
|
|
|
/**
|
|
* Return the left end of the time interval covered by this {#UsageRecord}
|
|
* @return Start Time
|
|
*/
|
|
protected Calendar getStartTimeAsCalendar() {
|
|
long millis = getStartTimeInMillis();
|
|
return timestampStringToCalendar(millis);
|
|
}
|
|
|
|
/**
|
|
* Set the left end of the time interval covered by this {#UsageRecord}
|
|
* @param startTime Start Time
|
|
* @throws InvalidValueException
|
|
*/
|
|
protected void setStartTime(Calendar startTime) throws InvalidValueException {
|
|
setResourceProperty(START_TIME, startTime.getTimeInMillis());
|
|
}
|
|
|
|
/**
|
|
* Return the right end of the time interval covered by this {#UsageRecord}
|
|
* @return End Time
|
|
*/
|
|
protected long getEndTimeInMillis() {
|
|
return (Long) this.resourceProperties.get(END_TIME);
|
|
}
|
|
|
|
/**
|
|
* Return the right end of the time interval covered by this {#UsageRecord}
|
|
* @return End Time
|
|
*/
|
|
protected Calendar getEndTimeAsCalendar() {
|
|
long millis = getEndTimeInMillis();
|
|
return timestampStringToCalendar(millis);
|
|
}
|
|
|
|
/**
|
|
* Set the right end of the time interval covered by this {#UsageRecord}
|
|
* @param endTime End Time
|
|
* @throws InvalidValueException
|
|
*/
|
|
protected void setEndTime(Calendar endTime) throws InvalidValueException {
|
|
setResourceProperty(END_TIME, endTime.getTimeInMillis());
|
|
}
|
|
|
|
protected int getOperationCount() {
|
|
return (Integer) this.resourceProperties.get(OPERATION_COUNT);
|
|
}
|
|
|
|
protected void setOperationCount(int operationCount) throws InvalidValueException {
|
|
setResourceProperty(OPERATION_COUNT, operationCount);
|
|
}
|
|
|
|
/* --------------------------------------- */
|
|
|
|
|
|
|
|
protected Comparable<? extends Serializable> validateField(String key, Comparable<? extends Serializable> value) throws InvalidValueException {
|
|
if(key == null){
|
|
throw new InvalidValueException("The key of property to set cannot be null");
|
|
}
|
|
Comparable<? extends Serializable> checkedValue = value;
|
|
List<FieldAction> fieldValidators = validation.get(key);
|
|
if(fieldValidators!=null){
|
|
for(FieldAction fieldValidator : fieldValidators){
|
|
if(aggregatedFields.contains(key)){
|
|
// TODO
|
|
}
|
|
if(computedFields.contains(key)){
|
|
logger.debug("{} is a computed field. To be calculated all the required fields to calcutalate it MUST be set. In any case the provided value will be ignored.", key);
|
|
}
|
|
try {
|
|
checkedValue = fieldValidator.validate(key, checkedValue, this);
|
|
} catch (InvalidValueException e) {
|
|
logger.error(String.format("The provided value %s is NOT valid for field with key %s.", checkedValue.toString(), key));
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
return checkedValue;
|
|
}
|
|
|
|
protected Map<String, Comparable<? extends Serializable>> validateProperties(Map<String, Comparable<? extends Serializable>> properties) throws InvalidValueException{
|
|
Map<String, Comparable<? extends Serializable>> validated = new HashMap<String, Comparable<? extends Serializable>>();
|
|
for(String key : properties.keySet()){
|
|
Comparable<? extends Serializable> value = properties.get(key);
|
|
validated.put(key, validateField(key, value));
|
|
}
|
|
return validated;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void validate() throws InvalidValueException {
|
|
validateProperties(this.resourceProperties);
|
|
Set<String> notPresentProperties = new HashSet<String>();
|
|
for(String key : this.requiredFields){
|
|
if(!this.resourceProperties.containsKey(key)){
|
|
notPresentProperties.add(key);
|
|
}
|
|
}
|
|
if(!notPresentProperties.isEmpty()){
|
|
String pluralManagement = notPresentProperties.size() == 1 ? "y" : "ies";
|
|
throw new InvalidValueException(String.format("The Usage Record does not contain the following required propert%s %s", pluralManagement, notPresentProperties.toString()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return resourceProperties.toString();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public OperationResult getOperationResult(){
|
|
return OperationResult.valueOf((String) this.resourceProperties.get(OPERATION_RESULT));
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @throws InvalidValueException
|
|
*/
|
|
@Override
|
|
public void setOperationResult(OperationResult operationResult) throws InvalidValueException {
|
|
setResourceProperty(OPERATION_RESULT, operationResult);
|
|
}
|
|
|
|
|
|
/**
|
|
* Compare this UsageRecord instance with the one provided as argument
|
|
* @param usageRecord the Usage Record to compare
|
|
* @return 0 is and only if the UsageRecord provided as parameter
|
|
* contains all and ONLY the parameters contained in this instance.
|
|
* If the number of parameters differs, the methods return the difference
|
|
* between the number of parameter in this instance and the ones in the
|
|
* UsageRecord provided as parameter.
|
|
* If the size is the same but the UsageRecord provided as parameter does
|
|
* not contains all parameters in this instance, -1 is returned.
|
|
*/
|
|
@Override
|
|
public int compareTo(UsageRecord usageRecord) {
|
|
Set<Entry<String, Comparable<? extends Serializable>>> thisSet = this.resourceProperties.entrySet();
|
|
Set<Entry<String, Comparable<? extends Serializable>>> usageRecordSet = usageRecord.getResourceProperties().entrySet();
|
|
if(thisSet.size() != usageRecordSet.size()){
|
|
return thisSet.size() - usageRecordSet.size();
|
|
}
|
|
if(usageRecordSet.containsAll(thisSet)){
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
private static final String AGGREGATED_PREFIX = "Aggregated";
|
|
|
|
@SuppressWarnings("unchecked")
|
|
protected static Class<? extends UsageRecord> getClass(String usageRecordName, boolean aggregated) throws ClassNotFoundException {
|
|
Class<? extends UsageRecord> clz = null;
|
|
|
|
Class<? extends UsageRecord> utilityClass = org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord.class;
|
|
if(aggregated){
|
|
utilityClass = org.gcube.accounting.aggregation.AggregatedServiceUsageRecord.class;
|
|
}
|
|
|
|
String classCanonicalName = utilityClass.getCanonicalName();
|
|
classCanonicalName = classCanonicalName.replace(utilityClass.getSimpleName().replace(AGGREGATED_PREFIX, ""), usageRecordName);
|
|
|
|
try {
|
|
clz = (Class<? extends UsageRecord>) Class.forName(classCanonicalName);
|
|
} catch (ClassNotFoundException e) {
|
|
logger.error("Unable to retrieve class {}", classCanonicalName);
|
|
throw e;
|
|
}
|
|
|
|
return clz;
|
|
}
|
|
|
|
/**
|
|
* This method use the resourceType value contained in the Map to instance
|
|
* the right UsageRecord class and return it. If the type implementation
|
|
* does not exist or the validation of one or more field validation fails
|
|
* an exception is thrown
|
|
* @param usageRecordMap
|
|
* @return the instance of the UsageRecord class.
|
|
* @throws Exception if fails
|
|
*/
|
|
public static UsageRecord getUsageRecord(Map<String, Comparable<? extends Serializable>> usageRecordMap) throws Exception {
|
|
String className = (String) usageRecordMap.get(USAGE_RECORD_TYPE);
|
|
boolean aggregated = false;
|
|
try {
|
|
aggregated = (Boolean) usageRecordMap.get(AGGREGATED);
|
|
}catch(Exception e){
|
|
try{
|
|
aggregated = Boolean.parseBoolean((String)usageRecordMap.get(AGGREGATED));
|
|
} catch(Exception e1){}
|
|
}
|
|
|
|
Class<? extends UsageRecord> clz = getClass(className, aggregated);
|
|
logger.debug("Trying to instantiate {}", clz);
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
Class[] usageRecordArgTypes = { Map.class };
|
|
Constructor<? extends UsageRecord> usageRecordConstructor = clz.getDeclaredConstructor(usageRecordArgTypes);
|
|
Object[] usageRecordArguments = {usageRecordMap};
|
|
|
|
UsageRecord usageRecord = usageRecordConstructor.newInstance(usageRecordArguments);
|
|
|
|
logger.debug("Created Usage Record : {}", usageRecord);
|
|
|
|
return usageRecord;
|
|
}
|
|
|
|
/*
|
|
* IT DOES NOT WORK
|
|
* @SuppressWarnings("unchecked")
|
|
* public static Map<String,Serializable> getMapFromString(String serializedMap) throws IOException, ClassNotFoundException {
|
|
* ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedMap.getBytes());
|
|
* ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
|
|
* return ((Map<String,Serializable>) objectInputStream.readObject());
|
|
* }
|
|
*/
|
|
|
|
|
|
private final static String LINE_FREFIX = "{";
|
|
private final static String LINE_SUFFIX = "}";
|
|
private final static String KEY_VALUE_PAIR_SEPARATOR = ",";
|
|
private final static String KEY_VALUE_LINKER = "=";
|
|
|
|
protected static Map<String, Comparable<? extends Serializable>> getMapFromString(String serializedMap){
|
|
/* Checking line sanity */
|
|
if(!serializedMap.startsWith(LINE_FREFIX) && !serializedMap.endsWith(LINE_SUFFIX)){
|
|
return null;
|
|
}
|
|
|
|
/* Cleaning prefix and suffix to parse line */
|
|
serializedMap = serializedMap.replace(LINE_FREFIX, "");
|
|
serializedMap = serializedMap.replace(LINE_SUFFIX, "");
|
|
|
|
Map<String, Comparable<? extends Serializable>> map = new HashMap<String, Comparable<? extends Serializable>>();
|
|
|
|
String[] pairs = serializedMap.split(KEY_VALUE_PAIR_SEPARATOR);
|
|
for (int i=0;i<pairs.length;i++) {
|
|
String pair = pairs[i];
|
|
pair.trim();
|
|
|
|
String[] keyValue = pair.split(KEY_VALUE_LINKER);
|
|
String key = keyValue[0].trim();
|
|
Comparable<? extends Serializable> value = keyValue[1].trim();
|
|
map.put(key, value);
|
|
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param serializedMap
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
public static UsageRecord getUsageRecord(String serializedMap) throws Exception {
|
|
Map<String,Comparable<? extends Serializable>> map = getMapFromString(serializedMap);
|
|
return getUsageRecord(map);
|
|
}
|
|
}
|