Project

General

Profile

Download (15.9 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.Set;
18
import java.util.UUID;
19

    
20
import javax.persistence.Basic;
21
import javax.persistence.Column;
22
import javax.persistence.FetchType;
23
import javax.persistence.GeneratedValue;
24
import javax.persistence.Id;
25
import javax.persistence.ManyToOne;
26
import javax.persistence.MappedSuperclass;
27
import javax.persistence.Transient;
28
import javax.validation.constraints.Min;
29
import javax.validation.constraints.NotNull;
30
import javax.xml.bind.annotation.XmlAccessType;
31
import javax.xml.bind.annotation.XmlAccessorType;
32
import javax.xml.bind.annotation.XmlAttribute;
33
import javax.xml.bind.annotation.XmlElement;
34
import javax.xml.bind.annotation.XmlID;
35
import javax.xml.bind.annotation.XmlIDREF;
36
import javax.xml.bind.annotation.XmlSchemaType;
37
import javax.xml.bind.annotation.XmlTransient;
38
import javax.xml.bind.annotation.XmlType;
39
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
40

    
41
import org.apache.log4j.Logger;
42
import org.hibernate.annotations.NaturalId;
43
import org.hibernate.annotations.Type;
44
import org.hibernate.envers.Audited;
45
import org.hibernate.search.annotations.Analyze;
46
import org.hibernate.search.annotations.DocumentId;
47
import org.hibernate.search.annotations.Field;
48
import org.hibernate.search.annotations.FieldBridge;
49
import org.hibernate.search.annotations.Index;
50
import org.hibernate.search.annotations.Store;
51
import org.hibernate.search.annotations.TermVector;
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.strategy.match.Match;
61
import eu.etaxonomy.cdm.strategy.match.MatchMode;
62

    
63

    
64

    
65

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

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

    
94
    //@XmlAttribute(name = "id", required = true)
95
    @XmlTransient
96
    @Id
97
//	@GeneratedValue(generator = "system-increment")
98
//	@GeneratedValue(generator = "enhanced-table")
99
    @GeneratedValue(generator = "custom-enhanced-table")
100
    @DocumentId
101
    @FieldBridge(impl=NotNullAwareIdBridge.class)
102
    @Field(store=Store.YES, termVector=TermVector.NO)
103
    @Match(MatchMode.IGNORE)
104
    @NotNull
105
    @Min(0)
106
    @Audited
107
    private int id;
108

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

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

    
132
    @XmlElement (name = "CreatedBy")
133
    @XmlIDREF
134
    @XmlSchemaType(name = "IDREF")
135
    @ManyToOne(fetch=FetchType.LAZY)
136
    @Match(MatchMode.IGNORE)
137
    @Audited
138
    private User createdBy;
139

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

    
149
    /**
150
     * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
151
     * @param listener
152
     */
153
    public void addPropertyChangeListener(PropertyChangeListener listener) {
154
        propertyChangeSupport.addPropertyChangeListener(listener);
155
    }
156

    
157
    /**
158
     * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
159
     */
160
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
161
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
162
    }
163

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

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

    
178
    public boolean hasListeners(String propertyName) {
179
        return propertyChangeSupport.hasListeners(propertyName);
180
    }
181

    
182
    public void firePropertyChange(String property, String oldval, String newval) {
183
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
184
    }
185
    public void firePropertyChange(String property, int oldval, int newval) {
186
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
187
    }
188
    public void firePropertyChange(String property, float oldval, float newval) {
189
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
190
    }
191
    public void firePropertyChange(String property, boolean oldval, boolean newval) {
192
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
193
    }
194
    public void firePropertyChange(String property, Object oldval, Object newval) {
195
        propertyChangeSupport.firePropertyChange(property, oldval, newval);
196
    }
197
    public void firePropertyChange(PropertyChangeEvent evt) {
198
        propertyChangeSupport.firePropertyChange(evt);
199
    }
