Project

General

Profile

Download (17.4 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.hibernate.HibernateProxyHelper;
55
import eu.etaxonomy.cdm.hibernate.search.DateTimeBridge;
56
import eu.etaxonomy.cdm.hibernate.search.NotNullAwareIdBridge;
57
import eu.etaxonomy.cdm.hibernate.search.UuidBridge;
58
import eu.etaxonomy.cdm.jaxb.DateTimeAdapter;
59
import eu.etaxonomy.cdm.jaxb.UUIDAdapter;
60
import eu.etaxonomy.cdm.model.NewEntityListener;
61
import eu.etaxonomy.cdm.strategy.match.Match;
62
import eu.etaxonomy.cdm.strategy.match.MatchMode;
63

    
64

    
65

    
66

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

    
91
    @Transient
92
    @XmlTransient
93
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
94

    
95
    @Transient
96
    @XmlTransient
97
    private static NewEntityListener newEntityListener;
98

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

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

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

    
136
    @XmlElement (name = "CreatedBy")
137
    @XmlIDREF
138
    @XmlSchemaType(name = "IDREF")
139
    @ManyToOne(fetch=FetchType.LAZY)
140
    @Match(MatchMode.IGNORE)
141
    @Audited
142
    private User createdBy;
143

    
144
    /**
145
     * Class constructor assigning a unique UUID and creation date.
146
     * UUID can be changed later via setUuid method.
147
     */
148
    public CdmBase() {
149
        this.uuid = UUID.randomUUID();
150
        this.created = new DateTime().withMillisOfSecond(0);
151
    }
152

    
153
    public static void setNewEntityListener(NewEntityListener nel) {
154
        newEntityListener = nel;
155
    }
156

    
157
    public static void fireOnCreateEvent(CdmBase cdmBase) {
158
        if(newEntityListener != null) {
159
            newEntityListener.onCreate(cdmBase);
160
        }
161
    }
162

    
163
    /**
164
     * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
165
     * @param listener
166
     */
167
    public void addPropertyChangeListener(PropertyChangeListener listener) {
168
        propertyChangeSupport.addPropertyChangeListener(listener);
169
    }
170

    
171
    /**
172
     * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
173
     */
174
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
175
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
176
    }
177

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

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

    
192
    public boolean hasListeners(String propertyName) {
193
        return propertyChangeSupport.hasListeners(propertyName);
194
    }
195

    
196
    public void firePropertyChange(String property, String oldval, String newval) {
197
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
198
    }
199
    public void firePropertyChange(String property, int oldval, int newval) {
200
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
201
    }
202
    public void firePropertyChange(String property, float oldval, float newval) {
203
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
204
    }
205
    public void firePropertyChange(String property, boolean oldval, boolean newval) {
206
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
207
    }
208
    public void firePropertyChange(String property, Object oldval, Object newval) {
209
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
210
    }
211
    public void firePropertyChange(PropertyChangeEvent evt) {
212
        propertyChangeSupport.firePropertyChange(evt);
213
    }
214

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

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

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

    
252
    @Override
253
    public UUID getUuid() {
254
        return uuid;
255
    }
256
    @Override
257
    public void setUuid(UUID uuid) {
258
        this.uuid = uuid;
259
    }
260

    
261
    @Override
262
    public int getId() {
263
        return this.id;
264
    }
265
    @Override
266
    public void setId(int id) {  //see #265 (private ?)
267
        this.id = id;
268
    }
269

    
270
    @Override
271
    public DateTime getCreated() {
272
        return created;
273
    }
274
    @Override
275
    public void setCreated(DateTime created) {
276
        if (created != null){
277
            created = created.withMillisOfSecond(0);
278
            //created.set(Calendar.MILLISECOND, 0);  //old, can be deleted
279
        }
280
        this.created = created;
281
    }
282

    
283

    
284
    @Override
285
    public User getCreatedBy() {
286
        return this.createdBy;
287
    }
288
    @Override
289
    public void setCreatedBy(User createdBy) {
290
        this.createdBy = createdBy;
291
    }
292

    
293
// ************************** Hibernate proxies *******************/
294

    
295
    /**
296
     * If entity is a HibernateProxy it returns the initialized object.
297
     * Otherwise entity itself is returned.
298
     * @param entity
299
     * @return
300
     * @throws ClassCastException
301
     */
302
    public static <T> T deproxy(T entity) {
303
        return HibernateProxyHelper.deproxy(entity);
304
    }
305

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

    
326
     public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
327
         return HibernateProxyHelper.isInstanceOf(this, clazz);
328
     }
329

    
330
// ************* Object overrides *************************/
331

    
332
    /**
333
     * Is true if UUID is the same for the passed Object and this one.
334
     * @see java.lang.Object#equals(java.lang.Object)
335
     * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
336
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
337
     * for more information about equals and hashcode.
338
     */
