X-Git-Url: https://dev.e-taxonomy.eu/gitweb/cdmlib.git/blobdiff_plain/62727fb64db75765b4113559a630326f95ed4529..58136b31ffc20c3f283b8d73bd14b09e0a3d3e7f:/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/CdmBase.java diff --git a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/CdmBase.java b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/CdmBase.java index e477040621..0ffa01ec8d 100644 --- a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/CdmBase.java +++ b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/CdmBase.java @@ -13,6 +13,8 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import javax.persistence.Basic; @@ -37,19 +39,24 @@ import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.apache.log4j.Logger; -import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.NaturalId; -import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.Type; +import org.hibernate.envers.Audited; +import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.FieldBridge; +import org.hibernate.search.annotations.Index; +import org.hibernate.search.annotations.Store; import org.joda.time.DateTime; -import eu.etaxonomy.cdm.hibernate.DateTimeBridge; import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper; +import eu.etaxonomy.cdm.hibernate.search.DateTimeBridge; +import eu.etaxonomy.cdm.hibernate.search.NotNullAwareIdBridge; +import eu.etaxonomy.cdm.hibernate.search.UuidBridge; import eu.etaxonomy.cdm.jaxb.DateTimeAdapter; import eu.etaxonomy.cdm.jaxb.UUIDAdapter; +import eu.etaxonomy.cdm.model.NewEntityListener; import eu.etaxonomy.cdm.strategy.match.Match; import eu.etaxonomy.cdm.strategy.match.MatchMode; @@ -75,315 +82,386 @@ import eu.etaxonomy.cdm.strategy.match.MatchMode; "createdBy" }) @MappedSuperclass -public abstract class CdmBase implements Serializable, ICdmBase, Cloneable{ - private static final long serialVersionUID = -3053225700018294809L; - @SuppressWarnings("unused") - private static final Logger logger = Logger.getLogger(CdmBase.class); +public abstract class CdmBase implements Serializable, ICdmBase, ISelfDescriptive, Cloneable{ + private static final long serialVersionUID = -3053225700018294809L; + @SuppressWarnings("unused") + private static final Logger logger = Logger.getLogger(CdmBase.class); + + @Transient + @XmlTransient + private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + @Transient + @XmlTransient + private static NewEntityListener newEntityListener; + + //@XmlAttribute(name = "id", required = true) + @XmlTransient + @Id +// @GeneratedValue(generator = "system-increment") //see also AuditEvent.revisionNumber +// @GeneratedValue(generator = "enhanced-table") + @GeneratedValue(generator = "custom-enhanced-table") + @DocumentId + @FieldBridge(impl=NotNullAwareIdBridge.class) + //commented since Hibernate Search 5.5.x since HSEARCH000247 is thrown otherwise +// @Field(store=Store.YES, termVector=TermVector.NO) + @Match(MatchMode.IGNORE) + @NotNull + @Min(0) + @Audited + private int id; - @Transient - @XmlTransient - private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); - - //@XmlAttribute(name = "id", required = true) - @XmlTransient - @Id -// @GeneratedValue(generator = "system-increment") - @GeneratedValue(generator = "custom-enhanced-table") - @GenericGenerator(name = "custom-enhanced-table", strategy="eu.etaxonomy.cdm.persistence.hibernate.TableGenerator") - @DocumentId - @Match(MatchMode.IGNORE) - @NotNull - @Min(0) - private int id; - - @XmlAttribute(required = true) + @XmlAttribute(required = true) @XmlJavaTypeAdapter(UUIDAdapter.class) + @XmlID @Type(type="uuidUserType") - @NaturalId // This has the effect of placing a "unique" constraint on the database column - @XmlID - @Column(length=36) - @Match(MatchMode.IGNORE) - @NotNull - protected UUID uuid; - - @XmlElement (name = "Created", type= String.class) - @XmlJavaTypeAdapter(DateTimeAdapter.class) - @Type(type="dateTimeUserType") - @Basic(fetch = FetchType.LAZY) - @Match(MatchMode.IGNORE) - @Field(index = org.hibernate.search.annotations.Index.UN_TOKENIZED) - @FieldBridge(impl = DateTimeBridge.class) - private DateTime created; - - @XmlElement (name = "CreatedBy") - @XmlIDREF - @XmlSchemaType(name = "IDREF") - @ManyToOne(fetch=FetchType.LAZY) + @NaturalId // This has the effect of placing a "unique" constraint on the database column + @Column(length=36) //TODO needed? Type UUID will always assure that is exactly 36 @Match(MatchMode.IGNORE) - private User createdBy; - - /** - * Class constructor assigning a unique UUID and creation date. - * UUID can be changed later via setUuid method. - */ - public CdmBase() { - this.uuid = UUID.randomUUID(); - this.created = new DateTime().withMillisOfSecond(0); - } - - /** - * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)} - * @param listener - */ - public void addPropertyChangeListener(PropertyChangeListener listener) { - propertyChangeSupport.addPropertyChangeListener(listener); - } - - /** - * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)} - */ - public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { - propertyChangeSupport.addPropertyChangeListener(propertyName, listener); - } - - /** - * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)} - */ - public void removePropertyChangeListener(PropertyChangeListener listener) { - propertyChangeSupport.removePropertyChangeListener(listener); - } - - /** - * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener) - */ - public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { - propertyChangeSupport.removePropertyChangeListener(propertyName, listener); - } - - public boolean hasListeners(String propertyName) { - return propertyChangeSupport.hasListeners(propertyName); - } - - public void firePropertyChange(String property, String oldval, String newval) { - propertyChangeSupport.firePropertyChange(property, oldval, newval); - } - public void firePropertyChange(String property, int oldval, int newval) { - propertyChangeSupport.firePropertyChange(property, oldval, newval); - } - public void firePropertyChange(String property, float oldval, float newval) { - propertyChangeSupport.firePropertyChange(property, oldval, newval); - } - public void firePropertyChange(String property, boolean oldval, boolean newval) { - propertyChangeSupport.firePropertyChange(property, oldval, newval); - } - public void firePropertyChange(String property, Object oldval, Object newval) { - propertyChangeSupport.firePropertyChange(property, oldval, newval); - } - public void firePropertyChange(PropertyChangeEvent evt) { - propertyChangeSupport.firePropertyChange(evt); - } - - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#getUuid() - */ - public UUID getUuid() { - return uuid; - } - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#setUuid(java.util.UUID) - */ - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#getId() - */ - public int getId() { - return this.id; - } - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#setId(int) - */ - public void setId(int id) { - this.id = id; - } - - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#getCreated() - */ - public DateTime getCreated() { - return created; - } - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#setCreated(java.util.Calendar) - */ - public void setCreated(DateTime created) { - if (created != null){ - new DateTime(); - created = created.withMillisOfSecond(0); - //created.set(Calendar.MILLISECOND, 0); //old, can be deleted - } - this.created = created; - } - - - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#getCreatedBy() - */ - public User getCreatedBy() { - return this.createdBy; - } - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.model.common.ICdmBase#setCreatedBy(eu.etaxonomy.cdm.model.agent.Person) - */ - public void setCreatedBy(User createdBy) { - this.createdBy = createdBy; - } + @NotNull + @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO) + @FieldBridge(impl = UuidBridge.class) + @Audited + protected UUID uuid; + + @XmlElement (name = "Created", type= String.class) + @XmlJavaTypeAdapter(DateTimeAdapter.class) + @Type(type="dateTimeUserType") + @Basic(fetch = FetchType.LAZY) + @Match(MatchMode.IGNORE) + @Field(analyze = Analyze.NO) + @FieldBridge(impl = DateTimeBridge.class) + @Audited + private DateTime created; + + @XmlElement (name = "CreatedBy") + @XmlIDREF + @XmlSchemaType(name = "IDREF") + @ManyToOne(fetch=FetchType.LAZY) + @Match(MatchMode.IGNORE) + @Audited + private User createdBy; + + /** + * Class constructor assigning a unique UUID and creation date. + * UUID can be changed later via setUuid method. + */ + public CdmBase() { + this.uuid = UUID.randomUUID(); + this.created = new DateTime().withMillisOfSecond(0); + } + + public static void setNewEntityListener(NewEntityListener nel) { + newEntityListener = nel; + } + + public static void fireOnCreateEvent(CdmBase cdmBase) { + if(newEntityListener != null) { + newEntityListener.onCreate(cdmBase); + } + } + + /** + * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)} + * @param listener + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + /** + * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)} + */ + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(propertyName, listener); + } + + /** + * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)} + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + /** + * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener) + */ + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(propertyName, listener); + } + + public boolean hasListeners(String propertyName) { + return propertyChangeSupport.hasListeners(propertyName); + } + + public void firePropertyChange(String property, String oldval, String newval) { + propertyChangeSupport.firePropertyChange(property, oldval, newval); + } + public void firePropertyChange(String property, int oldval, int newval) { + propertyChangeSupport.firePropertyChange(property, oldval, newval); + } + public void firePropertyChange(String property, float oldval, float newval) { + propertyChangeSupport.firePropertyChange(property, oldval, newval); + } + public void firePropertyChange(String property, boolean oldval, boolean newval) { + propertyChangeSupport.firePropertyChange(property, oldval, newval); + } + public void firePropertyChange(String property, Object oldval, Object newval) { + propertyChangeSupport.firePropertyChange(property, oldval, newval); + } + public void firePropertyChange(PropertyChangeEvent evt) { + propertyChangeSupport.firePropertyChange(evt); + } + + /** + * This method was initially added to {@link CdmBase} to fix #5161. + * It can be overridden by subclasses such as {@link IdentifiableEntity} + * to explicitly initialize listeners. This is needed e.g. after de-serialization + * as listeners are not serialized due to the @Transient annotation. + * However, it can be generally used for other use-cases as well + */ + public void initListener() {} + + /** + * Adds an item to a set of this object and fires the according + * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet + * implemented in aspectJ. + * @param set the set the new item is added to + * @param newItem the new item to be added to the set + * @param propertyName the name of the set as property in this object + */ + protected void addToSetWithChangeEvent(Set set, T newItem, String propertyName ){ + Set oldValue = new HashSet(set); + set.add(newItem); + firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set)); + } + + /** + * Removes an item from a set of this object and fires the according + * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet + * implemented in aspectJ. + * @param set the set the item is to be removed from + * @param itemToRemove the item to be removed from the set + * @param propertyName the name of the set as property in this object + */ + protected void removeFromSetWithChangeEvent(Set set, T itemToRemove, String propertyName ){ + Set oldValue = new HashSet(set); + set.remove(itemToRemove); + firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set)); + } + + @Override + public UUID getUuid() { + return uuid; + } + @Override + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + @Override + public int getId() { + return this.id; + } + @Override + public void setId(int id) { //see #265 (private ?) + this.id = id; + } + + @Override + public DateTime getCreated() { + return created; + } + @Override + public void setCreated(DateTime created) { + if (created != null){ + created = created.withMillisOfSecond(0); + //created.set(Calendar.MILLISECOND, 0); //old, can be deleted + } + this.created = created; + } + + + @Override + public User getCreatedBy() { + return this.createdBy; + } + @Override + public void setCreatedBy(User createdBy) { + this.createdBy = createdBy; + } // ************************** Hibernate proxies *******************/ - /** - * These methods are present due to HHH-1517 - that in a one-to-many - * relationship with a superclass at the "one" end, the proxy created - * by hibernate is the superclass, and not the subclass, resulting in - * a classcastexception when you try to cast it. - * - * Hopefully this will be resolved through improvements with the creation of - * proxy objects by hibernate and the following methods will become redundant, - * but for the time being . . . - * @param - * @param object - * @param clazz - * @return - * @throws ClassCastException - */ - //non-static does not work because javassist already unwrapps the proxy before calling the method - public static T deproxy(Object object, Class clazz) throws ClassCastException { - return HibernateProxyHelper.deproxy(object, clazz); - } - - public boolean isInstanceOf(Class clazz) throws ClassCastException { - return HibernateProxyHelper.isInstanceOf(this, clazz); - } + /** + * These methods are present due to HHH-1517 - that in a one-to-many + * relationship with a superclass at the "one" end, the proxy created + * by hibernate is the superclass, and not the subclass, resulting in + * a classcastexception when you try to cast it. + * + * Hopefully this will be resolved through improvements with the creation of + * proxy objects by hibernate and the following methods will become redundant, + * but for the time being . . . + * @param + * @param object + * @param clazz + * @return + * @throws ClassCastException + */ + //non-static does not work because javassist already unwrapps the proxy before calling the method + public static T deproxy(Object object, Class clazz) throws ClassCastException { + return HibernateProxyHelper.deproxy(object, clazz); + } + + public boolean isInstanceOf(Class clazz) throws ClassCastException { + return HibernateProxyHelper.isInstanceOf(this, clazz); + } // ************* Object overrides *************************/ - /** - * Is true if UUID is the same for the passed Object and this one. - * @see java.lang.Object#equals(java.lang.Object) - * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities} - * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm} - * for more information about equals and hashcode. - */ - @Override - public boolean equals(Object obj) { - if (obj == this){ - return true; - } - if (obj == null){ - return false; - } - if (!CdmBase.class.isAssignableFrom(obj.getClass())){ - return false; - } - ICdmBase cdmObj = (ICdmBase)obj; - boolean uuidEqual = cdmObj.getUuid().equals(this.getUuid()); - boolean createdEqual = cdmObj.getCreated().equals(this.getCreated()); - if (! uuidEqual || !createdEqual){ - return false; - } - return true; - } + /** + * Is true if UUID is the same for the passed Object and this one. + * @see java.lang.Object#equals(java.lang.Object) + * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities} + * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm} + * for more information about equals and hashcode. + */ + @Override + public boolean equals(Object obj) { + if (obj == this){ + return true; + } + if (obj == null){ + return false; + } + if (!CdmBase.class.isAssignableFrom(obj.getClass())){ + return false; + } + ICdmBase cdmObj = (ICdmBase)obj; + boolean uuidEqual = cdmObj.getUuid().equals(this.getUuid()); + boolean createdEqual = cdmObj.getCreated().equals(this.getCreated()); + if (! uuidEqual || !createdEqual){ + return false; + } + return true; + } + + + /** Overrides {@link java.lang.Object#hashCode()} + * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities} + * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm} + * for more information about equals and hashcode. + */ + @Override + public int hashCode() { + int hashCode = 7; + if(this.getUuid() != null) { + //this unfortunately leads to errors when loading maps via hibernate + //as hibernate computes hash values for CdmBase objects used as key at + // a time when the uuid is not yet loaded from the database. Therefore + //the hash values later change and give wrong results when retrieving + //data from the map (map.get(key) returns null, though there is an entry + //for key in the map. + //see further comments in #2114 + int result = 29 * hashCode + this.getUuid().hashCode(); +// int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode(); + return result; + } else { + return 29 * hashCode; + } + } + + /** + * Overrides {@link java.lang.Object#toString()}. + * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the + * call to {link {@link #instanceToString()}.
+ * Specification: This method should never call other object' methods so it can be well used for debugging + * without problems like lazy loading, unreal states etc. + *

