Project

General

Profile

Download (19.7 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.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.commons.lang3.StringUtils;
43
import org.apache.log4j.Logger;
44
import org.hibernate.annotations.NaturalId;
45
import org.hibernate.annotations.Type;
46
import org.hibernate.envers.Audited;
47
import org.hibernate.search.annotations.Analyze;
48
import org.hibernate.search.annotations.DocumentId;
49
import org.hibernate.search.annotations.Field;
50
import org.hibernate.search.annotations.FieldBridge;
51
import org.hibernate.search.annotations.Index;
52
import org.hibernate.search.annotations.Store;
53
import org.joda.time.DateTime;
54

    
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.model.permission.User;
63
import eu.etaxonomy.cdm.strategy.match.IMatchStrategyEqual;
64
import eu.etaxonomy.cdm.strategy.match.IMatchable;
65
import eu.etaxonomy.cdm.strategy.match.Match;
66
import eu.etaxonomy.cdm.strategy.match.MatchMode;
67

    
68

    
69

    
70

    
71
/**
72
 * The base class for all CDM domain classes implementing UUIDs and bean property change event firing.
73
 * It provides a globally unique UUID and keeps track of creation date and person.
74
 * The UUID is the same for different versions (see {@link VersionableEntity}) of a CDM object, so a locally unique id exists in addition
75
 * that allows to safely access and store several objects (=version) with the same UUID.
76
 *
77
 * This class together with the {@link eu.etaxonomy.cdm.aspectj.PropertyChangeAspect}
78
 * will fire bean change events to all registered listeners. Listener registration and event firing
79
 * is done with the help of the {@link PropertyChangeSupport} class.
80
 *
81
 * @author m.doering
82
 *
83
 */
84
@XmlAccessorType(XmlAccessType.FIELD)
85
@XmlType(name = "CdmBase", propOrder = {
86
    "created",
87
    "createdBy"
88
})
89
@MappedSuperclass
90
public abstract class CdmBase implements Serializable, ICdmBase, ISelfDescriptive, Cloneable{
91
    private static final long serialVersionUID = -3053225700018294809L;
92
    @SuppressWarnings("unused")
93
    private static final Logger logger = Logger.getLogger(CdmBase.class);
94

    
95
    protected static final int CLOB_LENGTH = 65536;
96

    
97
    @Transient
98
    @XmlTransient
99
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
100

    
101
    @Transient
102
    @XmlTransient
103
    private static NewEntityListener newEntityListener;
104

    
105
    //@XmlAttribute(name = "id", required = true)
106
    @XmlTransient
107
    @Id
108
//	@GeneratedValue(generator = "system-increment")  //see also AuditEvent.revisionNumber
109
//	@GeneratedValue(generator = "enhanced-table")
110
    @GeneratedValue(generator = "custom-enhanced-table")
111
    @DocumentId
112
    @FieldBridge(impl=NotNullAwareIdBridge.class)
113
    @Match(MatchMode.IGNORE)
114
    @NotNull
115
    @Min(0)
116
    @Audited
117
    private int id;
118

    
119
    @XmlAttribute(required = true)
120
    @XmlJavaTypeAdapter(UUIDAdapter.class)
121
    @XmlID
122
    @Type(type="uuidUserType")
123
    @NaturalId // This has the effect of placing a "unique" constraint on the database column
124
    @Column(length=36)  //TODO needed? Type UUID will always assure that is exactly 36
125
    @Match(MatchMode.IGNORE)
126
    @NotNull
127
    @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
128
    @FieldBridge(impl = UuidBridge.class)
129
    @Audited
130
    protected UUID uuid;
131

    
132
    @XmlElement (name = "Created", type= String.class)
133
    @XmlJavaTypeAdapter(DateTimeAdapter.class)
134
    @Type(type="dateTimeUserType")
135
    @Basic(fetch = FetchType.LAZY)
136
    @Match(MatchMode.IGNORE)
137
    @Field(analyze = Analyze.NO)
138
    @FieldBridge(impl = DateTimeBridge.class)
139
    @Audited
140
    private DateTime created;
141

    
142
    @XmlElement (name = "CreatedBy")
143
    @XmlIDREF
144
    @XmlSchemaType(name = "IDREF")
145
    @ManyToOne(fetch=FetchType.LAZY)
146
    @Match(MatchMode.IGNORE)
147
    @Audited
148
    private User createdBy;
149

    
150
    /**
151
     * Class constructor assigning a unique UUID and creation date.
152
     * UUID can be changed later via setUuid method.
153
     */
154
    public CdmBase() {
155
        this.uuid = UUID.randomUUID();
156
        this.created = new DateTime().withMillisOfSecond(0);
157
    }
158

    
159
    public static void setNewEntityListener(NewEntityListener nel) {
160
        newEntityListener = nel;
161
    }
162

    
163
    public static void fireOnCreateEvent(CdmBase cdmBase) {
164
        if(newEntityListener != null) {
165
            newEntityListener.onCreate(cdmBase);
166
        }
167
    }
168

    
169
    /**
170
     * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
171
     * @param listener
172
     */
173
    public void addPropertyChangeListener(PropertyChangeListener listener) {
174
        propertyChangeSupport.addPropertyChangeListener(listener);
175
    }
176

    
177
    /**
178
     * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
179
     */
180
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
181
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
182
    }
183

    
184
    /**
185
     * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
186
     */
187
    public void removePropertyChangeListener(PropertyChangeListener listener) {
188
        propertyChangeSupport.removePropertyChangeListener(listener);
189
    }
190

    
191
    /**
192
     * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
193
     */
194
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
195
        propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
196
    }
