1 /* Copyright 2004-2005 the original author or authors.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 package org.codehaus.groovy.grails.orm.hibernate;
16 
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.LinkedHashMap;
21 import java.util.Map;
22 import java.util.Set;
23 
24 import org.codehaus.groovy.grails.commons.AbstractGrailsClass;
25 import org.codehaus.groovy.grails.commons.ExternalGrailsDomainClass;
26 import org.codehaus.groovy.grails.commons.GrailsApplication;
27 import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
28 import org.codehaus.groovy.grails.commons.GrailsDomainConfigurationUtil;
29 import org.codehaus.groovy.grails.exceptions.InvalidPropertyException;
30 import org.codehaus.groovy.grails.validation.GrailsDomainClassValidator;
31 import org.hibernate.EntityMode;
32 import org.hibernate.MappingException;
33 import org.hibernate.SessionFactory;
34 import org.hibernate.engine.SessionFactoryImplementor;
35 import org.hibernate.metadata.ClassMetadata;
36 import org.hibernate.type.AnyType;
37 import org.hibernate.type.AssociationType;
38 import org.hibernate.type.Type;
39 import org.springframework.context.MessageSource;
40 import org.springframework.core.type.StandardAnnotationMetadata;
41 import org.springframework.validation.Validator;
42 
43 /**
44  * An implementation of the GrailsDomainClass interface that allows Classes
45  * mapped in Hibernate to integrate with Grails' validation, dynamic methods
46  * etc. seamlessly.
47  *
48  * @author Graeme Rocher
49  * @since 0.1
50  */
51 @SuppressWarnings("rawtypes")
52 public class GrailsHibernateDomainClass extends AbstractGrailsClass implements ExternalGrailsDomainClass {
53 
54     private static final String HIBERNATE = "hibernate";
55 
56     private GrailsHibernateDomainClassProperty identifier;
57     private GrailsHibernateDomainClassProperty version;
58 
59     private GrailsDomainClassProperty[] properties;
60 
61     private Map<String, GrailsHibernateDomainClassProperty> propertyMap = new LinkedHashMap<String, GrailsHibernateDomainClassProperty>();
62 
63     private Validator validator;
64 
65     private GrailsApplication application;
66 
67     private Set subClasses = new HashSet();
68     private Map constraints = Collections.emptyMap();
69     private Map<String, Object> defaultConstraints = Collections.emptyMap();
70 
71     /**
72      * Contructor to be used by all child classes to create a new instance
73      * and get the name right.
74      *
75      * @param clazz          the Grails class
76      * @param sessionFactory The Hibernate SessionFactory instance
77      * @param metaData       The ClassMetaData for this class retrieved from the SF
78      * @param defaultConstraints The default global constraints definition
79      */
GrailsHibernateDomainClass(Class<?> clazz, SessionFactory sessionFactory, GrailsApplication application, ClassMetadata metaData, Map<String, Object> defaultConstraints)80     public GrailsHibernateDomainClass(Class<?> clazz, SessionFactory sessionFactory, GrailsApplication application,
81             ClassMetadata metaData, Map<String, Object> defaultConstraints) {
82         super(clazz, "");
83         this.application = application;
84 
85         new StandardAnnotationMetadata(clazz);
86         String ident = metaData.getIdentifierPropertyName();
87         this.defaultConstraints = defaultConstraints;
88         if (ident != null) {
89             Class<?> identType = getPropertyType(ident);
90             identifier = new GrailsHibernateDomainClassProperty(this, ident);
91             identifier.setIdentity(true);
92             identifier.setType(identType);
93             propertyMap.put(ident, identifier);
94         }
95 
96         // configure the version property
97         final int versionIndex = metaData.getVersionProperty();
98         String versionPropertyName = null;
99         if (versionIndex >- 1) {
100             versionPropertyName = metaData.getPropertyNames()[versionIndex];
101             version = new GrailsHibernateDomainClassProperty(this, versionPropertyName);
102             version.setType(getPropertyType(versionPropertyName));
103         }
104 
105         // configure remaining properties
106         String[] propertyNames = metaData.getPropertyNames();
107         for (String propertyName : propertyNames) {
108             if (!propertyName.equals(ident) && !(versionPropertyName != null &&
109                     propertyName.equals(versionPropertyName))) {
110                 GrailsHibernateDomainClassProperty prop = new GrailsHibernateDomainClassProperty(this, propertyName);
111                 prop.setType(getPropertyType(propertyName));
112                 Type hibernateType = metaData.getPropertyType(propertyName);
113 
114                 // if its an association type
115                 if (hibernateType.isAssociationType()) {
116                     prop.setAssociation(true);
117                     // get the associated type from the session factory and set it on the property
118                     AssociationType assType = (AssociationType) hibernateType;
119                     if (assType instanceof AnyType) {
120                         continue;
121                     }
122                     try {
123                         String associatedEntity = assType.getAssociatedEntityName((SessionFactoryImplementor) sessionFactory);
124                         ClassMetadata associatedMetaData = sessionFactory.getClassMetadata(associatedEntity);
125                         prop.setRelatedClassType(associatedMetaData.getMappedClass(EntityMode.POJO));
126                     }
127                     catch (MappingException me) {
128                         // other side must be a value object
129                         if (hibernateType.isCollectionType()) {
130                             prop.setRelatedClassType(Collection.class);
131                         }
132                     }
133                     // configure type of relationship
134                     if (hibernateType.isCollectionType()) {
135                         prop.setOneToMany(true);
136                     }
137                     else if (hibernateType.isEntityType()) {
138                         prop.setManyToOne(true);
139                         // might not really be true, but for our purposes this is ok
140                         prop.setOneToOne(true);
141                     }
142                 }
143                 propertyMap.put(propertyName, prop);
144             }
145         }
146 
147         properties = propertyMap.values().toArray(new GrailsDomainClassProperty[propertyMap.size()]);
148         // process the constraints
149         evaluateConstraints();
150     }
151 
152     /**
153      * Evaluates the constraints closure to build the list of constraints
154      * @param defaultContraints The default global constraints definition
155      */
evaluateConstraints()156     private void evaluateConstraints() {
157         Map existing = (Map) getPropertyOrStaticPropertyOrFieldValue(GrailsDomainClassProperty.CONSTRAINTS, Map.class);
158         if (existing == null) {
159             constraints = GrailsDomainConfigurationUtil.evaluateConstraints(
160                     getClazz(), getProperties(), defaultConstraints);
161         }
162         else {
163             constraints = existing;
164         }
165     }
166 
isOwningClass(Class domainClass)167     public boolean isOwningClass(Class domainClass) {
168         return false;
169     }
170 
getProperties()171     public GrailsDomainClassProperty[] getProperties() {
172         return properties;
173     }
174 
175     /**
176      * @deprecated
177      */
178     @Deprecated
getPersistantProperties()179     public GrailsDomainClassProperty[] getPersistantProperties() {
180         return properties;
181     }
182 
getPersistentProperties()183     public GrailsDomainClassProperty[] getPersistentProperties() {
184         return properties;
185     }
186 
getIdentifier()187     public GrailsDomainClassProperty getIdentifier() {
188         return identifier;
189     }
190 
getVersion()191     public GrailsDomainClassProperty getVersion() {
192         return version;
193     }
194 
getPropertyByName(String name)195     public GrailsDomainClassProperty getPropertyByName(String name) {
196         if (propertyMap.containsKey(name)) {
197             return propertyMap.get(name);
198         }
199 
200         throw new InvalidPropertyException("No property found for name ["+name+"] for class ["+getClazz()+"]");
201     }
202 
getFieldName(String propertyName)203     public String getFieldName(String propertyName) {
204         return getPropertyByName(propertyName).getFieldName();
205     }
206 
hasSubClasses()207     public boolean hasSubClasses() {
208         return false;
209     }
210 
getMappedBy()211     public Map getMappedBy() {
212         return Collections.emptyMap();
213     }
214 
hasPersistentProperty(String propertyName)215     public boolean hasPersistentProperty(String propertyName) {
216         for (GrailsDomainClassProperty persistantProperty : properties) {
217             if (persistantProperty.getName().equals(propertyName)) return true;
218         }
219         return false;
220     }
221 
setMappingStrategy(String strategy)222     public void setMappingStrategy(String strategy) {
223         // do nothing, read-only
224     }
225 
isOneToMany(String propertyName)226     public boolean isOneToMany(String propertyName) {
227         GrailsDomainClassProperty prop = getPropertyByName(propertyName);
228         return prop != null && prop.isOneToMany();
229     }
230 
isManyToOne(String propertyName)231     public boolean isManyToOne(String propertyName) {
232         GrailsDomainClassProperty prop = getPropertyByName(propertyName);
233         return prop != null && prop.isManyToOne();
234     }
235 
isBidirectional(String propertyName)236     public boolean isBidirectional(String propertyName) {
237         return false;
238     }
239 
getRelatedClassType(String propertyName)240     public Class<?> getRelatedClassType(String propertyName) {
241         GrailsDomainClassProperty prop = getPropertyByName(propertyName);
242         if (prop == null) {
243             return null;
244         }
245 
246         return prop.getReferencedPropertyType();
247     }
248 
getConstrainedProperties()249     public Map getConstrainedProperties() {
250         return constraints;
251     }
252 
getValidator()253     public Validator getValidator() {
254         if (validator == null) {
255             GrailsDomainClassValidator gdcv = new GrailsDomainClassValidator();
256             gdcv.setDomainClass(this);
257             MessageSource messageSource = application.getMainContext().getBean(MessageSource.class);
258             gdcv.setMessageSource(messageSource);
259             validator = gdcv;
260         }
261         return validator;
262     }
263 
setValidator(Validator validator)264     public void setValidator(Validator validator) {
265         this.validator = validator;
266     }
267 
getMappingStrategy()268     public String getMappingStrategy() {
269         return HIBERNATE;
270     }
271 
272     @SuppressWarnings("unchecked")
getSubClasses()273     public Set getSubClasses() {
274         return subClasses;
275     }
276 
refreshConstraints()277     public void refreshConstraints() {
278         evaluateConstraints();
279     }
280 
isRoot()281     public boolean isRoot() {
282         return getClazz().getSuperclass().equals(Object.class);
283     }
284 
getAssociationMap()285     public Map getAssociationMap() {
286         return Collections.emptyMap();
287     }
288 }
289