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
.EnumSet
;
17 import java
.util
.HashSet
;
18 import java
.util
.List
;
20 import java
.util
.UUID
;
22 import javax
.persistence
.Basic
;
23 import javax
.persistence
.Column
;
24 import javax
.persistence
.FetchType
;
25 import javax
.persistence
.GeneratedValue
;
26 import javax
.persistence
.Id
;
27 import javax
.persistence
.ManyToOne
;
28 import javax
.persistence
.MappedSuperclass
;
29 import javax
.persistence
.Transient
;
30 import javax
.validation
.constraints
.Min
;
31 import javax
.validation
.constraints
.NotNull
;
32 import javax
.xml
.bind
.annotation
.XmlAccessType
;
33 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
34 import javax
.xml
.bind
.annotation
.XmlAttribute
;
35 import javax
.xml
.bind
.annotation
.XmlElement
;
36 import javax
.xml
.bind
.annotation
.XmlID
;
37 import javax
.xml
.bind
.annotation
.XmlIDREF
;
38 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
39 import javax
.xml
.bind
.annotation
.XmlTransient
;
40 import javax
.xml
.bind
.annotation
.XmlType
;
41 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
43 import org
.apache
.commons
.lang3
.StringUtils
;
44 import org
.apache
.log4j
.Logger
;
45 import org
.hibernate
.annotations
.NaturalId
;
46 import org
.hibernate
.annotations
.Type
;
47 import org
.hibernate
.envers
.Audited
;
48 import org
.hibernate
.search
.annotations
.Analyze
;
49 import org
.hibernate
.search
.annotations
.DocumentId
;
50 import org
.hibernate
.search
.annotations
.Field
;
51 import org
.hibernate
.search
.annotations
.FieldBridge
;
52 import org
.hibernate
.search
.annotations
.Index
;
53 import org
.hibernate
.search
.annotations
.Store
;
54 import org
.joda
.time
.DateTime
;
56 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
57 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
58 import eu
.etaxonomy
.cdm
.hibernate
.search
.DateTimeBridge
;
59 import eu
.etaxonomy
.cdm
.hibernate
.search
.NotNullAwareIdBridge
;
60 import eu
.etaxonomy
.cdm
.hibernate
.search
.UuidBridge
;
61 import eu
.etaxonomy
.cdm
.jaxb
.DateTimeAdapter
;
62 import eu
.etaxonomy
.cdm
.jaxb
.UUIDAdapter
;
63 import eu
.etaxonomy
.cdm
.model
.NewEntityListener
;
64 import eu
.etaxonomy
.cdm
.model
.permission
.User
;
65 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchStrategyEqual
;
66 import eu
.etaxonomy
.cdm
.strategy
.match
.IMatchable
;
67 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
68 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
74 * The base class for all CDM domain classes implementing UUIDs and bean property change event firing.
75 * It provides a globally unique UUID and keeps track of creation date and person.
76 * The UUID is the same for different versions (see {@link VersionableEntity}) of a CDM object, so a locally unique id exists in addition
77 * that allows to safely access and store several objects (=version) with the same UUID.
79 * This class together with the {@link eu.etaxonomy.cdm.aspectj.PropertyChangeAspect}
80 * will fire bean change events to all registered listeners. Listener registration and event firing
81 * is done with the help of the {@link PropertyChangeSupport} class.
86 @XmlAccessorType(XmlAccessType
.FIELD
)
87 @XmlType(name
= "CdmBase", propOrder
= {
92 public abstract class CdmBase
implements Serializable
, ICdmBase
, ISelfDescriptive
, Cloneable
{
94 private static final long serialVersionUID
= -3053225700018294809L;
95 @SuppressWarnings("unused")
96 private static final Logger logger
= Logger
.getLogger(CdmBase
.class);
98 protected static final int CLOB_LENGTH
= 65536;
102 private PropertyChangeSupport propertyChangeSupport
= new PropertyChangeSupport(this);
106 private static NewEntityListener newEntityListener
;
108 //@XmlAttribute(name = "id", required = true)
111 // @GeneratedValue(generator = "system-increment") //see also AuditEvent.revisionNumber
112 // @GeneratedValue(generator = "enhanced-table")
113 @GeneratedValue(generator
= "custom-enhanced-table")
115 @FieldBridge(impl
=NotNullAwareIdBridge
.class)
116 @Match(MatchMode
.IGNORE
)
122 @XmlAttribute(required
= true)
123 @XmlJavaTypeAdapter(UUIDAdapter
.class)
125 @Type(type
="uuidUserType")
126 @NaturalId // This has the effect of placing a "unique" constraint on the database column
127 @Column(length
=36) //TODO needed? Type UUID will always assure that is exactly 36
128 @Match(MatchMode
.IGNORE
)
130 @Field(store
= Store
.YES
, index
= Index
.YES
, analyze
= Analyze
.NO
)
131 @FieldBridge(impl
= UuidBridge
.class)
135 @XmlElement (name
= "Created", type
= String
.class)
136 @XmlJavaTypeAdapter(DateTimeAdapter
.class)
137 @Type(type
="dateTimeUserType")
138 @Basic(fetch
= FetchType
.LAZY
)
139 @Match(MatchMode
.IGNORE
)
140 @Field(analyze
= Analyze
.NO
)
141 @FieldBridge(impl
= DateTimeBridge
.class)
143 private DateTime created
;
145 @XmlElement (name
= "CreatedBy")
147 @XmlSchemaType(name
= "IDREF")
148 @ManyToOne(fetch
=FetchType
.LAZY
)
149 @Match(MatchMode
.IGNORE
)
151 private User createdBy
;
154 * Class constructor assigning a unique UUID and creation date.
155 * UUID can be changed later via setUuid method.
158 this.uuid
= UUID
.randomUUID();
159 this.created
= new DateTime().withMillisOfSecond(0);
162 //TODO are these 2 methods really needed, looks they are not used except for NewEntityListenerTest
163 public static void setNewEntityListener(NewEntityListener nel
) {
164 newEntityListener
= nel
;
167 public static void fireOnCreateEvent(CdmBase cdmBase
) {
168 if(newEntityListener
!= null) {
169 newEntityListener
.onCreate(cdmBase
);
174 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
176 public void addPropertyChangeListener(PropertyChangeListener listener
) {
177 propertyChangeSupport
.addPropertyChangeListener(listener
);
181 * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
183 public void addPropertyChangeListener(String propertyName
, PropertyChangeListener listener
) {
184 propertyChangeSupport
.addPropertyChangeListener(propertyName
, listener
);
188 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
190 public void removePropertyChangeListener(PropertyChangeListener listener
) {
191 propertyChangeSupport
.removePropertyChangeListener(listener
);
195 * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
197 public void removePropertyChangeListener(String propertyName
, PropertyChangeListener listener
) {
198 propertyChangeSupport
.removePropertyChangeListener(propertyName
, listener
);
201 public boolean hasListeners(String propertyName
) {
202 return propertyChangeSupport
.hasListeners(propertyName
);
205 public void firePropertyChange(String property
, String oldval
, String newval
) {
206 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
208 public void firePropertyChange(String property
, int oldval
, int newval
) {
209 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
211 public void firePropertyChange(String property
, float oldval
, float newval
) {
212 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
214 public void firePropertyChange(String property
, boolean oldval
, boolean newval
) {
215 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
217 public void firePropertyChange(String property
, Object oldval
, Object newval
) {
218 propertyChangeSupport
.firePropertyChange(property
, oldval
, newval
);
220 public void firePropertyChange(PropertyChangeEvent evt
) {
221 propertyChangeSupport
.firePropertyChange(evt
);
225 * This method was initially added to {@link CdmBase} to fix #5161.
226 * It can be overridden by subclasses such as {@link IdentifiableEntity}
227 * to explicitly initialize listeners. This is needed e.g. after de-serialization
228 * as listeners are not serialized due to the @Transient annotation.
229 * However, it can be generally used for other use-cases as well
231 public void initListener() {}
234 * Adds an item to a set of <code>this</code> object and fires the according
235 * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
236 * implemented in aspectJ.
237 * @param set the set the new item is added to
238 * @param newItem the new item to be added to the set
239 * @param propertyName the name of the set as property in <code>this</code> object
241 protected <T
extends CdmBase
> void addToSetWithChangeEvent(Set
<T
> set
, T newItem
, String propertyName
){
242 Set
<T
> oldValue
= new HashSet
<>(set
);
244 firePropertyChange(new PropertyChangeEvent(this, propertyName
, oldValue
, set
));
248 * Removes an item from a set of <code>this</code> object and fires the according
249 * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
250 * implemented in aspectJ.
251 * @param set the set the item is to be removed from
252 * @param itemToRemove the item to be removed from the set
253 * @param propertyName the name of the set as property in <code>this</code> object
255 protected <T
extends CdmBase
> void removeFromSetWithChangeEvent(Set
<T
> set
, T itemToRemove
, String propertyName
){
256 Set
<T
> oldValue
= new HashSet
<T
>(set
);
257 set
.remove(itemToRemove
);
258 firePropertyChange(new PropertyChangeEvent(this, propertyName
, oldValue
, set
));
262 public UUID
getUuid() {
266 public void setUuid(UUID uuid
) {
275 public void setId(int id
) { //see #265 (private ?)
280 public DateTime
getCreated() {
284 public void setCreated(DateTime created
) {
285 if (created
!= null){
286 created
= created
.withMillisOfSecond(0);
287 //created.set(Calendar.MILLISECOND, 0); //old, can be deleted
289 this.created
= created
;
294 public User
getCreatedBy() {
295 return this.createdBy
;
298 public void setCreatedBy(User createdBy
) {
299 this.createdBy
= createdBy
;
302 // ************************** Hibernate proxies *******************/
305 * If entity is a HibernateProxy it returns the initialized object.
306 * Otherwise entity itself is returned.
309 * @throws ClassCastException
311 public static <T
> T
deproxy(T entity
) {
312 return HibernateProxyHelper
.deproxy(entity
);
316 * These methods are present due to HHH-1517 - that in a one-to-many
317 * relationship with a superclass at the "one" end, the proxy created
318 * by hibernate is the superclass, and not the subclass, resulting in
319 * a ClassCastException when you try to cast it.
321 * Hopefully this will be resolved through improvements with the creation of
322 * proxy objects by hibernate and the following methods will become redundant,
323 * but for the time being . . .
328 * @throws ClassCastException
330 //non-static does not work because javassist already unwrapps the proxy before calling the method
331 public static <T
extends CdmBase
> T
deproxy(Object object
, Class
<T
> clazz
) throws ClassCastException
{
332 return HibernateProxyHelper
.deproxy(object
, clazz
);
336 public boolean isInstanceOf(Class
<?
extends CdmBase
> clazz
) throws ClassCastException
{
337 return HibernateProxyHelper
.isInstanceOf(this, clazz
);
343 public boolean isPersited() {
347 // ************* Object overrides *************************/
350 * Is <code>true</code> if UUID and created timestamp (is this really needed/make sense?)
351 * is the same for the passed Object and this one.
352 * This method is final as subclasses should not override it.<BR>
354 * The contract should be the same for all persistable entities.
355 * 2 instances are equal if they represent the same entity in a given
357 * NOTE: currently the method is only final in {@link VersionableEntity#equals(Object)}.
358 * For discussion see #7202.
361 * If one wants to compare 2 CdmBase entities content wise you may use e.g. a
362 * {@link IMatchStrategyEqual match strategy} and make sure
363 * {@link IMatchable matching} is implemented for the respective CdmBase subclass.
364 * You may adapt your match strategy to your own needs.
366 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities},
367 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
368 * for more information about equals and hashcode.
370 * See also https://dev.e-taxonomy.eu/redmine/issues/7155 and related tickets for discussion.
372 * @see java.lang.Object#equals(java.lang.Object)
376 public boolean equals(Object obj
) {
383 if (!CdmBase
.class.isAssignableFrom(obj
.getClass())){
386 ICdmBase cdmObj
= (ICdmBase
)obj
;
387 UUID objUuid
= cdmObj
.getUuid();
388 if (objUuid
== null){
389 throw new NullPointerException("CdmBase is missing UUID");
391 boolean uuidEqual
= objUuid
.equals(this.getUuid());
392 //TODO is this still needed?
393 // boolean createdEqual = CdmUtils.nullSafeEqual(cdmObj.getCreated(), this.getCreated());
394 boolean createdEqual
= true; //preliminary, to test im createdEqual is still needed #7201
395 if (! uuidEqual
|| !createdEqual
){
403 /** Overrides {@link java.lang.Object#hashCode()}
404 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
405 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
406 * for more information about equals and hashcode.
409 public int hashCode() {
411 if(this.getUuid() != null) {
412 //this unfortunately leads to errors when loading maps via hibernate
413 //as hibernate computes hash values for CdmBase objects used as key at
414 // a time when the uuid is not yet loaded from the database. Therefore
415 //the hash values later change and give wrong results when retrieving
416 //data from the map (map.get(key) returns null, though there is an entry
417 //for key in the map.
418 //see further comments in #2114
419 int result
= 29 * hashCode
+ this.getUuid().hashCode();
420 // int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
423 return 29 * hashCode
;
428 * Overrides {@link java.lang.Object#toString()}.
429 * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
430 * call to {link {@link #instanceToString()}.<br>
431 * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
432 * without problems like lazy loading, unreal states etc.
434 * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
435 * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
437 * <b>For example</b>: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0>
438 * @see java.lang.Object#toString()
441 public String
toString() {
442 return instanceToString();
446 * This returns an String that identifies the cdm instance well without being necessarily unique.
447 * The string representation combines the class name the {@link #id} and {@link #uuid}.
449 * <b>For example</b>: Taxon#13<b5938a98-c1de-4dda-b040-d5cc5bfb3bc0>
452 public String
instanceToString() {
453 return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
456 // **************** invoke methods **************************/
458 protected void invokeSetMethod(Method method
, Object object
){
460 method
.invoke(object
, this);
461 } catch (Exception e
) {
463 //TODO handle exceptioin;
467 protected void invokeSetMethodWithNull(Method method
, Object object
){
469 Object
[] nul
= new Object
[]{null};
470 method
.invoke(object
, nul
);
471 } catch (Exception e
) {
473 //TODO handle exceptioin;
479 public String
getUserFriendlyTypeName(){
480 return CdmUtils
.userFriendlyClassName(getClass());
485 public String
getUserFriendlyDescription(){
490 public String
getUserFriendlyFieldName(String field
){
496 * EnumSets being part of the model should be immutable to make hibernate know if they have been changed.
497 * Therefore any change to the enum set should result in a new enum set.
499 protected <T
extends Enum
<T
>> EnumSet
<T
> newEnumSet(@NotNull EnumSet
<T
> enumSet
, T additionalClass
, T classToRemove
) {
500 EnumSet
<T
> result
= EnumSet
.copyOf(enumSet
);
501 if (additionalClass
!= null){
502 result
.add(additionalClass
);
504 if (classToRemove
!= null){
505 result
.remove(classToRemove
);
510 // ********************* HELPER ****************************************/
512 protected <T
extends CdmBase
> boolean replaceInList(List
<T
> list
,
513 T newObject
, T oldObject
){
514 boolean result
= false;
515 for (int i
= 0; i
< list
.size(); i
++){
516 if (list
.get(i
).equals(oldObject
)){
517 list
.set(i
, newObject
);
525 * Returns <code>true</code> if the given String is blank.
526 * @param str the String to check
527 * @see StringUtils#isBlank(String)
528 * @return <code>true</code> if str is blank, <code>false</code> otherwise
530 protected static boolean isBlank(String str
) {
531 return StringUtils
.isBlank(str
);
535 * Returns <code>true</code> if the given String is not blank.
536 * @param str the String to check
537 * @see StringUtils#isNotBlank(String)
538 * @return <code>true</code> if str is not blank, <code>false</code> otherwise
540 protected static boolean isNotBlank(String str
) {
541 return StringUtils
.isNotBlank(str
);
544 // **************** EMPTY ************************/
547 * Checks if the entity is completely empty
548 * and therefore can be removed.<BR>
550 * To be implemented by subclasses if used
552 * @return <code>true</code> if empty
554 protected boolean checkEmpty(){
555 //nothing to check; id, uuid, created and createdBy are not relevant
559 //********************** CLONE *****************************************/
561 // protected void clone(CdmBase clone){
562 // clone.setCreatedBy(createdBy);
564 // clone.propertyChangeSupport=new PropertyChangeSupport(clone);
565 // //Constructor Attributes
566 // //clone.setCreated(created);
567 // //clone.setUuid(getUuid());
572 public CdmBase
clone() throws CloneNotSupportedException
{
573 CdmBase result
= (CdmBase
)super.clone();
574 result
.propertyChangeSupport
=new PropertyChangeSupport(result
);
578 result
.setUuid(UUID
.randomUUID());
579 result
.setCreated(new DateTime());
580 result
.setCreatedBy(null);