accounting-lib/src/main/java/org/gcube/accounting/datamodel/BasicUsageRecord.java

476 lines
14 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.ValidLong;
import org.gcube.accounting.datamodel.validations.annotations.ValidOperationResult;
import org.gcube.accounting.exception.InvalidValueException;
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);
@RequiredField @NotEmpty
public static final String ID = "id";
@RequiredField @NotEmpty
public static final String CREATOR_ID = "creatorId";
@RequiredField @NotEmpty
public static final String CONSUMER_ID = "consumerId";
@RequiredField @ValidLong
public static final String CREATION_TIME = "creationTime";
@AggregatedField @ValidLong
protected static final String START_TIME = "startTime";
@AggregatedField @ValidLong
protected static final String END_TIME = "endTime";
@RequiredField @NotEmpty
protected static final String USAGE_RECORD_TYPE = "usageRecordType";
@RequiredField @NotEmpty
public static final String RESOURCE_SCOPE = "resourceScope";
@AggregatedField @NotEmptyIfNotNull
protected static final String AGGREGATED = "aggregated";
@AggregatedField @NotEmptyIfNotNull
protected static final String AGGREGATED_USAGE_RECORD_ID = "aggregatedUsageRecordId";
@RequiredField @ValidOperationResult
public static final String OPERATION_RESULT = "operationResult";
/** resource-specific properties */
protected Map<String, Serializable> resourceProperties;
protected Map<String, List<FieldAction>> validation;
protected Set<String> requiredFields;
protected Set<String> aggregatedFields;
protected Set<String> computedFields;
protected void initializeValidation() {
logger.debug("Initializing Field Validators");
List<Field> fields = Arrays.asList(this.getClass().getDeclaredFields());
for(Field field : fields){
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) {
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);
}
}
}
}
/**
* Initialize variable
*/
private void init() {
this.validation = new HashMap<String, List<FieldAction>>();
this.requiredFields = new HashSet<String>();
initializeValidation();
}
public BasicUsageRecord(){
init();
this.resourceProperties = new HashMap<String, Serializable>();
this.resourceProperties.put(ID, UUID.randomUUID().toString());
this.resourceProperties.put(USAGE_RECORD_TYPE, this.getClass().getSimpleName());
Calendar calendar = Calendar.getInstance();
this.resourceProperties.put(CREATION_TIME, calendar.getTimeInMillis());
}
public BasicUsageRecord(Map<String, Serializable> properties) throws InvalidValueException {
init();
setResourceProperties(properties);
}
/**
* {@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 getCreatorId() {
return (String) this.resourceProperties.get(CREATOR_ID);
}
/**
* {@inheritDoc}
*/
@Override
public void setCreatorId(String creatorId) throws InvalidValueException {
setResourceProperty(CREATOR_ID, creatorId);
}
/**
* {@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());
}
/**
* Return the left end of the time interval covered by this {#UsageRecord}
* @return Start Time
*/
protected Calendar getStartTimeAsCalendar() {
long millis = (Long) this.resourceProperties.get(START_TIME);
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 Calendar getEndTimeAsCalendar() {
long millis = (Long) this.resourceProperties.get(END_TIME);
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 String getUsageRecordType() {
//return (String) this.resourceSpecificProperties.get(RESOURCE_TYPE);
return this.getClass().getSimpleName();
}
*/
/**
* {@inheritDoc}
*/
@Override
public String getResourceScope() {
return (String) this.resourceProperties.get(RESOURCE_SCOPE);
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceScope(String scope) throws InvalidValueException {
setResourceProperty(RESOURCE_SCOPE, scope);
}
/**
* {@inheritDoc}
*/
@Override
public String getAggregatedUsageRecordId() {
return (String) this.resourceProperties.get(AGGREGATED_USAGE_RECORD_ID);
}
/**
* {@inheritDoc}
*/
@Override
public void setAggregatedUsageRecordId(String aggregatedId) throws InvalidValueException {
setResourceProperty(AGGREGATED_USAGE_RECORD_ID, aggregatedId);
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, Serializable> getResourceProperties() {
return new HashMap<String, Serializable>(this.resourceProperties);
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceProperties(Map<String, Serializable> properties) throws InvalidValueException {
validateProperties(properties);
this.resourceProperties = new HashMap<String, Serializable>(properties);
}
/**
* {@inheritDoc}
*/
@Override
public Serializable getResourceProperty(String key) {
return this.resourceProperties.get(key);
}
/**
* {@inheritDoc}
*/
@Override
public void setResourceProperty(String key, Serializable value) throws InvalidValueException {
Serializable checkedValue = validateField(key, value);
if(checkedValue == null){
this.resourceProperties.remove(key);
}else{
this.resourceProperties.put(key, checkedValue);
}
}
protected Serializable validateField(String key, Serializable serializable) throws InvalidValueException {
if(key == null){
throw new InvalidValueException("The key of property to set cannot be null");
}
Serializable checkedValue = serializable;
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 is ignored.");
}
checkedValue = fieldValidator.validate(key, checkedValue, this);
}
}
return checkedValue;
}
protected void validateProperties(Map<String, Serializable> properties) throws InvalidValueException{
for(String key : properties.keySet()){
Serializable serializable = properties.get(key);
validateField(key, serializable);
}
}
/**
* {@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, Serializable>> thisSet = this.resourceProperties.entrySet();
Set<Entry<String, Serializable>> usageRecordSet = usageRecord.getResourceProperties().entrySet();
if(thisSet.size() != usageRecordSet.size()){
return thisSet.size() - usageRecordSet.size();
}
if(usageRecordSet.containsAll(thisSet)){
return 0;
}
return 1;
}
@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.implementations.JobUsageRecord.class;
if(aggregated){
utilityClass = org.gcube.accounting.datamodel.implementations.aggregated.JobUsageRecord.class;
}
String classCanonicalName = utilityClass.getCanonicalName();
classCanonicalName.replace(utilityClass.getSimpleName(), 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, Serializable> usageRecordMap) throws Exception {
String className = (String) usageRecordMap.get(USAGE_RECORD_TYPE);
boolean aggregated = false;
try {
aggregated = (Boolean) usageRecordMap.get(AGGREGATED);
}catch(Exception e){}
Class<? extends UsageRecord> clz = getClass(className, aggregated);
logger.debug("Trying to instantiate {}", clz.getClass().getSimpleName());
@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;
}
}