Project

General

Profile

Download (19.1 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.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.IMatchStrategyEqual;
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
    protected static final int CLOB_LENGTH = 65536;
95

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
288

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

    
298
// ************************** Hibernate proxies *******************/
299

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

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

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

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

    
343
// ************* Object overrides *************************/
344

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

    
397

    
398

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

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

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

    
452
// **************** invoke methods **************************/
453

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

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

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

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

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

    
490
// ********************* HELPER ****************************************/
491

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

    
504

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

    
515

    
516

    
517
//********************** CLONE *****************************************/
518

    
519
//    protected void clone(CdmBase clone){
520
//        clone.setCreatedBy(createdBy);
521
//        clone.setId(id);
522
//        clone.propertyChangeSupport=new PropertyChangeSupport(clone);
523
//        //Constructor Attributes
524
//        //clone.setCreated(created);
525
//        //clone.setUuid(getUuid());
526
//
527
//    }
528

    
529
    @Override
530
    public Object clone() throws CloneNotSupportedException{
531
        CdmBase result = (CdmBase)super.clone();
532
        result.propertyChangeSupport=new PropertyChangeSupport(result);
533

    
534
        //TODO ?
535
        result.setId(0);
536
        result.setUuid(UUID.randomUUID());
537
        result.setCreated(new DateTime());
538
        result.setCreatedBy(null);
539

    
540
        //no changes to: -
541
        return result;
542
    }
543

    
544
}
(5-5/81)