View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.api.mo;
17  
18  import org.apache.commons.lang.builder.EqualsBuilder;
19  import org.apache.commons.lang.builder.HashCodeBuilder;
20  import org.apache.commons.lang.builder.ToStringBuilder;
21  import org.kuali.rice.core.api.CoreConstants;
22  import org.kuali.rice.core.api.util.collect.CollectionUtils;
23  
24  import javax.xml.bind.Unmarshaller;
25  import javax.xml.bind.annotation.XmlTransient;
26  import java.io.IOException;
27  import java.io.ObjectInputStream;
28  import java.io.ObjectOutputStream;
29  import java.lang.reflect.Field;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Collections;
33  
34  /**
35   * All model object's that are Jaxb annotated should extend this class.
36   *
37   * This class does several important things:
38   * <ol>
39   *     <li>Defines jaxb callback method to ensure that Collection and Map types are unmarshalled into immutable empty forms rather than null values</li>
40   *     <li>Defines equals/hashcode/toString</li>
41   *
42   *     Note: the equals/hashCode implementation excludes {@value CoreConstants.CommonElements#FUTURE_ELEMENTS} field.
43   *     This element should be present on all jaxb annotated classes.
44   * </ol>
45   *
46   * <b>Important: all classes extending this class must be immutable</b>
47   */
48  @XmlTransient // marked as @XmlTransient so that an AbstractDataTransferObjectType is not included in all WSDL schemas
49  public abstract class AbstractDataTransferObject implements ModelObjectComplete {
50  
51      private transient volatile Integer _hashCode;
52      private transient volatile String _toString;
53  
54      protected AbstractDataTransferObject() {
55          super();
56      }
57  
58      /**
59       * compute or return the memoized hashcode for this immutable class, excluding the named fields.
60       * @param excludedFields the names of fields to exclude when computing the hashcode.
61       * @return the hashcode value
62       * @see #equalsExcludeFields(Object, java.util.Collection)
63       * @see #getDefaultHashCodeEqualsExcludeFields()
64       */
65      protected final int hashCodeExcludeFields(Collection<String> excludedFields) {
66          //using DCL idiom to cache hashCodes.  Hashcodes on immutable objects never change.  They can be safely cached.
67          //see effective java 2nd ed. pg. 71
68          Integer h = _hashCode;
69          if (h == null) {
70              synchronized (this) {
71                  h = _hashCode;
72                  if (h == null) {
73                      _hashCode = h = Integer.valueOf(HashCodeBuilder.reflectionHashCode(this, excludedFields));
74                  }
75              }
76          }
77  
78          return h.intValue();
79      }
80  
81      @Override
82      public int hashCode() {
83          return hashCodeExcludeFields(Constants.hashCodeEqualsExclude);
84      }
85  
86      /**
87       * Indicates whether the obj parameter is equal to this object.  Uses {@link org.apache.commons.lang.builder.EqualsBuilder#reflectionEquals(Object, Object, java.util.Collection)}
88       * and takes the names of fields to exclude from comparison.
89       * @param obj the other object to compare to
90       * @param excludedFields the names of fields to exclude when computing the hashcode.
91       * @return if equal
92       * @see #hashCodeExcludeFields(java.util.Collection)
93       * @see #getDefaultHashCodeEqualsExcludeFields()
94       */
95      protected final boolean equalsExcludeFields(Object obj, Collection<String> excludedFields) {
96          return EqualsBuilder.reflectionEquals(obj, this, excludedFields);
97      }
98  
99      @Override
100     public boolean equals(Object obj) {
101         return equalsExcludeFields(obj, Constants.hashCodeEqualsExclude);
102     }
103 
104     @Override
105     public String toString() {
106         //using DCL idiom to cache toString.  toStrings on immutable objects never change.  They can be safely cached.
107         //see effective java 2nd ed. pg. 71
108         String t = _toString;
109         if (t == null) {
110             synchronized (this) {
111                 t = _toString;
112                 if (t == null) {
113                     _toString = t = ToStringBuilder.reflectionToString(this);
114                 }
115             }
116         }
117 
118         return t;
119     }
120 
121     @SuppressWarnings("unused")
122     protected void beforeUnmarshal(Unmarshaller u, Object parent) throws Exception {
123     }
124 
125     @SuppressWarnings("unused")
126     protected void afterUnmarshal(Unmarshaller u, Object parent) throws Exception {
127         CollectionUtils.makeUnmodifiableAndNullSafe(this);
128     }
129 
130     private transient Object serializationMutex = new Object();
131 
132     private void writeObject(ObjectOutputStream out) throws IOException {
133         synchronized (serializationMutex) {
134             clearFutureElements();
135             out.defaultWriteObject();
136         }
137     }
138 
139     private void readObject(ObjectInputStream ois) throws IOException,
140             ClassNotFoundException {
141         ois.defaultReadObject();
142         serializationMutex = new Object();
143     }
144 
145     /**
146      * Looks for a field named "_futureElements" on the class and clears it's value if it exists.  This allows us to
147      * prevent from storing these values during serialization.
148      */
149     private void clearFutureElements() {
150         try {
151             Field futureElementsField = getClass().getDeclaredField(CoreConstants.CommonElements.FUTURE_ELEMENTS);
152             boolean originalAccessible = futureElementsField.isAccessible();
153             futureElementsField.setAccessible(true);
154             try {
155                 futureElementsField.set(this, null);
156             } finally {
157                 futureElementsField.setAccessible(originalAccessible);
158             }
159         } catch (NoSuchFieldException e) {
160             // if the field does not exist, don't do anything
161         } catch (IllegalAccessException e) {
162             // can't modify the field, ignore
163         }
164     }
165 
166 
167     /**
168      * Defines some internal constants used on this class.
169      */
170     protected static class Constants {
171         final static Collection<String> hashCodeEqualsExclude = Collections.unmodifiableCollection(
172                 Arrays.asList(CoreConstants.CommonElements.FUTURE_ELEMENTS, "_hashCode", "_toString")
173         );
174     }
175 
176     protected static final Collection<String> getDefaultHashCodeEqualsExcludeFields() {
177         return Constants.hashCodeEqualsExclude;
178     }
179 }