GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / commons / org / codehaus / groovy / grails / commons / GrailsDomainConfigurationUtil.java
blob4f99abf6d298c244799ab1927f8d588064625bb2
1 /* Copyright 2004-2005 the original author or authors.
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
15 package org.codehaus.groovy.grails.commons;
17 import groovy.lang.Closure;
18 import groovy.lang.GroovyObject;
19 import org.apache.commons.lang.StringUtils;
20 import org.codehaus.groovy.grails.commons.metaclass.DynamicMethods;
21 import org.codehaus.groovy.grails.commons.metaclass.GroovyDynamicMethodsInterceptor;
22 import org.codehaus.groovy.grails.exceptions.GrailsConfigurationException;
23 import org.codehaus.groovy.grails.validation.metaclass.ConstraintsEvaluatingDynamicProperty;
25 import java.beans.IntrospectionException;
26 import java.beans.PropertyDescriptor;
27 import java.io.Serializable;
28 import java.math.BigDecimal;
29 import java.math.BigInteger;
30 import java.net.URI;
31 import java.net.URL;
32 import java.sql.Blob;
33 import java.sql.Clob;
34 import java.sql.Time;
35 import java.sql.Timestamp;
36 import java.util.*;
37 import java.lang.reflect.Method;
38 import java.lang.reflect.InvocationTargetException;
40 /**
41 * Utility methods used in configuring the Grails Hibernate integration
43 * @author Graeme Rocher
44 * @since 18-Feb-2006
46 public class GrailsDomainConfigurationUtil {
49 private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
51 public static Serializable getAssociationIdentifier(Object target, String propertyName, GrailsDomainClass referencedDomainClass) {
52 String getterName = GrailsClassUtils.getGetterName(propertyName);
54 try {
55 Method m = target.getClass().getDeclaredMethod(getterName, EMPTY_CLASS_ARRAY);
56 Object value = m.invoke(target, null);
57 if(value != null && referencedDomainClass != null) {
58 String identifierGetter = GrailsClassUtils.getGetterName(referencedDomainClass.getIdentifier().getName());
59 m = value.getClass().getDeclaredMethod(identifierGetter, EMPTY_CLASS_ARRAY);
60 return (Serializable)m.invoke(value, null);
62 } catch (NoSuchMethodException e) {
63 // ignore
64 } catch (IllegalAccessException e) {
65 // ignore
66 } catch (InvocationTargetException e) {
67 // ignore
69 return null;
72 /**
73 * Configures the relationships between domain classes after they have been all loaded.
75 * @param domainClasses The domain classes to configure relationships for
76 * @param domainMap The domain class map
78 public static void configureDomainClassRelationships(GrailsClass[] domainClasses, Map domainMap) {
80 // configure super/sub class relationships
81 // and configure how domain class properties reference each other
82 for (int i = 0; i < domainClasses.length; i++) {
83 GrailsDomainClass domainClass = (GrailsDomainClass) domainClasses[i];
84 if (!domainClass.isRoot()) {
85 Class superClass = domainClasses[i].getClazz().getSuperclass();
86 while (!superClass.equals(Object.class) && !superClass.equals(GroovyObject.class)) {
87 GrailsDomainClass gdc = (GrailsDomainClass) domainMap.get(superClass.getName());
88 if (gdc == null || gdc.getSubClasses() == null)
89 break;
91 gdc.getSubClasses().add(domainClasses[i]);
92 superClass = superClass.getSuperclass();
95 GrailsDomainClassProperty[] props = domainClass.getPersistentProperties();
97 for (int j = 0; j < props.length; j++) {
98 if (props[j] != null && props[j].isAssociation()) {
99 GrailsDomainClassProperty prop = props[j];
100 GrailsDomainClass referencedGrailsDomainClass = (GrailsDomainClass) domainMap.get(props[j].getReferencedPropertyType().getName());
101 prop.setReferencedDomainClass(referencedGrailsDomainClass);
108 // now configure so that the 'other side' of a property can be resolved by the property itself
109 for (int i = 0; i < domainClasses.length; i++) {
110 GrailsDomainClass domainClass = (GrailsDomainClass) domainClasses[i];
111 GrailsDomainClassProperty[] props = domainClass.getPersistentProperties();
113 for (int j = 0; j < props.length; j++) {
114 if (props[j] != null && props[j].isAssociation()) {
115 GrailsDomainClassProperty prop = props[j];
116 GrailsDomainClass referenced = prop.getReferencedDomainClass();
117 if (referenced != null) {
118 boolean isOwnedBy = referenced.isOwningClass(domainClass.getClazz());
119 prop.setOwningSide(isOwnedBy);
120 String refPropertyName = null;
121 try {
122 refPropertyName = prop.getReferencedPropertyName();
123 } catch (UnsupportedOperationException e) {
124 // ignore (to support Hibernate entities)
126 if (!StringUtils.isBlank(refPropertyName)) {
127 GrailsDomainClassProperty otherSide = referenced.getPropertyByName(refPropertyName);
128 prop.setOtherSide(otherSide);
129 otherSide.setOtherSide(prop);
130 } else {
131 GrailsDomainClassProperty[] referencedProperties = referenced.getPersistentProperties();
132 for (int k = 0; k < referencedProperties.length; k++) {
133 // for bi-directional circular dependencies we don't want the other side
134 // to be equal to self
135 GrailsDomainClassProperty referencedProp = referencedProperties[k];
136 if (prop.equals(referencedProp) && prop.isBidirectional())
137 continue;
138 if (isCandidateForOtherSide(domainClass, prop, referencedProp)) {
139 prop.setOtherSide(referencedProp);
140 break;
151 private static boolean isCandidateForOtherSide(GrailsDomainClass domainClass, GrailsDomainClassProperty prop, GrailsDomainClassProperty referencedProp) {
153 if (prop.equals(referencedProp)) return false;
154 if (prop.isOneToMany() && referencedProp.isOneToMany()) return false;
155 Class referencedPropertyType = referencedProp.getReferencedPropertyType();
156 if (referencedPropertyType == null || !referencedPropertyType.isAssignableFrom(domainClass.getClazz()))
157 return false;
158 Map mappedBy = domainClass.getMappedBy();
160 Object propertyMapping = mappedBy.get(prop.getName());
161 boolean mappedToDifferentProperty = propertyMapping != null && !propertyMapping.equals(referencedProp.getName());
163 mappedBy = referencedProp.getDomainClass().getMappedBy();
164 propertyMapping = mappedBy.get(referencedProp.getName());
165 boolean mappedFromDifferentProperty = propertyMapping != null && !propertyMapping.equals(prop.getName());
168 return !mappedToDifferentProperty && !mappedFromDifferentProperty;
172 * Returns the ORM frameworks mapping file name for the specified class name
174 * @param className The class name of the mapped file
175 * @return The mapping file name
177 public static String getMappingFileName(String className) {
178 String fileName = className.replaceAll("\\.", "/");
179 return fileName += ".hbm.xml";
183 * Returns the association map for the specified domain class
185 * @param domainClass the domain class
186 * @return The association map
188 public static Map getAssociationMap(Class domainClass) {
189 Map associationMap = (Map) GrailsClassUtils.getPropertyValueOfNewInstance(domainClass, GrailsDomainClassProperty.RELATES_TO_MANY, Map.class);
190 if (associationMap == null) {
191 associationMap = (Map) GrailsClassUtils.getPropertyValueOfNewInstance(domainClass, GrailsDomainClassProperty.HAS_MANY, Map.class);
192 if (associationMap == null) {
193 associationMap = Collections.EMPTY_MAP;
196 return associationMap;
200 * Retrieves the mappedBy map for the specified class
202 * @param domainClass The domain class
203 * @return The mappedBy map
205 public static Map getMappedByMap(Class domainClass) {
206 Map mappedByMap = (Map) GrailsClassUtils.getPropertyValueOfNewInstance(domainClass, GrailsDomainClassProperty.MAPPED_BY, Map.class);
207 if (mappedByMap == null) {
208 return Collections.EMPTY_MAP;
210 return mappedByMap;
214 * Establish whether its a basic type
216 * @param prop The domain class property
217 * @return True if it is basic
219 public static boolean isBasicType(GrailsDomainClassProperty prop) {
220 if (prop == null) return false;
221 Class propType = prop.getType();
222 return isBasicType(propType);
225 private static final Set BASIC_TYPES;
227 static {
228 Set basics = new HashSet();
229 basics.add(boolean.class.getName());
230 basics.add(long.class.getName());
231 basics.add(short.class.getName());
232 basics.add(int.class.getName());
233 basics.add(byte.class.getName());
234 basics.add(float.class.getName());
235 basics.add(double.class.getName());
236 basics.add(char.class.getName());
237 basics.add(Boolean.class.getName());
238 basics.add(Long.class.getName());
239 basics.add(Short.class.getName());
240 basics.add(Integer.class.getName());
241 basics.add(Byte.class.getName());
242 basics.add(Float.class.getName());
243 basics.add(Double.class.getName());
244 basics.add(Character.class.getName());
245 basics.add(String.class.getName());
246 basics.add(java.util.Date.class.getName());
247 basics.add(Time.class.getName());
248 basics.add(Timestamp.class.getName());
249 basics.add(java.sql.Date.class.getName());
250 basics.add(BigDecimal.class.getName());
251 basics.add(BigInteger.class.getName());
252 basics.add(Locale.class.getName());
253 basics.add(Calendar.class.getName());
254 basics.add(GregorianCalendar.class.getName());
255 basics.add(java.util.Currency.class.getName());
256 basics.add(TimeZone.class.getName());
257 basics.add(Object.class.getName());
258 basics.add(Class.class.getName());
259 basics.add(byte[].class.getName());
260 basics.add(Byte[].class.getName());
261 basics.add(char[].class.getName());
262 basics.add(Character[].class.getName());
263 basics.add(Blob.class.getName());
264 basics.add(Clob.class.getName());
265 basics.add(Serializable.class.getName());
266 basics.add(URI.class.getName());
267 basics.add(URL.class.getName());
269 BASIC_TYPES = Collections.unmodifiableSet(basics);
272 public static boolean isBasicType(Class propType) {
273 if (propType.isArray()) {
274 return isBasicType(propType.getComponentType());
276 return BASIC_TYPES.contains(propType.getName());
281 * Checks whether is property is configurational
283 * @param descriptor The descriptor
284 * @return True if it is configurational
286 public static boolean isNotConfigurational(PropertyDescriptor descriptor) {
287 return !descriptor.getName().equals(GrailsDomainClassProperty.META_CLASS) &&
288 !descriptor.getName().equals(GrailsDomainClassProperty.CLASS) &&
289 !descriptor.getName().equals(GrailsDomainClassProperty.TRANSIENT) &&
290 !descriptor.getName().equals(GrailsDomainClassProperty.RELATES_TO_MANY) &&
291 !descriptor.getName().equals(GrailsDomainClassProperty.HAS_MANY) &&
292 !descriptor.getName().equals(GrailsDomainClassProperty.EVANESCENT) &&
293 !descriptor.getName().equals(GrailsDomainClassProperty.CONSTRAINTS) &&
294 !descriptor.getName().equals(GrailsDomainClassProperty.MAPPING_STRATEGY) &&
295 !descriptor.getName().equals(GrailsDomainClassProperty.MAPPED_BY) &&
296 !descriptor.getName().equals(GrailsDomainClassProperty.BELONGS_TO);
300 * Evaluates the constraints closure to build the list of constraints
302 * @param instance The instance to evaluate constraints for
303 * @param properties The properties of the instance
304 * @return A Map of constraints
305 * @throws java.beans.IntrospectionException
306 * When the bean cannot be introspected
308 public static Map evaluateConstraints(Object instance, GrailsDomainClassProperty[] properties) throws IntrospectionException {
309 Closure constraintsClosure = (Closure) GrailsClassUtils.getPropertyOrStaticPropertyOrFieldValue(instance, GrailsDomainClassProperty.CONSTRAINTS);
310 Class objClass = instance.getClass();
311 String fullName = objClass.getName();
312 if (constraintsClosure != null && !GrailsClassUtils.isStaticProperty(objClass, GrailsDomainClassProperty.CONSTRAINTS)) {
313 throw new GrailsConfigurationException(
314 "Domain class [" + fullName + "] has non-static constraints. Constraints must be " +
315 "declared static.");
319 GroovyObject go = (GroovyObject) instance;
320 DynamicMethods interceptor = new GroovyDynamicMethodsInterceptor(go);
321 interceptor.addDynamicProperty(new ConstraintsEvaluatingDynamicProperty(properties));
323 return (Map) go.getProperty(GrailsDomainClassProperty.CONSTRAINTS);