200

    
201
    /**
202
     * Adds an item to a set of <code>this</code> object and fires the according
203
     * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
204
     * implemented in aspectJ.
205
     * @param set the set the new item is added to
206
     * @param newItem the new item to be added to the set
207
     * @param propertyName the name of the set as property in <code>this</code> object
208
     */
209
    protected <T extends CdmBase> void addToSetWithChangeEvent(Set<T> set, T newItem, String propertyName ){
210
        Set<T> oldValue = new HashSet<T>(set);
211
        set.add(newItem);
212
        firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
213
    }
214

    
215
    /**
216
     * Removes an item from a set of <code>this</code> object and fires the according
217
     * {@link PropertyChangeEvent}. Workaround as long as add and remove is not yet
218
     * implemented in aspectJ.
219
     * @param set the set the item is to be removed from
220
     * @param itemToRemove the item to be removed from the set
221
     * @param propertyName the name of the set as property in <code>this</code> object
222
     */
223
    protected <T extends CdmBase> void removeFromSetWithChangeEvent(Set<T> set, T itemToRemove, String propertyName ){
224
        Set<T> oldValue = new HashSet<T>(set);
225
        set.remove(itemToRemove);
226
        firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, set));
227
    }
228

    
229
    @Override
230
    public UUID getUuid() {
231
        return uuid;
232
    }
233
    @Override
234
    public void setUuid(UUID uuid) {
235
        this.uuid = uuid;
236
    }
237

    
238
    @Override
239
    public int getId() {
240
        return this.id;
241
    }
242
    @Override
243
    public void setId(int id) {  //see #265 (private ?)
244
        this.id = id;
245
    }
246

    
247
    @Override
248
    public DateTime getCreated() {
249
        return created;
250
    }
251
    @Override
252
    public void setCreated(DateTime created) {
253
        if (created != null){
254
            new DateTime();
255
            created = created.withMillisOfSecond(0);
256
            //created.set(Calendar.MILLISECOND, 0);  //old, can be deleted
257
        }
258
        this.created = created;
259
    }
260

    
261

    
262
    @Override
263
    public User getCreatedBy() {
264
        return this.createdBy;
265
    }
266
    @Override
267
    public void setCreatedBy(User createdBy) {
268
        this.createdBy = createdBy;
269
    }
270

    
271
// ************************** Hibernate proxies *******************/
272
    /**
273
     * These methods are present due to HHH-1517 - that in a one-to-many
274
     * relationship with a superclass at the "one" end, the proxy created
275
     * by hibernate is the superclass, and not the subclass, resulting in
276
     * a classcastexception when you try to cast it.
277
     *
278
     * Hopefully this will be resolved through improvements with the creation of
279
     * proxy objects by hibernate and the following methods will become redundant,
280
     * but for the time being . . .
281
     * @param <T>
282
     * @param object
283
     * @param clazz
284
     * @return
285
     * @throws ClassCastException
286
     */
287
    //non-static does not work because javassist already unwrapps the proxy before calling the method
288
     public static <T extends CdmBase> T deproxy(Object object, Class<T> clazz) throws ClassCastException {
289
         return HibernateProxyHelper.deproxy(object, clazz);
290
     }
291

    
292
     public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
293
         return HibernateProxyHelper.isInstanceOf(this, clazz);
294
     }
295

    
296
// ************* Object overrides *************************/
297

    
298
    /**
299
     * Is true if UUID is the same for the passed Object and this one.
300
     * @see java.lang.Object#equals(java.lang.Object)
301
     * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
302
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
303
     * for more information about equals and hashcode.
304
     */
305
    @Override
