ref #9619 add conceptId, conceptDefinitions, conceptStatus, taxonTypes and currentCon...
[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.EnumSet;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.UUID;
21
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;
42
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;
55
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;
69
70
71
72
73 /**
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.
78 *
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.
82 *
83 * @author m.doering
84 *
85 */
86 @XmlAccessorType(XmlAccessType.FIELD)
87 @XmlType(name = "CdmBase", propOrder = {
88 "created",
89 "createdBy"
90 })
91 @MappedSuperclass
92 public abstract class CdmBase implements Serializable, ICdmBase, ISelfDescriptive, Cloneable{
93
94 private static final long serialVersionUID = -3053225700018294809L;
95 @SuppressWarnings("unused")
96 private static final Logger logger = Logger.getLogger(CdmBase.class);
97
98 protected static final int CLOB_LENGTH = 65536;
99
100 @Transient
101 @XmlTransient
102 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
103
104 @Transient
105 @XmlTransient
106 private static NewEntityListener newEntityListener;
107
108 //@XmlAttribute(name = "id", required = true)
109 @XmlTransient
110 @Id
111 // @GeneratedValue(generator = "system-increment") //see also AuditEvent.revisionNumber
112 // @GeneratedValue(generator = "enhanced-table")
113 @GeneratedValue(generator = "custom-enhanced-table")
114 @DocumentId
115 @FieldBridge(impl=NotNullAwareIdBridge.class)
116 @Match(MatchMode.IGNORE)
117 @NotNull
118 @Min(0)
119 @Audited
120 private int id;
121
122 @XmlAttribute(required = true)
123 @XmlJavaTypeAdapter(UUIDAdapter.class)
124 @XmlID
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)
129 @NotNull
130 @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
131 @FieldBridge(impl = UuidBridge.class)
132 @Audited
133 protected UUID uuid;
134
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)
142 @Audited
143 private DateTime created;
144
145 @XmlElement (name = "CreatedBy")
146 @XmlIDREF
147 @XmlSchemaType(name = "IDREF")
148 @ManyToOne(fetch=FetchType.LAZY)
149 @Match(MatchMode.IGNORE)
150 @Audited
151 private User createdBy;
152
153 /**
154 * Class constructor assigning a unique UUID and creation date.
155 * UUID can be changed later via setUuid method.
156 */
157 public CdmBase() {
158 this.uuid = UUID.randomUUID();
159 this.created = new DateTime().withMillisOfSecond(0);
160 }
161
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;
165 }
166
167 public static void fireOnCreateEvent(CdmBase cdmBase) {
168 if(newEntityListener != null) {
169 newEntityListener.onCreate(cdmBase);
170 }
171 }
172
173 /**
174 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
175 */
176 public void addPropertyChangeListener(PropertyChangeListener listener) {
177 propertyChangeSupport.addPropertyChangeListener(listener);
178 }
179
180 /**
181 * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
182 */
183 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
184 propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
185 }
186
187 /**
188 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
189 */
190 public void removePropertyChangeListener(PropertyChangeListener listener) {
191 propertyChangeSupport.removePropertyChangeListener(listener);
192 }
193
194 /**
195 * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
196 */
197 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
198 propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
199 }
200
201 public boolean hasListeners(String propertyName) {
202 return propertyChangeSupport.hasListeners(propertyName);
203 }
204
205 public void firePropertyChange(String property, String oldval, String newval) {
206 propertyChangeSupport.firePropertyChange(property, oldval, newval);
207 }
208 public void firePropertyChange(String property, int oldval, int newval) {
209 propertyChangeSupport.firePropertyChange(property, oldval, newval);
210 }
211 public void firePropertyChange(String property, float oldval, float newval) {
212 propertyChangeSupport.firePropertyChange(property, oldval, newval);
213 }
214 public void firePropertyChange(String property, boolean oldval, boolean newval) {
215 propertyChangeSupport.firePropertyChange(property, oldval, newval);
216 }
217 public void firePropertyChange(String property, Object oldval, Object newval) {
218 propertyChangeSupport.firePropertyChange(property, oldval, newval);
219 }
220 public void firePropertyChange(PropertyChangeEvent evt) {
221 propertyChangeSupport.firePropertyChange(evt);
222 }
223
224 /**
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
230 */
231 public void initListener() {}
232
233 /**
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
240 */
241 protected <T extends CdmBase> void addToSetWithChangeEvent(Set<T> set, T newItem, String propertyName ){
242 Set<T> oldValue = new HashSet<>(set);
243 set.add(newItem);
244 firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
245 }
246
247 /**
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
254 */
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));
259 }
260
261 @Override
262 public UUID getUuid() {
263 return uuid;
264 }
265 @Override
266 public void setUuid(UUID uuid) {
267 this.uuid = uuid;
268 }
269
270 @Override
271 public int getId() {
272 return this.id;
273 }
274 @Override
275 public void setId(int id) { //see #265 (private ?)
276 this.id = id;
277 }
278
279 @Override
280 public DateTime getCreated() {
281 return created;
282 }
283 @Override
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
288 }
289 this.created = created;
290 }
291
292
293 @Override
294 public User getCreatedBy() {
295 return this.createdBy;
296 }
297 @Override
298 public void setCreatedBy(User createdBy) {
299 this.createdBy = createdBy;
300 }
301
302 // ************************** Hibernate proxies *******************/
303
304 /**
305 * If entity is a HibernateProxy it returns the initialized object.
306 * Otherwise entity itself is returned.
307 * @param entity
308 * @return
309 * @throws ClassCastException
310 */
311 public static <T> T deproxy(T entity) {
312 return HibernateProxyHelper.deproxy(entity);
313 }
314
315 /**
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.
320 *
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 . . .
324 * @param <T>
325 * @param object
326 * @param clazz
327 * @return
328 * @throws ClassCastException
329 */
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);
333 }
334
335 @Override
336 public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
337 return HibernateProxyHelper.isInstanceOf(this, clazz);
338 }
339
340 @Override
341 @XmlTransient
342 @Transient
343 public boolean isPersited() {
344 return id != 0;
345 }
346
347 // ************* Object overrides *************************/
348
349 /**
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>
353 *
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
356 * database.<BR>
357 * NOTE: currently the method is only final in {@link VersionableEntity#equals(Object)}.
358 * For discussion see #7202.
359 * <BR><BR>
360 *
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.
365 *
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.
369 * <BR>
370 * See also https://dev.e-taxonomy.eu/redmine/issues/7155 and related tickets for discussion.
371 *
372 * @see java.lang.Object#equals(java.lang.Object)
373 *
374 */
375 @Override
376 public boolean equals(Object obj) {
377 if (obj == this){
378 return true;
379 }
380 if (obj == null){
381 return false;
382 }
383 if (!CdmBase.class.isAssignableFrom(obj.getClass())){
384 return false;
385 }
386 ICdmBase cdmObj = (ICdmBase)obj;
387 UUID objUuid = cdmObj.getUuid();
388 if (objUuid == null){
389 throw new NullPointerException("CdmBase is missing UUID");
390 }
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){
396 return false;
397 }
398 return true;
399 }
400
401
402
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.
407 */
408 @Override
409 public int hashCode() {
410 int hashCode = 7;
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();
421 return result;
422 } else {
423 return 29 * hashCode;
424 }
425 }
426
427 /**
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.
433 * <p>
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.
436 * <p>
437 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
438 * @see java.lang.Object#toString()
439 */
440 @Override
441 public String toString() {
442 return instanceToString();
443 }
444
445 /**
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}.
448 * <p>
449 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
450 * @return
451 */
452 public String instanceToString() {
453 return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
454 }
455
456 // **************** invoke methods **************************/
457
458 protected void invokeSetMethod(Method method, Object object){
459 try {
460 method.invoke(object, this);
461 } catch (Exception e) {
462 e.printStackTrace();
463 //TODO handle exceptioin;
464 }
465 }
466
467 protected void invokeSetMethodWithNull(Method method, Object object){
468 try {
469 Object[] nul = new Object[]{null};
470 method.invoke(object, nul);
471 } catch (Exception e) {
472 e.printStackTrace();
473 //TODO handle exceptioin;
474 }
475 }
476
477 @Transient
478 @Override
479 public String getUserFriendlyTypeName(){
480 return CdmUtils.userFriendlyClassName(getClass());
481 }
482
483 @Transient
484 @Override
485 public String getUserFriendlyDescription(){
486 return toString();
487 }
488
489 @Override
490 public String getUserFriendlyFieldName(String field){
491 return field;
492 }
493
494
495 /**
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.
498 */
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);
503 }
504 if (classToRemove != null){
505 result.remove(classToRemove);
506 }
507 return result;
508 }
509
510 // ********************* HELPER ****************************************/
511
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);
518 result = true;
519 }
520 }
521 return result;
522 }
523
524 /**
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
529 */
530 protected static boolean isBlank(String str) {
531 return StringUtils.isBlank(str);
532 }
533
534 /**
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
539 */
540 protected static boolean isNotBlank(String str) {
541 return StringUtils.isNotBlank(str);
542 }
543
544 // **************** EMPTY ************************/
545
546 /**
547 * Checks if the entity is completely empty
548 * and therefore can be removed.<BR>
549 *
550 * To be implemented by subclasses if used
551 *
552 * @return <code>true</code> if empty
553 */
554 protected boolean checkEmpty(){
555 //nothing to check; id, uuid, created and createdBy are not relevant
556 return true;
557 }
558
559 //********************** CLONE *****************************************/
560
561 // protected void clone(CdmBase clone){
562 // clone.setCreatedBy(createdBy);
563 // clone.setId(id);
564 // clone.propertyChangeSupport=new PropertyChangeSupport(clone);
565 // //Constructor Attributes
566 // //clone.setCreated(created);
567 // //clone.setUuid(getUuid());
568 //
569 // }
570
571 @Override
572 public CdmBase clone() throws CloneNotSupportedException{
573 CdmBase result = (CdmBase)super.clone();
574 result.propertyChangeSupport=new PropertyChangeSupport(result);
575
576 //TODO ?
577 result.setId(0);
578 result.setUuid(UUID.randomUUID());
579 result.setCreated(new DateTime());
580 result.setCreatedBy(null);
581
582 //no changes to: -
583 return result;
584 }
585
586 }