ref #3560 fix includeUnpublished for taxon relationships and correct includePublished...
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / CdmBase.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9 package eu.etaxonomy.cdm.model.common;
10
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;
18 import java.util.Set;
19 import java.util.UUID;
20
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;
41
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;
53
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;
66
67
68
69
70 /**
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.
75 *
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.
79 *
80 * @author m.doering
81 *
82 */
83 @XmlAccessorType(XmlAccessType.FIELD)
84 @XmlType(name = "CdmBase", propOrder = {
85 "created",
86 "createdBy"
87 })
88 @MappedSuperclass
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);
93
94 @Transient
95 @XmlTransient
96 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
97
98 @Transient
99 @XmlTransient
100 private static NewEntityListener newEntityListener;
101
102 //@XmlAttribute(name = "id", required = true)
103 @XmlTransient
104 @Id
105 // @GeneratedValue(generator = "system-increment") //see also AuditEvent.revisionNumber
106 // @GeneratedValue(generator = "enhanced-table")
107 @GeneratedValue(generator = "custom-enhanced-table")
108 @DocumentId
109 @FieldBridge(impl=NotNullAwareIdBridge.class)
110 @Match(MatchMode.IGNORE)
111 @NotNull
112 @Min(0)
113 @Audited
114 private int id;
115
116 @XmlAttribute(required = true)
117 @XmlJavaTypeAdapter(UUIDAdapter.class)
118 @XmlID
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)
123 @NotNull
124 @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
125 @FieldBridge(impl = UuidBridge.class)
126 @Audited
127 protected UUID uuid;
128
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)
136 @Audited
137 private DateTime created;
138
139 @XmlElement (name = "CreatedBy")
140 @XmlIDREF
141 @XmlSchemaType(name = "IDREF")
142 @ManyToOne(fetch=FetchType.LAZY)
143 @Match(MatchMode.IGNORE)
144 @Audited
145 private User createdBy;
146
147 /**
148 * Class constructor assigning a unique UUID and creation date.
149 * UUID can be changed later via setUuid method.
150 */
151 public CdmBase() {
152 this.uuid = UUID.randomUUID();
153 this.created = new DateTime().withMillisOfSecond(0);
154 }
155
156 public static void setNewEntityListener(NewEntityListener nel) {
157 newEntityListener = nel;
158 }
159
160 public static void fireOnCreateEvent(CdmBase cdmBase) {
161 if(newEntityListener != null) {
162 newEntityListener.onCreate(cdmBase);
163 }
164 }
165
166 /**
167 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
168 * @param listener
169 */
170 public void addPropertyChangeListener(PropertyChangeListener listener) {
171 propertyChangeSupport.addPropertyChangeListener(listener);
172 }
173
174 /**
175 * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
176 */
177 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
178 propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
179 }
180
181 /**
182 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
183 */
184 public void removePropertyChangeListener(PropertyChangeListener listener) {
185 propertyChangeSupport.removePropertyChangeListener(listener);
186 }
187
188 /**
189 * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
190 */
191 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
192 propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
193 }
194
195 public boolean hasListeners(String propertyName) {
196 return propertyChangeSupport.hasListeners(propertyName);
197 }
198
199 public void firePropertyChange(String property, String oldval, String newval) {
200 propertyChangeSupport.firePropertyChange(property, oldval, newval);
201 }
202 public void firePropertyChange(String property, int oldval, int newval) {
203 propertyChangeSupport.firePropertyChange(property, oldval, newval);
204 }
205 public void firePropertyChange(String property, float oldval, float newval) {
206 propertyChangeSupport.firePropertyChange(property, oldval, newval);
207 }
208 public void firePropertyChange(String property, boolean oldval, boolean newval) {
209 propertyChangeSupport.firePropertyChange(property, oldval, newval);
210 }
211 public void firePropertyChange(String property, Object oldval, Object newval) {
212 propertyChangeSupport.firePropertyChange(property, oldval, newval);
213 }
214 public void firePropertyChange(PropertyChangeEvent evt) {
215 propertyChangeSupport.firePropertyChange(evt);
216 }
217
218 /**
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
224 */
225 public void initListener() {}
226
227 /**
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
234 */
235 protected <T extends CdmBase> void addToSetWithChangeEvent(Set<T> set, T newItem, String propertyName ){
236 Set<T> oldValue = new HashSet<>(set);
237 set.add(newItem);
238 firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
239 }
240
241 /**
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
248 */
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));
253 }
254
255 @Override
256 public UUID getUuid() {
257 return uuid;
258 }
259 @Override
260 public void setUuid(UUID uuid) {
261 this.uuid = uuid;
262 }
263
264 @Override
265 public int getId() {
266 return this.id;
267 }
268 @Override
269 public void setId(int id) { //see #265 (private ?)
270 this.id = id;
271 }
272
273 @Override
274 public DateTime getCreated() {
275 return created;
276 }
277 @Override
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
282 }
283 this.created = created;
284 }
285
286
287 @Override
288 public User getCreatedBy() {
289 return this.createdBy;
290 }
291 @Override
292 public void setCreatedBy(User createdBy) {
293 this.createdBy = createdBy;
294 }
295
296 // ************************** Hibernate proxies *******************/
297
298 /**
299 * If entity is a HibernateProxy it returns the initialized object.
300 * Otherwise entity itself is returned.
301 * @param entity
302 * @return
303 * @throws ClassCastException
304 */
305 public static <T> T deproxy(T entity) {
306 return HibernateProxyHelper.deproxy(entity);
307 }
308
309 /**
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.
314 *
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 . . .
318 * @param <T>
319 * @param object
320 * @param clazz
321 * @return
322 * @throws ClassCastException
323 */
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);
327 }
328
329 @Override
330 public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
331 return HibernateProxyHelper.isInstanceOf(this, clazz);
332 }
333
334 @Override
335 @XmlTransient
336 @Transient
337 public boolean isPersited() {
338 return id != 0;
339 }
340
341 // ************* Object overrides *************************/
342
343 /**
344 * Is <code>true</code> if UUID and created timestamp (is this really needed/make sense?)
345 * is the same for the passed Object and this one.
346 * This method is final as subclasses should not override it.<BR>
347 *
348 * The contract should be the same for all persistable entities.
349 * 2 instances are equal if they represent the same entity in a given
350 * database.<BR>
351 * NOTE: currently the method is only final in {@link VersionableEntity#equals(Object)}.
352 * For discussion see #7202.
353 * <BR><BR>
354 *
355 * If one wants to compare 2 CdmBase entities content wise you may use e.g. a
356 * {@link IMatchStrategy match strategy} and make sure
357 * {@link IMatchable matching} is implemented for the respective CdmBase subclass.
358 * You may adapt your match strategy to your own needs.
359 *
360 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities},
361 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
362 * for more information about equals and hashcode.
363 * <BR>
364 * See also https://dev.e-taxonomy.eu/redmine/issues/7155 and related tickets for discussion.
365 *
366 * @see java.lang.Object#equals(java.lang.Object)
367 *
368 */
369 @Override
370 public boolean equals(Object obj) {
371 if (obj == this){
372 return true;
373 }
374 if (obj == null){
375 return false;
376 }
377 if (!CdmBase.class.isAssignableFrom(obj.getClass())){
378 return false;
379 }
380 ICdmBase cdmObj = (ICdmBase)obj;
381 UUID objUuid = cdmObj.getUuid();
382 if (objUuid == null){
383 throw new NullPointerException("CdmBase is missing UUID");
384 }
385 boolean uuidEqual = objUuid.equals(this.getUuid());
386 //TODO is this still needed?
387 // boolean createdEqual = CdmUtils.nullSafeEqual(cdmObj.getCreated(), this.getCreated());
388 boolean createdEqual = true; //preliminary, to test im createdEqual is still needed #7201
389 if (! uuidEqual || !createdEqual){
390 return false;
391 }
392 return true;
393 }
394
395
396
397 /** Overrides {@link java.lang.Object#hashCode()}
398 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
399 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
400 * for more information about equals and hashcode.
401 */
402 @Override
403 public int hashCode() {
404 int hashCode = 7;
405 if(this.getUuid() != null) {
406 //this unfortunately leads to errors when loading maps via hibernate
407 //as hibernate computes hash values for CdmBase objects used as key at
408 // a time when the uuid is not yet loaded from the database. Therefore
409 //the hash values later change and give wrong results when retrieving
410 //data from the map (map.get(key) returns null, though there is an entry
411 //for key in the map.
412 //see further comments in #2114
413 int result = 29 * hashCode + this.getUuid().hashCode();
414 // int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
415 return result;
416 } else {
417 return 29 * hashCode;
418 }
419 }
420
421 /**
422 * Overrides {@link java.lang.Object#toString()}.
423 * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
424 * call to {link {@link #instanceToString()}.<br>
425 * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
426 * without problems like lazy loading, unreal states etc.
427 * <p>
428 * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
429 * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
430 * <p>
431 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
432 * @see java.lang.Object#toString()
433 */
434 @Override
435 public String toString() {
436 return instanceToString();
437 }
438
439 /**
440 * This returns an String that identifies the cdm instance well without being necessarily unique.
441 * The string representation combines the class name the {@link #id} and {@link #uuid}.
442 * <p>
443 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
444 * @return
445 */
446 public String instanceToString() {
447 return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
448 }
449
450 // **************** invoke methods **************************/
451
452 protected void invokeSetMethod(Method method, Object object){
453 try {
454 method.invoke(object, this);
455 } catch (Exception e) {
456 e.printStackTrace();
457 //TODO handle exceptioin;
458 }
459 }
460
461 protected void invokeSetMethodWithNull(Method method, Object object){
462 try {
463 Object[] nul = new Object[]{null};
464 method.invoke(object, nul);
465 } catch (Exception e) {
466 e.printStackTrace();
467 //TODO handle exceptioin;
468 }
469 }
470
471 @Transient
472 @Override
473 public String getUserFriendlyTypeName(){
474 return getClass().getSimpleName();
475 }
476
477 @Transient
478 @Override
479 public String getUserFriendlyDescription(){
480 return toString();
481 }
482
483 @Override
484 public String getUserFriendlyFieldName(String field){
485 return field;
486 }
487
488 // ********************* HELPER ****************************************/
489
490 protected <T extends CdmBase> boolean replaceInList(List<T> list,
491 T newObject, T oldObject){
492 boolean result = false;
493 for (int i = 0; i < list.size(); i++){
494 if (list.get(i).equals(oldObject)){
495 list.set(i, newObject);
496 result = true;
497 }
498 }
499 return result;
500 }
501
502
503 /**
504 * Returns true if the given String is blank.
505 * @param str the String to check
506 * @see CdmUtils#isBlank(String)
507 * @return true if str is blank
508 */
509 protected boolean isBlank(String str) {
510 return CdmUtils.isBlank(str);
511 }
512
513
514
515 //********************** CLONE *****************************************/
516
517 // protected void clone(CdmBase clone){
518 // clone.setCreatedBy(createdBy);
519 // clone.setId(id);
520 // clone.propertyChangeSupport=new PropertyChangeSupport(clone);
521 // //Constructor Attributes
522 // //clone.setCreated(created);
523 // //clone.setUuid(getUuid());
524 //
525 // }
526
527 @Override
528 public Object clone() throws CloneNotSupportedException{
529 CdmBase result = (CdmBase)super.clone();
530 result.propertyChangeSupport=new PropertyChangeSupport(result);
531
532 //TODO ?
533 result.setId(0);
534 result.setUuid(UUID.randomUUID());
535 result.setCreated(new DateTime());
536 result.setCreatedBy(null);
537
538 //no changes to: -
539 return result;
540 }
541
542 }