197

    
198
    public boolean hasListeners(String propertyName) {
199
        return propertyChangeSupport.hasListeners(propertyName);
200
    }
201

    
202
    public void firePropertyChange(String property, String oldval, String newval) {
203
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
204
    }
205
    public void firePropertyChange(String property, int oldval, int newval) {
206
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
207
    }
208
    public void firePropertyChange(String property, float oldval, float newval) {
209
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
210
    }
211
    public void firePropertyChange(String property, boolean oldval, boolean newval) {
212
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
213
    }
214
    public void firePropertyChange(String property, Object oldval, Object newval) {
215
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
216
    }
217
    public void firePropertyChange(PropertyChangeEvent evt) {
218
        propertyChangeSupport.firePropertyChange(evt);
219
    }
220

    
221
    /**
222
     * This method was initially added to {@link CdmBase} to fix #5161.
223
     * It can be overridden by subclasses such as {@link IdentifiableEntity}
224
     * to explicitly initialize listeners. This is needed e.g. after de-serialization
225
     * as listeners are not serialized due to the @Transient annotation.
226
     * However, it can be generally used for other use-cases as well
227
     */
228
    public void initListener() {}
229

    
230
    /**
231
     * Adds an item to a set of <code>this</code> object and fires the according
232
     * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
233
     * implemented in aspectJ.
234
     * @param set the set the new item is added to
235
     * @param newItem the new item to be added to the set
236
     * @param propertyName the name of the set as property in <code>this</code> object
237
     */
238
    protected <T extends CdmBase> void addToSetWithChangeEvent(Set<T> set, T newItem, String propertyName ){
239
        Set<T> oldValue = new HashSet<>(set);
240
        set.add(newItem);
241
        firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
242
    }
243

    
244
    /**
245
     * Removes an item from a set of <code>this</code> object and fires the according
246
     * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
247
     * implemented in aspectJ.
248
     * @param set the set the item is to be removed from
249
     * @param itemToRemove the item to be removed from the set
250
     * @param propertyName the name of the set as property in <code>this</code> object
251
     */
252
    protected <T extends CdmBase> void removeFromSetWithChangeEvent(Set<T> set, T itemToRemove, String propertyName ){
253
        Set<T> oldValue = new HashSet<T>(set);
254
        set.remove(itemToRemove);
255
        firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
256
    }
257

    
258
    @Override
259
    public UUID getUuid() {
260
        return uuid;
261
    }
262
    @Override
263
    public void setUuid(UUID uuid) {
264
        this.uuid = uuid;
265
    }
266

    
267
    @Override
268
    public int getId() {
269
        return this.id;
270
    }
271
    @Override
272
    public void setId(int id) {  //see #265 (private ?)
273
        this.id = id;
274
    }
275

    
276
    @Override
277
    public DateTime getCreated() {
278
        return created;
279
    }
280
    @Override
281
    public void setCreated(DateTime created) {
282
        if (created != null){
283
            created = created.withMillisOfSecond(0);
284
            //created.set(Calendar.MILLISECOND, 0);  //old, can be deleted
285
        }
286
        this.created = created;
287
    }
288

    
289

    
290
    @Override
