/** * */ 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.Date; 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.RequiredField; import org.gcube.accounting.datamodel.decorators.ValidatorAction; import org.gcube.accounting.datamodel.decorators.ValidatorField; import org.gcube.accounting.datamodel.deprecationmanagement.annotations.DeprecatedWarning; import org.gcube.accounting.datamodel.deprecationmanagement.annotations.MoveToAggregatedUsageRecordId; 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 RawUsageRecord implements UsageRecord, Serializable { private static Logger logger = LoggerFactory.getLogger(RawUsageRecord.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 RESOURCE_TYPE = "resourceType"; protected static final String USAGE_RECORD_TYPE = "resourceType"; @RequiredField @NotEmpty public static final String RESOURCE_SCOPE = "resourceScope"; @DeprecatedWarning @NotEmpty protected static final String RESOURCE_OWNER = "resourceOwner"; @AggregatedField @NotEmptyIfNotNull protected static final String AGGREGATED = "aggregated"; @AggregatedField @NotEmptyIfNotNull protected static final String AGGREGATED_USAGE_RECORD_ID = "aggregatedUsageRecordId"; @NotEmptyIfNotNull @MoveToAggregatedUsageRecordId protected static final String AGGREGATED_ID = "aggregatedId"; @RequiredField @ValidOperationResult public static final String OPERATION_RESULT = "operationResult"; /** * Generated Serial Version UID */ private static final long serialVersionUID = -2060728578456796388L; /** resource-specific properties */ protected Map resourceProperties; protected Map> validation; protected Set requiredFields; protected void initializeValidation() { logger.debug("Initializing Field Validators"); List fields = Arrays.asList(this.getClass().getDeclaredFields()); for(Field field : fields){ String keyString; try { keyString = (String) field.get(null); } catch (Exception e) { continue; } List fieldValidators = new ArrayList(); validation.put(keyString, fieldValidators); for (Annotation annotation : field.getAnnotations()){ if (annotation.annotationType().isAnnotationPresent(ValidatorField.class)){ Class managedClass = ((ValidatorField)annotation.annotationType().getAnnotation(ValidatorField.class)).managed(); ValidatorAction validator; try { validator = managedClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { continue; } fieldValidators.add(validator); } if(annotation.getClass().isAssignableFrom(RequiredField.class)){ requiredFields.add(keyString); } } } } /** * Initialize variable */ private void init() { this.validation = new HashMap>(); this.requiredFields = new HashSet(); initializeValidation(); } public RawUsageRecord(){ init(); this.resourceProperties = new HashMap(); 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 RawUsageRecord(Map 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()); } @Override @Deprecated public Date getCreateTime() { long millis = (Long) this.resourceProperties.get(CREATION_TIME); return timestampStringToCalendar(millis).getTime(); } /** * {@inheritDoc} */ @Override @Deprecated public void setCreateTime(Date createTime) throws InvalidValueException { Calendar calendar = Calendar.getInstance(); calendar.setTime(createTime); setCreationTime(calendar); } /** * {@inheritDoc} */ @Override public Calendar getStartTime() { 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()); } /* * * {@inheritDoc} * / @Override @Deprecated public void setStartTime(Date startTime) throws InvalidValueException { Calendar calendar = Calendar.getInstance(); calendar.setTime(startTime); setStartTime(calendar); } */ /** * {@inheritDoc} */ @Override public Calendar getEndTime() { 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()); } /* * * {@inheritDoc} * / @Override @Deprecated public void setEndTime(Date endTime) throws InvalidValueException { Calendar calendar = Calendar.getInstance(); calendar.setTime(endTime); setEndTime(calendar); } */ protected String getUsageRecordType() { //return (String) this.resourceSpecificProperties.get(RESOURCE_TYPE); return this.getClass().getSimpleName(); } /** * {@inheritDoc} */ @Override @Deprecated public String getResourceType(){ return getUsageRecordType(); } /** * {@inheritDoc} */ @Deprecated public void setResourceType(String resourceType){} /** * {@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 @Deprecated public String getResourceOwner() { return (String) this.resourceProperties.get(RESOURCE_OWNER); } /** * {@inheritDoc} */ @Override @Deprecated public void setResourceOwner(String owner) throws InvalidValueException { setResourceProperty(RESOURCE_OWNER, owner); } /** * {@inheritDoc} */ @Override @Deprecated public String getAggregatedId() { return (String) this.resourceProperties.get(AGGREGATED_ID); } /** * {@inheritDoc} */ @Override@ Deprecated public void setAggregatedId(String aggregatedId) throws InvalidValueException { setResourceProperty(AGGREGATED_ID, aggregatedId); } /** * {@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 @Deprecated public Map getResourceSpecificProperties() { return getResourceProperties(); } /** * {@inheritDoc} */ @Override @Deprecated public void setResourceSpecificProperties(Map properties) throws InvalidValueException { setResourceProperties(properties); } /** * {@inheritDoc} */ @Override public Map getResourceProperties() { return this.resourceProperties; } /** * {@inheritDoc} */ @Override public void setResourceProperties(Map properties) throws InvalidValueException { validateProperties(properties); this.resourceProperties = new HashMap(properties); } /** * {@inheritDoc} */ @Override @Deprecated public Serializable getResourceSpecificProperty(String key) { return getResourceProperty(key); } /** * {@inheritDoc} */ @Override @Deprecated public void setResourceSpecificProperty(String key, Serializable value) throws InvalidValueException { setResourceProperty(key, value); } /** * {@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 fieldValidators = validation.get(key); if(fieldValidators!=null){ for(ValidatorAction fieldValidator : fieldValidators){ checkedValue = fieldValidator.validate(key, checkedValue, this); } } return checkedValue; } protected void validateProperties(Map 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 notPresentProperties = new HashSet(); 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 @Deprecated public String getFullyQualifiedConsumerId() { return getConsumerId(); } /** * {@inheritDoc} */ @Override @Deprecated public void setFullyQualifiedConsumerId(String fqcid) { } /** * {@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> thisSet = this.resourceProperties.entrySet(); Set> 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 getClass(String usageRecordName, boolean aggregated) throws ClassNotFoundException { Class clz = null; Class 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) 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 usageRecordMap) throws Exception { String className = (String) usageRecordMap.get(USAGE_RECORD_TYPE); boolean aggregated = (Boolean) usageRecordMap.get(AGGREGATED); Class clz = getClass(className, aggregated); logger.debug("Trying to instantiate {}", clz.getClass().getSimpleName()); @SuppressWarnings("rawtypes") Class[] usageRecordArgTypes = { Map.class }; Constructor usageRecordConstructor = clz.getDeclaredConstructor(usageRecordArgTypes); Object[] usageRecordArguments = {usageRecordMap}; return usageRecordConstructor.newInstance(usageRecordArguments); } }