339
    @Override
340
    public boolean equals(Object obj) {
341
        if (obj == this){
342
            return true;
343
        }
344
        if (obj == null){
345
            return false;
346
        }
347
        if (!CdmBase.class.isAssignableFrom(obj.getClass())){
348
            return false;
349
        }
350
        ICdmBase cdmObj = (ICdmBase)obj;
351
        boolean uuidEqual = cdmObj.getUuid().equals(this.getUuid());
352
        boolean createdEqual = cdmObj.getCreated().equals(this.getCreated());
353
        if (! uuidEqual || !createdEqual){
354
                return false;
355
        }
356
        return true;
357
    }
358

    
359

    
360
    /** Overrides {@link java.lang.Object#hashCode()}
361
     *  See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
362
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
363
     * for more information about equals and hashcode.
364
     */
365
    @Override
366
    public int hashCode() {
367
           int hashCode = 7;
368
           if(this.getUuid() != null) {
369
               //this unfortunately leads to errors when loading maps via hibernate
370
               //as hibernate computes hash values for CdmBase objects used as key at
371
               // a time when the uuid is not yet loaded from the database. Therefore
372
               //the hash values later change and give wrong results when retrieving
373
               //data from the map (map.get(key) returns null, though there is an entry
374
               //for key in the map.
375
               //see further comments in #2114
376
               int result = 29 * hashCode + this.getUuid().hashCode();
377
//		       int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
378
               return result;
379
           } else {
380
               return 29 * hashCode;
381
           }
382
    }
383

    
384
    /**
385
     * Overrides {@link java.lang.Object#toString()}.
386
     * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
387
     * call to {link {@link #instanceToString()}.<br>
388
     * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
389
     * without problems like lazy loading, unreal states etc.
390
     * <p>
391
     * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
392
     * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
393
     * <p>
394
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
395
     * @see java.lang.Object#toString()
396
     */
397
    @Override
398
    public String toString() {
399
        return instanceToString();
400
    }
401

    
402
    /**
403
     * This returns an String that identifies the cdm instacne well without being necessarily unique.
404
     * The string representation combines the class name the {@link #id} and {@link #uuid}.
405
     * <p>
406
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
407
     * @return
408
     */
409
    public String instanceToString() {
410
        return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
411
    }
412

    
413
// **************** invoke methods **************************/
414

    
415
    protected void invokeSetMethod(Method method, Object object){
416
        try {
417
            method.invoke(object, this);
418
        } catch (Exception e) {
419
            e.printStackTrace();
420
            //TODO handle exceptioin;
421
        }
422
    }
423

    
424
    protected void invokeSetMethodWithNull(Method method, Object object){
425
        try {
426
            Object[] nul = new Object[]{null};
427
            method.invoke(object, nul);
428
        } catch (Exception e) {
429
            e.printStackTrace();
430
            //TODO handle exceptioin;
431
        }
432
    }
433

    
434
    @Transient
435
	@Override
436
	public String getUserFriendlyTypeName(){
437
		return getClass().getSimpleName();
438
	}
439

    
440
	@Transient
441
	@Override
442
	public String getUserFriendlyDescription(){
443
		return toString();
444
	}
445

    
446
	@Override
447
	public String getUserFriendlyFieldName(String field){
448
		return field;
449
	}
450

    
451
// ********************* HELPER ****************************************/
452

    
453
    protected <T extends CdmBase> boolean replaceInList(List<T> list,
454
            T newObject, T oldObject){
455
        boolean result = false;
456
        for (int i = 0; i < list.size(); i++){
457
            if (list.get(i).equals(oldObject)){
458
                list.set(i, newObject);
459
                result = true;
460
            }
461
        }
462
        return result;
463
    }
464

    
465

    
466

    
467
//********************** CLONE *****************************************/
468

    
469
//    protected void clone(CdmBase clone){
470
//        clone.setCreatedBy(createdBy);
471
//        clone.setId(id);
472
//        clone.propertyChangeSupport=new PropertyChangeSupport(clone);
473
//        //Constructor Attributes
474
//        //clone.setCreated(created);
475
//        //clone.setUuid(getUuid());
476
//
477
//    }
478

    
479
    @Override
480
    public Object clone() throws CloneNotSupportedException{
481
        CdmBase result = (CdmBase)super.clone();
482
        result.propertyChangeSupport=new PropertyChangeSupport(result);
483

    
484
        //TODO ?
485
        result.setId(0);
486
        result.setUuid(UUID.randomUUID());
487
        result.setCreated(new DateTime());
488
        result.setCreatedBy(null);
489

    
490
        //no changes to: -
491
        return result;
492
    }
493

    
494
}
(4-4/72)