package sushi.event.attribute;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import com.fasterxml.jackson.annotation.JsonIgnore;
import sushi.correlation.CorrelationRule;
import sushi.event.SushiEventType;
import sushi.persistence.Persistable;
import sushi.persistence.Persistor;
/**
* Representation of an Attribute/Datatype element of the SushiAttributTree
*/
@Entity
@Table(
name = "SushiAttribute"
)
public class SushiAttribute extends Persistable {
private static final long serialVersionUID = -3804228219409837851L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected int ID;
@ManyToOne
@JoinColumn(name="AttributeTreeID")
private SushiAttributeTree attributeTree;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private SushiAttribute parent;
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<SushiAttribute> children = new ArrayList<SushiAttribute>();
@OneToMany(mappedBy = "firstAttribute")
private Set<CorrelationRule> correlationRulesFirst = new HashSet<CorrelationRule>();
@OneToMany(mappedBy = "secondAttribute")
private Set<CorrelationRule> correlationRulesSecond = new HashSet<CorrelationRule>();
@Column(name = "Name")
protected String name;
@Column(name = "AttributeType")
@Enumerated(EnumType.STRING)
private SushiAttributeTypeEnum type = SushiAttributeTypeEnum.STRING;
/**
* for temporary use in TransformationRuleEditor only
* not persisted by JPA
*/
@Transient
private boolean isTimestamp;
public SushiAttribute() {
this.ID = 0;
this.isTimestamp = false;
}
/**
* creates a root attribute without data type
* (use this constructor if you add child attributes to the root attribute)
*
* @param name allowed characters: a-z, A-Z, 0-9, _; whitespace(s) converted to underscore
*/
public SushiAttribute(String name) throws RuntimeException {
this();
if (name.equals("ProcessInstance")) {
throw new RuntimeException("The attribute name 'ProcessInstance' is reserved. Please choose another one.");
}
this.name = name.trim().replaceAll(" +","_").replaceAll("[^a-zA-Z0-9_]+","");
}
/**
* creates a root attribute with data type
*
* @param name
* @param type
*/
public SushiAttribute(String name, SushiAttributeTypeEnum type) throws RuntimeException {
this(name);
this.type = type;
}
/**
* creates a child node without data type and adds it to the given parent
* (use this constructor if you add child attributes to the root attribute)
*
* @param parent
* @param name
*/
public SushiAttribute(SushiAttribute parent, String name) throws RuntimeException {
this.parent = parent;
this.name = name;
parent.setType(null);
this.parent.addChild(this);
}
/**
* creates a child node with data type and adds it to the given parent
*
* @param parent
* @param name
* @param type
*/
public SushiAttribute(SushiAttribute parent, String name, SushiAttributeTypeEnum type) throws RuntimeException {
this(parent, name);
this.type = type;
}
public int getID() {
return ID;
}
public void setID(int iD) {
ID = iD;
}
/**
* Use getAttributeExpression() instead if you want to use it
* as a key for the SushiMapTree (= values) for the SushiEvent.
*
* @return the name of the attribute
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean hasParent() {
return parent != null;
}
public SushiAttribute getParent() {
return parent;
}
public void setParent(SushiAttribute parent) {
this.parent = parent;
this.parent.setType(null);
this.parent.addChild(this);
}
public boolean isRootElement() {
return parent == null;
}
public SushiAttributeTypeEnum getType() {
return type;
}
public void setType(SushiAttributeTypeEnum attributeType) {
this.type = attributeType;
}
public boolean isTimestamp() {
return isTimestamp;
}
public void setTimestamp(boolean isTimestamp) {
this.isTimestamp = isTimestamp;
}
public ArrayList<SushiAttribute> getChildren() {
return new ArrayList<SushiAttribute>(children);
}
public boolean hasChildren() {
return !children.isEmpty();
}
/**
* adds child node to attribute element
*/
public boolean addChild(SushiAttribute attribute) {
if (!attribute.hasParent()) {
attribute.setParent(attribute);
}
return children.add(attribute);
}
public void removeAttribute() {
children.clear();
if (hasParent()) {
parent.removeChild(this);
if (!parent.hasChildren()) {
// because leaf attributes without types are not allowed
parent.setType(SushiAttributeTypeEnum.STRING);
}
}
}
public void removeChild(SushiAttribute attribute) {
children.remove(attribute);
}
public void removeAllChildren() {
children.clear();
}
/**
* Generates an identifier composed of [level of this attribute in tree]-[name of parent attribute]-[name of this attribute] and returns it.
* Does not return the ID under which the attribute is stored in the database!
*
* @return generated identifier as String
*/
public String getIdentifier() {
StringBuilder sb = new StringBuilder();
sb.append(getLevelInTree(this, 0));
if (parent != null) {
sb.append("-" + parent.getName().trim());
}
sb.append(name.trim());
return sb.toString();
}
/**
* @return level of this attribute in tree as an intenger
*/
private int getLevelInTree(SushiAttribute attribute, int rootLevel) {
if (!attribute.hasParent()) {
return rootLevel;
}
return getLevelInTree(attribute.getParent(), rootLevel + 1);
}
/**
*
* @return returns recursive the path to this element as XPath
*/
public String getXPath() {
if (parent == null) {
return "/" + name.toString().replaceAll(" ", "");
}
return parent.getXPath() + "/" + name.toString();
}
public String getAttributeExpression() {
if (isRootElement()) {
return name;
}
return parent.getAttributeExpression() + "." + name;
}
private SushiAttribute getRootLevelParent() {
if (parent == null) {
return this;
}
return parent.getRootLevelParent();
}
@JsonIgnore
public SushiEventType getEventType() {
SushiAttribute rootLevelParent = getRootLevelParent();
Query query = Persistor.getEntityManager().createNativeQuery("" +
"SELECT * FROM EventType " +
"WHERE Attributes = '" + rootLevelParent.getAttributeTree().getID() + "'", SushiEventType.class);
return (SushiEventType) query.getSingleResult();
}
/**
* attribute identifier consisting of event type name plus attribute expression
* example: Order.orderId for first level attribute with name "orderId" of event type "Order"
*
* @return qualified attribute name
*/
public String getQualifiedAttributeName() {
return getEventType().getTypeName() + "." + getAttributeExpression();
}
@JsonIgnore
public SushiAttributeTree getAttributeTree() {
return attributeTree;
}
public void setAttributeTree(SushiAttributeTree attributeTree) {
this.attributeTree = attributeTree;
}
public boolean addToCorrelationRulesFirst(CorrelationRule rule) {
return correlationRulesFirst.add(rule);
}
public boolean addToCorrelationRulesSecond(CorrelationRule rule) {
return correlationRulesSecond.add(rule);
}
/**
* attributes are equal if their parent attributes and attribute names are equal
* NOTE: use equalsWithEventType(SushiAttribute element) for including the event type
*/
@Override
public boolean equals(Object element) {
if (element instanceof SushiAttribute) {
SushiAttribute attribute = (SushiAttribute) element;
/*
* attributes are equal irrespective of their types
* because it would be else possible to add attributes
* with the same name to a parent attribute
*/
return getAttributeExpression().equals(attribute.getAttributeExpression());
} else {
return false;
}
}
/**
* equality by parent attributes and attribute names
*/
public boolean equalsWithEventType(SushiAttribute attribute) {
boolean bool1 = false, bool2 = false;
if (getRootLevelParent().getAttributeTree() == null) {
bool1 = (attribute.getAttributeTree() == null) ? true : false;
} else {
bool1 = getRootLevelParent().getAttributeTree().equals(attribute.getAttributeTree());
}
bool2 = getAttributeExpression().equals(attribute.getAttributeExpression());
return bool1 && bool2;
}
/**
* attributes have the same hashcode if their types and names are equal
* and if they belong to the same attribute tree (-> event type)
*/
@Override
public int hashCode() {
int hashCode = 0;
if (type != null) hashCode += type.hashCode();
if (getXPath() != null) hashCode += getXPath().hashCode();
if (attributeTree != null) hashCode += attributeTree.hashCode();
return hashCode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(name);
if (isTimestamp()) {
sb.append(" : Timestamp");
} else if (type != null) {
sb.append(" : " + type);
}
return sb.toString();
}
}