291
    public User getCreatedBy() {
292
        return this.createdBy;
293
    }
294
    @Override
295
    public void setCreatedBy(User createdBy) {
296
        this.createdBy = createdBy;
297
    }
298

    
299
// ************************** Hibernate proxies *******************/
300

    
301
    /**
302
     * If entity is a HibernateProxy it returns the initialized object.
303
     * Otherwise entity itself is returned.
304
     * @param entity
305
     * @return
306
     * @throws ClassCastException
307
     */
308
    public static <T> T deproxy(T entity) {
309
        return HibernateProxyHelper.deproxy(entity);
310
    }
311

    
312
    /**
313
     * These methods are present due to HHH-1517 - that in a one-to-many
314
     * relationship with a superclass at the "one" end, the proxy created
315
     * by hibernate is the superclass, and not the subclass, resulting in
316
     * a ClassCastException when you try to cast it.
317
     *
318
     * Hopefully this will be resolved through improvements with the creation of
319
     * proxy objects by hibernate and the following methods will become redundant,
320
     * but for the time being . . .
321
     * @param <T>
322
     * @param object
323
     * @param clazz
324
     * @return
325
     * @throws ClassCastException
326
     */
327
    //non-static does not work because javassist already unwrapps the proxy before calling the method
328
     public static <T extends CdmBase> T deproxy(Object object, Class<T> clazz) throws ClassCastException {
329
         return HibernateProxyHelper.deproxy(object, clazz);
330
     }
331

    
332
     @Override
333
     public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
334
         return HibernateProxyHelper.isInstanceOf(this, clazz);
335
     }
336

    
337
    @Override
338
    @XmlTransient
339
    @Transient
340
    public boolean isPersited() {
341
        return id != 0;
342
    }
343

    
344
// ************* Object overrides *************************/
345

    
346
    /**
347
     * Is <code>true</code> if UUID and created timestamp (is this really needed/make sense?)
348
     * is the same for the passed Object and this one.
349
     * This method is final as subclasses should not override it.<BR>
350
     *
351
     * The contract should be the same for all persistable entities.
352
     * 2 instances are equal if they represent the same entity in a given
353
     * database.<BR>
354
     * NOTE: currently the method is only final in {@link VersionableEntity#equals(Object)}.
355
     * For discussion see #7202.
356
     * <BR><BR>
357
     *
358
     * If one wants to compare 2 CdmBase entities content wise you may use e.g. a
359
     * {@link IMatchStrategyEqual match strategy} and make sure
360
     * {@link IMatchable matching} is implemented for the respective CdmBase subclass.
361
     * You may adapt your match strategy to your own needs.
362
     *
363
     * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities},
364
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
365
     * for more information about equals and hashcode.
366
     * <BR>
367
     * See also https://dev.e-taxonomy.eu/redmine/issues/7155 and related tickets for discussion.
368
     *
369
     * @see java.lang.Object#equals(java.lang.Object)
370
     *
371
     */
372
    @Override
373
    public boolean equals(Object obj) {
374
        if (obj == this){
375
            return true;
376
        }
377
        if (obj == null){
378
            return false;
379
        }
380
        if (!CdmBase.class.isAssignableFrom(obj.getClass())){
381
            return false;
382
        }
383
        ICdmBase cdmObj = (ICdmBase)obj;
384
        UUID objUuid = cdmObj.getUuid();
385
        if (objUuid == null){
386
            throw new NullPointerException("CdmBase is missing UUID");
387
        }
388
        boolean uuidEqual = objUuid.equals(this.getUuid());
389
        //TODO is this still needed?
390
//        boolean createdEqual = CdmUtils.nullSafeEqual(cdmObj.getCreated(), this.getCreated());
391
        boolean createdEqual = true; //preliminary, to test im createdEqual is still needed #7201
392
        if (! uuidEqual || !createdEqual){
393
                return false;
394
        }
395
        return true;
396
    }
397

    
398

    
399

    
400
    /** Overrides {@link java.lang.Object#hashCode()}
401
     *  See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
402
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
403
     * for more information about equals and hashcode.
404
     */
405
    @Override
