2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.model
.common
;
11 import java
.beans
.PropertyChangeEvent
;
12 import java
.beans
.PropertyChangeListener
;
13 import java
.beans
.PropertyChangeSupport
;
14 import java
.io
.Serializable
;
15 import java
.lang
.reflect
.Method
;
16 import java
.util
.HashSet
;
17 import java
.util
.List
;
19 import java
.util
.UUID
;
21 import javax
.persistence
.Basic
;
22 import javax
.persistence
.Column
;
23 import javax
.persistence
.FetchType
;
24 import javax
.persistence
.GeneratedValue
;
25 import javax
.persistence
.Id
;
26 import javax
.persistence
.ManyToOne
;
27 import javax
.persistence
.MappedSuperclass
;
28 import javax
.persistence
.Transient
;
29 import javax
.validation
.constraints
.Min
;
30 import javax
.validation
.constraints
.NotNull
;
31 import javax
.xml
.bind
.annotation
.XmlAccessType
;
32 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
33 import javax
.xml
.bind
.annotation
.XmlAttribute
;
34 import javax
.xml
.bind
.annotation
.XmlElement
;
35 import javax
.xml
.bind
.annotation
.XmlID
;
36 import javax
.xml
.bind
.annotation
.XmlIDREF
;
37 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
38 import javax
.xml
.bind
.annotation
.XmlTransient
;
39 import javax
.xml
.bind
.annotation
.XmlType
;
40 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
42 import org
.apache
.log4j
.Logger
;
43 import org
.hibernate
.annotations
.NaturalId
;
44 import org
.hibernate
.annotations
.Type
;
45 import org
.hibernate
.envers
.Audited
;
46 import org
.hibernate
.search
.annotations
.Analyze
;
47 import org
.hibernate
.search
.annotations
.DocumentId
;
48 import org
.hibernate
.search
.annotations
.Field
;
49 import org
.hibernate
.search
.annotations
.FieldBridge
;
50 import org
.hibernate
.search
.annotations
.Index
;
51 import org
.hibernate
.search
.annotations
.Store
;
52 import org
.joda
.time
.DateTime
;
54 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
55 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
56 import eu
.etaxonomy
.cdm
.hibernate
.search
.DateTimeBridge
;
57 import eu
.etaxonomy
.cdm
.hibernate
.search
.NotNullAwareIdBridge
;
58 import eu
.etaxonomy
.cdm
.hibernate
.search
.UuidBridge
;
59 import eu
.etaxonomy
.cdm
.jaxb
.DateTimeAdapter
;
60 import eu
.etaxonomy
.cdm
.jaxb
.UUIDAdapter
;
61 import eu
.etaxonomy
.cdm
.model
.NewEntityListener
;
62 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchStrategy
;
63 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchable
;
64 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
65 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
71 * The base class for all CDM domain classes implementing UUIDs and bean property change event firing.
72 * It provides a globally unique UUID and keeps track of creation date and person.
73 * The UUID is the same for different versions (see {@link VersionableEntity}) of a CDM object, so a locally unique id exists in addition
74 * that allows to safely access and store several objects (=version) with the same UUID.
76 * This class together with the {@link eu.etaxonomy.cdm.aspectj.PropertyChangeAspect}
77 * will fire bean change events to all registered listeners. Listener registration and event firing
78 * is done with the help of the {@link PropertyChangeSupport} class.
83 @XmlAccessorType(XmlAccessType
.FIELD
)
84 @XmlType(name
= "CdmBase", propOrder
= {
89 public abstract class CdmBase
implements Serializable
, ICdmBase
, ISelfDescriptive
, Cloneable
{
90 private static final long serialVersionUID
= -3053225700018294809L;
91 @SuppressWarnings("unused")
92 private static final Logger logger
= Logger
.getLogger(CdmBase
.class);
96 private PropertyChangeSupport propertyChangeSupport
= new PropertyChangeSupport(this);
100 private static NewEntityListener newEntityListener
;
102 //@XmlAttribute(name = "id", required = true)
105 // @GeneratedValue(generator = "system-increment") //see also AuditEvent.revisionNumber
106 // @GeneratedValue(generator = "enhanced-table")
107 @GeneratedValue(generator
= "custom-enhanced-table")
109 @FieldBridge(impl
=NotNullAwareIdBridge
.class)
110 @Match(MatchMode
.IGNORE
)
116 @XmlAttribute(required
= true)
117 @XmlJavaTypeAdapter(UUIDAdapter
.class)
119 @Type(type
="uuidUserType")
120 @NaturalId // This has the effect of placing a "unique" constraint on the database column
121 @Column(length
=36) //TODO needed? Type UUID will always assure that is exactly 36
122 @Match(MatchMode
.IGNORE
)
124 @Field(store
= Store
.YES
, index
= Index
.YES
, analyze
= Analyze
.NO
)
125 @FieldBridge(impl
= UuidBridge
.class)
129 @XmlElement (name
= "Created", type
= String
.class)
130 @XmlJavaTypeAdapter(DateTimeAdapter
.class)
131 @Type(type
="dateTimeUserType")
132 @Basic(fetch
= FetchType
.LAZY
)
133 @Match(MatchMode
.IGNORE
)
134 @Field(analyze
= Analyze
.NO
)
135 @FieldBridge(impl
= DateTimeBridge
.class)
137 private DateTime created
;
139 @XmlElement (name
= "CreatedBy")
141 @XmlSchemaType(name
= "IDREF")
142 @ManyToOne(fetch
=FetchType
.LAZY
)
143 @Match(MatchMode
.IGNORE
)
145 private User createdBy
;
148 * Class constructor assigning a unique UUID and creation date.
149 * UUID can be changed later via setUuid method.
152 this.uuid
= UUID
.randomUUID();
153 this.created
= new DateTime().withMillisOfSecond(0);
156 public static void setNewEntityListener(NewEntityListener nel
) {
157 newEntityListener
= nel
;
160 public static void fireOnCreateEvent(CdmBase cdmBase
) {
161 if(newEntityListener
!= null) {
162 newEntityListener
.onCreate(cdmBase
);
167 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
170 public void addPropertyChangeListener(PropertyChangeListener listener
) {
171 propertyChangeSupport
.addPropertyChangeListener(listener
);
175 * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
177 public void addPropertyChangeListener(String propertyName
, PropertyChangeListener listener
) {
178 propertyChangeSupport
.addPropertyChangeListener(propertyName
, listener
);
182 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
184 public void removePropertyChangeListener(PropertyChangeListener listener
) {
185 propertyChangeSupport
.removePropertyChangeListener(listener
);
189 * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
191 public void removePropertyChangeListener(String propertyName
, PropertyChangeListener listener
) {
192 propertyChangeSupport
.removePropertyChangeListener(propertyName
, listener
);
195 public boolean hasListeners(String propertyName
) {
196 return propertyChangeSupport
.hasListeners(propertyName
);
199 public void firePropertyChange(String property
, String oldval
, String newval
) {
200 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
202 public void firePropertyChange(String property
, int oldval
, int newval
) {
203 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
205 public void firePropertyChange(String property
, float oldval
, float newval
) {
206 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
208 public void firePropertyChange(String property
, boolean oldval
, boolean newval
) {
209 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
211 public void firePropertyChange(String property
, Object oldval
, Object newval
) {
212 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
214 public void firePropertyChange(PropertyChangeEvent evt
) {
215 propertyChangeSupport
.firePropertyChange(evt
);
219 * This method was initially added to {@link CdmBase} to fix #5161.
220 * It can be overridden by subclasses such as {@link IdentifiableEntity}
221 * to explicitly initialize listeners. This is needed e.g. after de-serialization
222 * as listeners are not serialized due to the @Transient annotation.
223 * However, it can be generally used for other use-cases as well
225 public void initListener() {}
228 * Adds an item to a set of <code>this</code> object and fires the according
229 * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
230 * implemented in aspectJ.
231 * @param set the set the new item is added to
232 * @param newItem the new item to be added to the set
233 * @param propertyName the name of the set as property in <code>this</code> object
235 protected <T
extends CdmBase
> void addToSetWithChangeEvent(Set
<T
> set
, T newItem
, String propertyName
){
236 Set
<T
> oldValue
= new HashSet
<>(set
);
238 firePropertyChange(new PropertyChangeEvent(this, propertyName
, oldValue
, set
));
242 * Removes an item from a set of <code>this</code> object and fires the according
243 * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
244 * implemented in aspectJ.
245 * @param set the set the item is to be removed from
246 * @param itemToRemove the item to be removed from the set
247 * @param propertyName the name of the set as property in <code>this</code> object
249 protected <T
extends CdmBase
> void removeFromSetWithChangeEvent(Set
<T
> set
, T itemToRemove
, String propertyName
){
250 Set
<T
> oldValue
= new HashSet
<T
>(set
);
251 set
.remove(itemToRemove
);
252 firePropertyChange(new PropertyChangeEvent(this, propertyName
, oldValue
, set
));
256 public UUID
getUuid() {
260 public void setUuid(UUID uuid
) {
269 public void setId(int id
) { //see #265 (private ?)
274 public DateTime
getCreated() {
278 public void setCreated(DateTime created
) {
279 if (created
!= null){
280 created
= created
.withMillisOfSecond(0);
281 //created.set(Calendar.MILLISECOND, 0); //old, can be deleted
283 this.created
= created
;
288 public User
getCreatedBy() {
289 return this.createdBy
;
292 public void setCreatedBy(User createdBy
) {
293 this.createdBy
= createdBy
;
296 // ************************** Hibernate proxies *******************/
299 * If entity is a HibernateProxy it returns the initialized object.
300 * Otherwise entity itself is returned.
303 * @throws ClassCastException
305 public static <T
> T
deproxy(T entity
) {
306 return HibernateProxyHelper
.deproxy(entity
);
310 * These methods are present due to HHH-1517 - that in a one-to-many
311 * relationship with a superclass at the "one" end, the proxy created
312 * by hibernate is the superclass, and not the subclass, resulting in
313 * a classcastexception when you try to cast it.
315 * Hopefully this will be resolved through improvements with the creation of
316 * proxy objects by hibernate and the following methods will become redundant,
317 * but for the time being . . .
322 * @throws ClassCastException
324 //non-static does not work because javassist already unwrapps the proxy before calling the method
325 public static <T
extends CdmBase
> T
deproxy(Object object
, Class
<T
> clazz
) throws ClassCastException
{
326 return HibernateProxyHelper
.deproxy(object
, clazz
);
330 public boolean isInstanceOf(Class
<?
extends CdmBase
> clazz
) throws ClassCastException
{
331 return HibernateProxyHelper
.isInstanceOf(this, clazz
);
334 // ************* Object overrides *************************/
337 * Is <code>true</code> if UUID and created timestamp (is this really needed/make sense?)
338 * is the same for the passed Object and this one.
339 * This method is final as subclasses should not override it.
340 * The contract should be the same for all persistable entities.
341 * 2 instances are equal if they represent the same entity in a given
345 * If one wants to compare 2 CdmBase entities content wise you may use e.g. a
346 * {@link IMatchStrategy match strategy} and make sure
347 * {@link IMatchable matching} is implemented for the respective CdmBase subclass.
348 * You may adapt your match strategy to your own needs.
350 * @see java.lang.Object#equals(java.lang.Object)
351 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
352 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
353 * for more information about equals and hashcode.
356 public final boolean equals(Object obj
) {
363 if (!CdmBase
.class.isAssignableFrom(obj
.getClass())){
366 ICdmBase cdmObj
= (ICdmBase
)obj
;
367 UUID objUuid
= cdmObj
.getUuid();
368 if (objUuid
== null){
369 throw new NullPointerException("CdmBase is missing UUID");
371 boolean uuidEqual
= objUuid
.equals(this.getUuid());
372 //TODO is this still needed?
373 boolean createdEqual
= CdmUtils
.nullSafeEqual(cdmObj
.getCreated(), this.getCreated());
374 if (! uuidEqual
|| !createdEqual
){
382 /** Overrides {@link java.lang.Object#hashCode()}
383 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
384 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
385 * for more information about equals and hashcode.
388 public int hashCode() {
390 if(this.getUuid() != null) {
391 //this unfortunately leads to errors when loading maps via hibernate
392 //as hibernate computes hash values for CdmBase objects used as key at
393 // a time when the uuid is not yet loaded from the database. Therefore
394 //the hash values later change and give wrong results when retrieving
395 //data from the map (map.get(key) returns null, though there is an entry
396 //for key in the map.
397 //see further comments in #2114
398 int result
= 29 * hashCode
+ this.getUuid().hashCode();
399 // int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
402 return 29 * hashCode
;
407 * Overrides {@link java.lang.Object#toString()}.
408 * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
409 * call to {link {@link #instanceToString()}.<br>
410 * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
411 * without problems like lazy loading, unreal states etc.
413 * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
414 * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
416 * <b>For example</b>: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0>
417 * @see java.lang.Object#toString()
420 public String
toString() {
421 return instanceToString();
425 * This returns an String that identifies the cdm instance well without being necessarily unique.
426 * The string representation combines the class name the {@link #id} and {@link #uuid}.
428 * <b>For example</b>: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0>
431 public String
instanceToString() {
432 return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
435 // **************** invoke methods **************************/
437 protected void invokeSetMethod(Method method
, Object object
){
439 method
.invoke(object
, this);
440 } catch (Exception e
) {
442 //TODO handle exceptioin;
446 protected void invokeSetMethodWithNull(Method method
, Object object
){
448 Object
[] nul
= new Object
[]{null};
449 method
.invoke(object
, nul
);
450 } catch (Exception e
) {
452 //TODO handle exceptioin;
458 public String
getUserFriendlyTypeName(){
459 return getClass().getSimpleName();
464 public String
getUserFriendlyDescription(){
469 public String
getUserFriendlyFieldName(String field
){
473 // ********************* HELPER ****************************************/
475 protected <T
extends CdmBase
> boolean replaceInList(List
<T
> list
,
476 T newObject
, T oldObject
){
477 boolean result
= false;
478 for (int i
= 0; i
< list
.size(); i
++){
479 if (list
.get(i
).equals(oldObject
)){
480 list
.set(i
, newObject
);
489 * Returns true if the given String is blank.
490 * @param str the String to check
491 * @see CdmUtils#isBlank(String)
492 * @return true if str is blank
494 protected boolean isBlank(String str
) {
495 return CdmUtils
.isBlank(str
);
500 //********************** CLONE *****************************************/
502 // protected void clone(CdmBase clone){
503 // clone.setCreatedBy(createdBy);
505 // clone.propertyChangeSupport=new PropertyChangeSupport(clone);
506 // //Constructor Attributes
507 // //clone.setCreated(created);
508 // //clone.setUuid(getUuid());
513 public Object
clone() throws CloneNotSupportedException
{
514 CdmBase result
= (CdmBase
)super.clone();
515 result
.propertyChangeSupport
=new PropertyChangeSupport(result
);
519 result
.setUuid(UUID
.randomUUID());
520 result
.setCreated(new DateTime());
521 result
.setCreatedBy(null);