306
    public boolean equals(Object obj) {
307
        if (obj == this){
308
            return true;
309
        }
310
        if (obj == null){
311
            return false;
312
        }
313
        if (!CdmBase.class.isAssignableFrom(obj.getClass())){
314
            return false;
315
        }
316
        ICdmBase cdmObj = (ICdmBase)obj;
317
        boolean uuidEqual = cdmObj.getUuid().equals(this.getUuid());
318
        boolean createdEqual = cdmObj.getCreated().equals(this.getCreated());
319
        if (! uuidEqual || !createdEqual){
320
                return false;
321
        }
322
        return true;
323
    }
324

    
325

    
326
    /** Overrides {@link java.lang.Object#hashCode()}
327
     *  See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
328
     * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
329
     * for more information about equals and hashcode.
330
     */
331
    @Override
332
    public int hashCode() {
333
           int hashCode = 7;
334
           if(this.getUuid() != null) {
335
               //this unfortunately leads to errors when loading maps via hibernate
336
               //as hibernate computes hash values for CdmBase objects used as key at
337
               // a time when the uuid is not yet loaded from the database. Therefore
338
               //the hash values later change and give wrong results when retrieving
339
               //data from the map (map.get(key) returns null, though there is an entry
340
               //for key in the map.
341
               //see further comments in #2114
342
               int result = 29 * hashCode + this.getUuid().hashCode();
343
//		       int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
344
               return result;
345
           } else {
346
               return 29 * hashCode;
347
           }
348
    }
349

    
350
    /**
351
     * Overrides {@link java.lang.Object#toString()}.
352
     * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
353
     * call to {link {@link #instanceToString()}.<br>
354
     * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
355
     * without problems like lazy loading, unreal states etc.
356
     * <p>
357
     * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
358
     * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
359
     * <p>
360
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
361
     * @see java.lang.Object#toString()
362
     */
363
    @Override
364
    public String toString() {
365
        return instanceToString();
366
    }
367

    
368
    /**
369
     * This returns an String that identifies the cdm instacne well without being necessarily unique.
370
     * The string representation combines the class name the {@link #id} and {@link #uuid}.
371
     * <p>
372
     * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
373
     * @return
374
     */
375
    public String instanceToString() {
376
        return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
377
    }
378

    
379
// **************** invoke methods **************************/
380

    
381
    protected void invokeSetMethod(Method method, Object object){
382
        try {
383
            method.invoke(object, this);
384
        } catch (Exception e) {
385
            e.printStackTrace();
386
            //TODO handle exceptioin;
387
        }
388
    }
389

    
390
    protected void invokeSetMethodWithNull(Method method, Object object){
391
        try {
392
            Object[] nul = new Object[]{null};
393
            method.invoke(object, nul);
394
        } catch (Exception e) {
395
            e.printStackTrace();
396
            //TODO handle exceptioin;
397
        }
398
    }
399

    
400
    @Transient
401
	@Override
402
	public String getUserFriendlyTypeName(){
403
		return getClass().getSimpleName();
404
	}
405

    
406
	@Transient
407
	@Override
408
	public String getUserFriendlyDescription(){
409
		return toString();
410
	}
411

    
412
	@Override
413
	public String getUserFriendlyFieldName(String field){
414
		return field;
415
	}
416

    
417
//********************** CLONE *****************************************/
418

    
419
//    protected void clone(CdmBase clone){
420
//        clone.setCreatedBy(createdBy);
421
//        clone.setId(id);
422
//        clone.propertyChangeSupport=new PropertyChangeSupport(clone);
423
//        //Constructor Attributes
424
//        //clone.setCreated(created);
425
//        //clone.setUuid(getUuid());
426
//
427
//    }
428

    
429
    @Override
430
    public Object clone() throws CloneNotSupportedException{
431
        CdmBase result = (CdmBase)super.clone();
432
        result.propertyChangeSupport=new PropertyChangeSupport(result);
433

    
434
        //TODO ?
435
        result.setId(0);
436
        result.setUuid(UUID.randomUUID());
437
        result.setCreated(new DateTime());
438
        result.setCreatedBy(null);
439

    
440
        //no changes to: -
441
        return result;
442
    }
443

    
444
}
(4-4/72)