merge-update from trunk
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / CdmBase.java
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.UUID;
17
18 import javax.persistence.Basic;
19 import javax.persistence.Column;
20 import javax.persistence.FetchType;
21 import javax.persistence.GeneratedValue;
22 import javax.persistence.Id;
23 import javax.persistence.ManyToOne;
24 import javax.persistence.MappedSuperclass;
25 import javax.persistence.Transient;
26 import javax.validation.constraints.Min;
27 import javax.validation.constraints.NotNull;
28 import javax.xml.bind.annotation.XmlAccessType;
29 import javax.xml.bind.annotation.XmlAccessorType;
30 import javax.xml.bind.annotation.XmlAttribute;
31 import javax.xml.bind.annotation.XmlElement;
32 import javax.xml.bind.annotation.XmlID;
33 import javax.xml.bind.annotation.XmlIDREF;
34 import javax.xml.bind.annotation.XmlSchemaType;
35 import javax.xml.bind.annotation.XmlTransient;
36 import javax.xml.bind.annotation.XmlType;
37 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
38
39 import org.apache.log4j.Logger;
40 import org.hibernate.annotations.NaturalId;
41 import org.hibernate.annotations.Type;
42 import org.hibernate.envers.Audited;
43 import org.hibernate.search.annotations.Analyze;
44 import org.hibernate.search.annotations.DocumentId;
45 import org.hibernate.search.annotations.Field;
46 import org.hibernate.search.annotations.FieldBridge;
47 import org.hibernate.search.annotations.Index;
48 import org.hibernate.search.annotations.Store;
49 import org.hibernate.search.annotations.TermVector;
50 import org.joda.time.DateTime;
51
52 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
53 import eu.etaxonomy.cdm.hibernate.search.DateTimeBridge;
54 import eu.etaxonomy.cdm.hibernate.search.NotNullAwareIdBridge;
55 import eu.etaxonomy.cdm.hibernate.search.UuidBridge;
56 import eu.etaxonomy.cdm.jaxb.DateTimeAdapter;
57 import eu.etaxonomy.cdm.jaxb.UUIDAdapter;
58 import eu.etaxonomy.cdm.strategy.match.Match;
59 import eu.etaxonomy.cdm.strategy.match.MatchMode;
60
61
62
63
64 /**
65 * The base class for all CDM domain classes implementing UUIDs and bean property change event firing.
66 * It provides a globally unique UUID and keeps track of creation date and person.
67 * The UUID is the same for different versions (see {@link VersionableEntity}) of a CDM object, so a locally unique id exists in addition
68 * that allows to safely access and store several objects (=version) with the same UUID.
69 *
70 * This class together with the {@link eu.etaxonomy.cdm.aspectj.PropertyChangeAspect}
71 * will fire bean change events to all registered listeners. Listener registration and event firing
72 * is done with the help of the {@link PropertyChangeSupport} class.
73 *
74 * @author m.doering
75 *
76 */
77 @XmlAccessorType(XmlAccessType.FIELD)
78 @XmlType(name = "CdmBase", propOrder = {
79 "created",
80 "createdBy"
81 })
82 @MappedSuperclass
83 public abstract class CdmBase implements Serializable, ICdmBase, Cloneable{
84 private static final long serialVersionUID = -3053225700018294809L;
85 @SuppressWarnings("unused")
86 private static final Logger logger = Logger.getLogger(CdmBase.class);
87
88 @Transient
89 @XmlTransient
90 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
91
92 //@XmlAttribute(name = "id", required = true)
93 @XmlTransient
94 @Id
95 // @GeneratedValue(generator = "system-increment")
96 // @GeneratedValue(generator = "enhanced-table")
97 @GeneratedValue(generator = "custom-enhanced-table")
98 @DocumentId
99 @FieldBridge(impl=NotNullAwareIdBridge.class)
100 @Field(store=Store.YES, termVector=TermVector.NO)
101 @Match(MatchMode.IGNORE)
102 @NotNull
103 @Min(0)
104 @Audited
105 private int id;
106
107 @XmlAttribute(required = true)
108 @XmlJavaTypeAdapter(UUIDAdapter.class)
109 @XmlID
110 @Type(type="uuidUserType")
111 @NaturalId // This has the effect of placing a "unique" constraint on the database column
112 @Column(length=36) //TODO needed? Type UUID will always assure that is exactly 36
113 @Match(MatchMode.IGNORE)
114 @NotNull
115 @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
116 @FieldBridge(impl = UuidBridge.class)
117 @Audited
118 protected UUID uuid;
119
120 @XmlElement (name = "Created", type= String.class)
121 @XmlJavaTypeAdapter(DateTimeAdapter.class)
122 @Type(type="dateTimeUserType")
123 @Basic(fetch = FetchType.LAZY)
124 @Match(MatchMode.IGNORE)
125 @Field(analyze = Analyze.NO)
126 @FieldBridge(impl = DateTimeBridge.class)
127 @Audited
128 private DateTime created;
129
130 @XmlElement (name = "CreatedBy")
131 @XmlIDREF
132 @XmlSchemaType(name = "IDREF")
133 @ManyToOne(fetch=FetchType.LAZY)
134 @Match(MatchMode.IGNORE)
135 @Audited
136 private User createdBy;
137
138 /**
139 * Class constructor assigning a unique UUID and creation date.
140 * UUID can be changed later via setUuid method.
141 */
142 public CdmBase() {
143 this.uuid = UUID.randomUUID();
144 this.created = new DateTime().withMillisOfSecond(0);
145 }
146
147 /**
148 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
149 * @param listener
150 */
151 public void addPropertyChangeListener(PropertyChangeListener listener) {
152 propertyChangeSupport.addPropertyChangeListener(listener);
153 }
154
155 /**
156 * see {@link PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)}
157 */
158 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
159 propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
160 }
161
162 /**
163 * see {@link PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener)}
164 */
165 public void removePropertyChangeListener(PropertyChangeListener listener) {
166 propertyChangeSupport.removePropertyChangeListener(listener);
167 }
168
169 /**
170 * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener)
171 */
172 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
173 propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
174 }
175
176 public boolean hasListeners(String propertyName) {
177 return propertyChangeSupport.hasListeners(propertyName);
178 }
179
180 public void firePropertyChange(String property, String oldval, String newval) {
181 propertyChangeSupport.firePropertyChange(property, oldval, newval);
182 }
183 public void firePropertyChange(String property, int oldval, int newval) {
184 propertyChangeSupport.firePropertyChange(property, oldval, newval);
185 }
186 public void firePropertyChange(String property, float oldval, float newval) {
187 propertyChangeSupport.firePropertyChange(property, oldval, newval);
188 }
189 public void firePropertyChange(String property, boolean oldval, boolean newval) {
190 propertyChangeSupport.firePropertyChange(property, oldval, newval);
191 }
192 public void firePropertyChange(String property, Object oldval, Object newval) {
193 propertyChangeSupport.firePropertyChange(property, oldval, newval);
194 }
195 public void firePropertyChange(PropertyChangeEvent evt) {
196 propertyChangeSupport.firePropertyChange(evt);
197 }
198
199 @Override
200 public UUID getUuid() {
201 return uuid;
202 }
203 @Override
204 public void setUuid(UUID uuid) {
205 this.uuid = uuid;
206 }
207
208 @Override
209 public int getId() {
210 return this.id;
211 }
212 @Override
213 public void setId(int id) { //see #265 (private ?)
214 this.id = id;
215 }
216
217 @Override
218 public DateTime getCreated() {
219 return created;
220 }
221 @Override
222 public void setCreated(DateTime created) {
223 if (created != null){
224 new DateTime();
225 created = created.withMillisOfSecond(0);
226 //created.set(Calendar.MILLISECOND, 0); //old, can be deleted
227 }
228 this.created = created;
229 }
230
231
232 @Override
233 public User getCreatedBy() {
234 return this.createdBy;
235 }
236 @Override
237 public void setCreatedBy(User createdBy) {
238 this.createdBy = createdBy;
239 }
240
241 // ************************** Hibernate proxies *******************/
242 /**
243 * These methods are present due to HHH-1517 - that in a one-to-many
244 * relationship with a superclass at the "one" end, the proxy created
245 * by hibernate is the superclass, and not the subclass, resulting in
246 * a classcastexception when you try to cast it.
247 *
248 * Hopefully this will be resolved through improvements with the creation of
249 * proxy objects by hibernate and the following methods will become redundant,
250 * but for the time being . . .
251 * @param <T>
252 * @param object
253 * @param clazz
254 * @return
255 * @throws ClassCastException
256 */
257 //non-static does not work because javassist already unwrapps the proxy before calling the method
258 public static <T extends CdmBase> T deproxy(Object object, Class<T> clazz) throws ClassCastException {
259 return HibernateProxyHelper.deproxy(object, clazz);
260 }
261
262 public boolean isInstanceOf(Class<? extends CdmBase> clazz) throws ClassCastException {
263 return HibernateProxyHelper.isInstanceOf(this, clazz);
264 }
265
266 // ************* Object overrides *************************/
267
268 /**
269 * Is true if UUID is the same for the passed Object and this one.
270 * @see java.lang.Object#equals(java.lang.Object)
271 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
272 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
273 * for more information about equals and hashcode.
274 */
275 @Override
276 public boolean equals(Object obj) {
277 if (obj == this){
278 return true;
279 }
280 if (obj == null){
281 return false;
282 }
283 if (!CdmBase.class.isAssignableFrom(obj.getClass())){
284 return false;
285 }
286 ICdmBase cdmObj = (ICdmBase)obj;
287 boolean uuidEqual = cdmObj.getUuid().equals(this.getUuid());
288 boolean createdEqual = cdmObj.getCreated().equals(this.getCreated());
289 if (! uuidEqual || !createdEqual){
290 return false;
291 }
292 return true;
293 }
294
295
296 /** Overrides {@link java.lang.Object#hashCode()}
297 * See {@link http://www.hibernate.org/109.html hibernate109}, {@link http://www.geocities.com/technofundo/tech/java/equalhash.html geocities}
298 * or {@link http://www.ibm.com/developerworks/java/library/j-jtp05273.html ibm}
299 * for more information about equals and hashcode.
300 */
301 @Override
302 public int hashCode() {
303 int hashCode = 7;
304 if(this.getUuid() != null) {
305 //this unfortunately leads to errors when loading maps via hibernate
306 //as hibernate computes hash values for CdmBase objects used as key at
307 // a time when the uuid is not yet loaded from the database. Therefore
308 //the hash values later change and give wrong results when retrieving
309 //data from the map (map.get(key) returns null, though there is an entry
310 //for key in the map.
311 //see further comments in #2114
312 int result = 29 * hashCode + this.getUuid().hashCode();
313 // int shresult = 29 * hashCode + Integer.valueOf(this.getId()).hashCode();
314 return result;
315 } else {
316 return 29 * hashCode;
317 }
318 }
319
320 /**
321 * Overrides {@link java.lang.Object#toString()}.
322 * This returns an String that identifies the object well without being necessarily unique. Internally the method is delegating the
323 * call to {link {@link #instanceToString()}.<br>
324 * <b>Specification:</b> This method should never call other object' methods so it can be well used for debugging
325 * without problems like lazy loading, unreal states etc.
326 * <p>
327 * <b>Note</b>: If overriding this method's javadoc always copy or link the above requirement.
328 * If not overwritten by a subclass method returns the class, id and uuid as a string for any CDM object.
329 * <p>
330 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
331 * @see java.lang.Object#toString()
332 */
333 @Override
334 public String toString() {
335 return instanceToString();
336 }
337
338 /**
339 * This returns an String that identifies the cdm instacne well without being necessarily unique.
340 * The string representation combines the class name the {@link #id} and {@link #uuid}.
341 * <p>
342 * <b>For example</b>: Taxon#13&lt;b5938a98-c1de-4dda-b040-d5cc5bfb3bc0&gt;
343 * @return
344 */
345 public String instanceToString() {
346 return this.getClass().getSimpleName()+"#"+this.getId()+"<"+this.getUuid()+">";
347 }
348
349 // **************** invoke methods **************************/
350
351 protected void invokeSetMethod(Method method, Object object){
352 try {
353 method.invoke(object, this);
354 } catch (Exception e) {
355 e.printStackTrace();
356 //TODO handle exceptioin;
357 }
358 }
359
360 protected void invokeSetMethodWithNull(Method method, Object object){
361 try {
362 Object[] nul = new Object[]{null};
363 method.invoke(object, nul);
364 } catch (Exception e) {
365 e.printStackTrace();
366 //TODO handle exceptioin;
367 }
368 }
369
370 //********************** CLONE *****************************************/
371
372 // protected void clone(CdmBase clone){
373 // clone.setCreatedBy(createdBy);
374 // clone.setId(id);
375 // clone.propertyChangeSupport=new PropertyChangeSupport(clone);
376 // //Constructor Attributes
377 // //clone.setCreated(created);
378 // //clone.setUuid(getUuid());
379 //
380 // }
381
382 @Override
383 public Object clone() throws CloneNotSupportedException{
384 CdmBase result = (CdmBase)super.clone();
385 result.propertyChangeSupport=new PropertyChangeSupport(result);
386
387 //TODO ?
388 result.setId(0);
389 result.setUuid(UUID.randomUUID());
390 result.setCreated(new DateTime());
391 result.setCreatedBy(null);
392
393 //no changes to: -
394 return result;
395 }
396
397 }