406
    public int hashCode() {
407
           int hashCode = 7;
408
           if(this.getUuid() != null) {
409
               //this unfortunately leads to errors when loading maps via hibernate
410
               //as hibernate computes hash values for CdmBase objects used as key at
411
               // a time when the uuid is not yet loaded from the database. Therefore
412
               //the hash values later change and give wrong results when retrieving
413
               //data from the map (map.get(key) returns null, though there is an entry
414
               //for key in the map.
415
               //see further comments in #2114
416
               int result = 29 * hashCode + this.getUuid().hashCode();
417
//		       int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
418
               return result;
419
           } else {
420
               return 29 * hashCode;
421
           }
422
    }
423

    
424
    /**
425
     * Overrides {@link java.lang.Object#toString()}.
426
     * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
427
     * call to {link {@link #instanceToString()}.<br>
428
     * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
429
     * without problems like lazy loading, unreal states etc.
430
     * <p>
431
     * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
432
     * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
433
     * <p>
434
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
435
     * @see java.lang.Object#toString()
436
     */
437
    @Override
438
    public String toString() {
439
        return instanceToString();
440
    }
441

    
442
    /**
443
     * This returns an String that identifies the cdm instance well without being necessarily unique.
444
     * The string representation combines the class name the {@link #id} and {@link #uuid}.
445
     * <p>
446
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
447
     * @return
448
     */
449
    public String instanceToString() {
450
        return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
451
    }
452

    
453
// **************** invoke methods **************************/
454

    
455
    protected void invokeSetMethod(Method method, Object object){
456
        try {
457
            method.invoke(object, this);
458
        } catch (Exception e) {
459
            e.printStackTrace();
460
            //TODO handle exceptioin;
461
        }
462
    }
463

    
464
    protected void invokeSetMethodWithNull(Method method, Object object){
465
        try {
466
            Object[] nul = new Object[]{null};
467
            method.invoke(object, nul);
468
        } catch (Exception e) {
469
            e.printStackTrace();
470
            //TODO handle exceptioin;
471
        }
472
    }
473

    
474
    @Transient
475
	@Override
476
	public String getUserFriendlyTypeName(){
477
		return getClass().getSimpleName();
478
	}
479

    
480
	@Transient
481
	@Override
482
	public String getUserFriendlyDescription(){
483
		return toString();
484
	}
485

    
486
	@Override
487
	public String getUserFriendlyFieldName(String field){
488
		return field;
489
	}
490

    
491
// ********************* HELPER ****************************************/
492

    
493
    protected <T extends CdmBase> boolean replaceInList(List<T> list,
494
            T newObject, T oldObject){
495
        boolean result = false;
496
        for (int i = 0; i < list.size(); i++){
497
            if (list.get(i).equals(oldObject)){
498
                list.set(i, newObject);
499
                result = true;
500
            }
501
        }
502
        return result;
503
    }
504

    
505
    /**
506
     * Returns <code>true</code> if the given String is blank.
507
     * @param str the String to check
508
     * @see StringUtils#isBlank(String)
509
     * @return <code>true</code> if str is blank, <code>false</code> otherwise
510
     */
511
    protected static boolean isBlank(String str) {
512
        return StringUtils.isBlank(str);
513
    }
514

    
515
    /**
516
     * Returns <code>true</code> if the given String is not blank.
517
     * @param str the String to check
518
     * @see StringUtils#isNotBlank(String)
519
     * @return <code>true</code> if str is not blank, <code>false</code> otherwise
520
     */
521
    protected static boolean isNotBlank(String str) {
522
        return StringUtils.isNotBlank(str);
523
    }
524

    
525
// **************** EMPTY ************************/
526

    
527
    protected boolean checkEmpty(){
528
       return true;
529
    }
530

    
531
//********************** CLONE *****************************************/
532

    
533
//    protected void clone(CdmBase clone){
534
//        clone.setCreatedBy(createdBy);
535
//        clone.setId(id);
536
//        clone.propertyChangeSupport=new PropertyChangeSupport(clone);
537
//        //Constructor Attributes
538
//        //clone.setCreated(created);
539
//        //clone.setUuid(getUuid());
540
//
541
//    }
542

    
543
    @Override
544
    public Object clone() throws CloneNotSupportedException{
545
        CdmBase result = (CdmBase)super.clone();
546
        result.propertyChangeSupport=new PropertyChangeSupport(result);
547

    
548
        //TODO ?
549
        result.setId(0);
550
        result.setUuid(UUID.randomUUID());
551
        result.setCreated(new DateTime());
552
        result.setCreatedBy(null);
553

    
554
        //no changes to: -
555
        return result;
556
    }
557

    
558
}
(6-6/60)