package uk.ac.ebi.fg.myequivalents.provenance.model; import static uk.ac.ebi.fg.myequivalents.resources.Const.COL_LENGTH_M; import static uk.ac.ebi.fg.myequivalents.resources.Const.COL_LENGTH_S; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OrderColumn; import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.apache.commons.lang3.ArrayUtils; import org.hibernate.annotations.Index; import org.hibernate.validator.constraints.NotEmpty; import uk.ac.ebi.fg.myequivalents.utils.jaxb.DateJaxbXmlAdapter; import uk.ac.ebi.utils.security.IdUtils; /** * The provenance register keeps track of which user issued which change operation. * * <dl><dt>date</dt><dd>31 Mar 2014</dd></dl> * @author Marco Brandizi * * TODO: The log is written in a separate transaction, after the operation is successful. We should put all inside the * same transaction. * * TODO: Several operations are recorded (by the managers), independently on the fact it physically really occurred or not. * For instance, if you delete a non-existing service, the deletion operation is recorded anyway. We might want to change * this in future. * */ @Entity @Table ( name = "provenance_register" ) @XmlRootElement ( name = "provenance-entry" ) @XmlAccessorType ( XmlAccessType.NONE ) public class ProvenanceRegisterEntry { private String id; private String userEmail; private Date timestamp; private String operation; private List<ProvenanceRegisterParameter> parameters; protected ProvenanceRegisterEntry () { } public ProvenanceRegisterEntry ( String userEmail, Date timestamp, String operation, List<ProvenanceRegisterParameter> parameters ) { this.userEmail = userEmail; this.timestamp = timestamp; this.operation = operation; this.parameters = parameters; } public ProvenanceRegisterEntry ( String userEmail, String operation, List<ProvenanceRegisterParameter> parameters ) { this ( userEmail, new Date (), operation, parameters ); } public ProvenanceRegisterEntry ( String userEmail, Date timestamp, String operation ) { this ( userEmail, timestamp, operation, null ); } public ProvenanceRegisterEntry ( String userEmail, String operation ) { this ( userEmail, operation, null ); } /** * A universal ID for job registry entries. * This is automatically created using {@link IdUtils#createCompactUUID()}. We do not use numerical auto-IDs, cause * we want to ensure cross-DB identifiers, even before an entry is persisted. */ @Id @Column ( length = 22 ) public String getId () { if ( id == null ) this.setId ( IdUtils.createCompactUUID () ); return id; } /** * You should never explicitly set this, Hibernate will handle the creation of this ID whenever a new object is saved. * */ protected void setId ( String id ) { this.id = id; } @NotEmpty @Index ( name = "prov_email" ) @Column ( name = "user_email", length = COL_LENGTH_S ) @XmlAttribute ( name = "user-email" ) public String getUserEmail () { return userEmail; } protected void setUserEmail ( String userEmail ) { this.userEmail = userEmail; } @NotNull @Index ( name = "prov_ts" ) @XmlAttribute ( name = "timestamp" ) @XmlJavaTypeAdapter ( DateJaxbXmlAdapter.class ) public Date getTimestamp () { return timestamp; } protected void setTimestamp ( Date timestamp ) { this.timestamp = timestamp; } @ElementCollection @CollectionTable( name = "provenance_register_parameter", joinColumns = @JoinColumn ( name="prov_entry_id" ) ) @OrderColumn ( name = "idx" ) @XmlElementWrapper( name = "parameters" ) @XmlElement ( name = "parameter" ) public List<ProvenanceRegisterParameter> getParameters () { return parameters; } public void setParameters ( List<ProvenanceRegisterParameter> parameters ) { this.parameters = parameters; } @NotEmpty @Index ( name = "prov_op" ) @Column ( name = "operation", length = COL_LENGTH_M ) @XmlAttribute ( name = "operation" ) public String getOperation () { return operation; } protected void setOperation ( String operation ) { this.operation = operation; } @Override public boolean equals ( Object o ) { if ( o == null ) return false; if ( this == o ) return true; if ( this.getClass () != o.getClass () ) return false; // The entity type ProvenanceRegisterEntry that = (ProvenanceRegisterEntry) o; return this.getId ().equals ( that.getId () ); } @Override public int hashCode () { return this.getId ().hashCode (); } @Override public String toString() { return String.format ( "%s { id: %s, userEmail: '%s', timestamp: %s, operation: %s, parameters: '%s'", this.getClass ().getSimpleName (), this.getId (), this.getUserEmail (), this.getTimestamp (), this.getOperation (), this.getParameters () == null ? "null" : ArrayUtils.toString ( this.parameters ) ); } public Set<ProvenanceRegisterParameter> containsParameters ( List<ProvenanceRegisterParameter> params ) { if ( params == null || params.isEmpty () ) return Collections.emptySet (); Set<ProvenanceRegisterParameter> result = new HashSet<> (); for ( ProvenanceRegisterParameter psearch: params ) { if ( result.contains ( psearch ) ) continue; String ptype = psearch.getValueType (), val = psearch.getValue (), xval = psearch.getExtraValue (); for ( ProvenanceRegisterParameter param: this.getParameters () ) { if ( ptype != null && !ptype.equals ( param.getValueType () ) ) continue; if ( val != null && !val.equals ( param.getValue () ) ) continue; if ( xval != null && !xval.equals ( param.getExtraValue () ) ) continue; result.add ( param ); } } return result; } }