+ * Note: If overriding this method's javadoc always copy or link the above requirement. + * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object. + *

+ * For example: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0> + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return instanceToString(); + } + + /** + * This returns an String that identifies the cdm instacne well without being necessarily unique. + * The string representation combines the class name the {@link #id} and {@link #uuid}. + *

+ * For example: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0> + * @return + */ + public String instanceToString() { + return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">"; + } +// **************** invoke methods **************************/ - /** Overrides {@link java.lang.Object#hashCode()} - * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities} - * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm} - * for more information about equals and hashcode. - */ + protected void invokeSetMethod(Method method, Object object){ + try { + method.invoke(object, this); + } catch (Exception e) { + e.printStackTrace(); + //TODO handle exceptioin; + } + } + + protected void invokeSetMethodWithNull(Method method, Object object){ + try { + Object[] nul = new Object[]{null}; + method.invoke(object, nul); + } catch (Exception e) { + e.printStackTrace(); + //TODO handle exceptioin; + } + } + + @Transient @Override - public int hashCode() { - int hashCode = 7; - if(this.getUuid() != null) { - //this unfortunately leads to errors when loading maps via hibernate - //as hibernate computes hash values for CdmBase objects used as key at - // a time when the uuid is not yet loaded from the database. Therefore - //the hash values later change and give wrong results when retrieving - //data from the map (map.get(key) returns null, though there is an entry - //for key in the map. - //see further comments in #2114 - int result = 29 * hashCode + this.getUuid().hashCode(); -// int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode(); - return result; - } else { - return 29 * hashCode; - } + public String getUserFriendlyTypeName(){ + return getClass().getSimpleName(); } - /** - * Overrides {@link java.lang.Object#toString()}. - * This returns an String that identifies the object well without beeing necessarily unique. - * Specification: This method should never call other object' methods so it can be well used for debugging - * without problems like lazy loading, unreal states etc. - * Note: If overriding this method's javadoc always copy or link the above requirement. - * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object. - * For example: Taxon#13 - * @see java.lang.Object#toString() - */ + @Transient @Override - public String toString() { - return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">"; - } - -// **************** invoke methods **************************/ - - protected void invokeSetMethod(Method method, Object object){ - try { - method.invoke(object, this); - } catch (Exception e) { - e.printStackTrace(); - //TODO handle exceptioin; - } + public String getUserFriendlyDescription(){ + return toString(); } - protected void invokeSetMethodWithNull(Method method, Object object){ - try { - Object[] nul = new Object[]{null}; - method.invoke(object, nul); - } catch (Exception e) { - e.printStackTrace(); - //TODO handle exceptioin; - } + @Override + public String getUserFriendlyFieldName(String field){ + return field; } //********************** CLONE *****************************************/ - protected void clone(CdmBase clone){ - clone.setCreatedBy(createdBy); - clone.setId(id); - clone.propertyChangeSupport=new PropertyChangeSupport(clone); - //Constructor Attributes - //clone.setCreated(created); - //clone.setUuid(getUuid()); - - } - - /* (non-Javadoc) - * @see java.lang.Object#clone() - */ - @Override - public Object clone() throws CloneNotSupportedException{ - CdmBase result = (CdmBase)super.clone(); - result.propertyChangeSupport=new PropertyChangeSupport(result); - - //TODO ? - result.setId(0); - result.setUuid(UUID.randomUUID()); - result.setCreated(new DateTime()); - result.setCreatedBy(null); - - //no changes to: - - return result; - } +// protected void clone(CdmBase clone){ +// clone.setCreatedBy(createdBy); +// clone.setId(id); +// clone.propertyChangeSupport=new PropertyChangeSupport(clone); +// //Constructor Attributes +// //clone.setCreated(created); +// //clone.setUuid(getUuid()); +// +// } + + @Override + public Object clone() throws CloneNotSupportedException{ + CdmBase result = (CdmBase)super.clone(); + result.propertyChangeSupport=new PropertyChangeSupport(result); + + //TODO ? + result.setId(0); + result.setUuid(UUID.randomUUID()); + result.setCreated(new DateTime()); + result.setCreatedBy(null); + + //no changes to: - + return result; + } }