Project

General

Profile

Download (20.8 KB) Statistics
| Branch: | Tag: | Revision:
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.logging.log4j.LogManager;import org.apache.logging.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 = LogManager.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
//*************************************************************/
478

    
479
    @Transient
480
	@Override
481
	public String getUserFriendlyTypeName(){
482
        return CdmUtils.userFriendlyClassName(getClass());
483
	}
484

    
485
	@Transient
486
	@Override
487
	public String getUserFriendlyDescription(){
488
		return toString();
489
	}
490

    
491
	@Override
492
	public String getUserFriendlyFieldName(String field){
493
		return field;
494
	}
495

    
496

    
497
    /**
498
     * EnumSets being part of the model should be immutable to make hibernate know if they have been changed.
499
     * Therefore any change to the enum set should result in a new enum set.
500
     */
501
    protected <T extends Enum<T>> EnumSet<T> newEnumSet(@NotNull EnumSet<T> enumSet, T additionalClass, T classToRemove) {
502
        EnumSet<T> result = EnumSet.copyOf(enumSet);
503
        if (additionalClass != null){
504
            result.add(additionalClass);
505
        }
506
        if (classToRemove != null){
507
            result.remove(classToRemove);
508
        }
509
        return result;
510
    }
511

    
512
// ********************* HELPER ****************************************/
513

    
514
    protected <T extends CdmBase> boolean replaceInList(List<T> list,
515
            T newObject, T oldObject){
516
        boolean result = false;
517
        for (int i = 0; i < list.size(); i++){
518
            if (list.get(i).equals(oldObject)){
519
                list.set(i, newObject);
520
                result = true;
521
            }
522
        }
523
        return result;
524
    }
525

    
526
    /**
527
     * Returns <code>true</code> if the given String is blank.
528
     * @param str the String to check
529
     * @see StringUtils#isBlank(String)
530
     * @return <code>true</code> if str is blank, <code>false</code> otherwise
531
     */
532
    protected static boolean isBlank(String str) {
533
        return StringUtils.isBlank(str);
534
    }
535

    
536
    /**
537
     * Returns <code>true</code> if the given String is not blank.
538
     * @param str the String to check
539
     * @see StringUtils#isNotBlank(String)
540
     * @return <code>true</code> if str is not blank, <code>false</code> otherwise
541
     */
542
    protected static boolean isNotBlank(String str) {
543
        return StringUtils.isNotBlank(str);
544
    }
545

    
546
// **************** EMPTY ************************/
547

    
548
    /**
549
     * Checks if the entity is completely empty
550
     * and therefore can be removed.<BR>
551
     *
552
     * To be implemented by subclasses if used
553
     *
554
     * @return <code>true</code> if empty
555
     */
556
    protected boolean checkEmpty(){
557
        //nothing to check; id, uuid, created and createdBy are not relevant
558
        return true;
559
    }
560

    
561
//********************** CLONE *****************************************/
562

    
563
//    protected void clone(CdmBase clone){
564
//        clone.setCreatedBy(createdBy);
565
//        clone.setId(id);
566
//        clone.propertyChangeSupport=new PropertyChangeSupport(clone);
567
//        //Constructor Attributes
568
//        //clone.setCreated(created);
569
//        //clone.setUuid(getUuid());
570
//
571
//    }
572

    
573
    @Override
574
    public CdmBase clone() throws CloneNotSupportedException{
575
        CdmBase result = (CdmBase)super.clone();
576
        result.propertyChangeSupport=new PropertyChangeSupport(result);
577

    
578
        //TODO ?
579
        result.setId(0);
580
        result.setUuid(UUID.randomUUID());
581
        result.setCreated(new DateTime());
582
        result.setCreatedBy(null);
583

    
584
        //no changes to: -
585
        return result;
586
    }
587

    
588
}
(7-7/58)