1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.engine;
26
27 import java.util.Iterator;
28
29 import org.hibernate.HibernateException;
30 import org.hibernate.PropertyValueException;
31 import org.hibernate.intercept.LazyPropertyInitializer;
32 import org.hibernate.persister.entity.EntityPersister;
33 import org.hibernate.type.AbstractComponentType;
34 import org.hibernate.type.CollectionType;
35 import org.hibernate.type.Type;
36
37 /**
38 * Implements the algorithm for validating property values
39 * for illegal null values
40 * @author Gavin King
41 */
42 public final class Nullability {
43
44 private final SessionImplementor session;
45
46 public Nullability(SessionImplementor session) {
47 this.session = session;
48 }
49 /**
50 * Check nullability of the class persister properties
51 *
52 * @param values entity properties
53 * @param persister class persister
54 * @param isUpdate wether it is intended to be updated or saved
55 * @throws org.hibernate.PropertyValueException Break the nullability of one property
56 * @throws HibernateException error while getting Component values
57 */
58 public void checkNullability(
59 final Object[] values,
60 final EntityPersister persister,
61 final boolean isUpdate)
62 throws PropertyValueException, HibernateException {
63
64 /*
65 * Algorithm
66 * Check for any level one nullability breaks
67 * Look at non null components to
68 * recursively check next level of nullability breaks
69 * Look at Collections contraining component to
70 * recursively check next level of nullability breaks
71 *
72 *
73 * In the previous implementation, not-null stuffs where checked
74 * filtering by level one only updateable
75 * or insertable columns. So setting a sub component as update="false"
76 * has no effect on not-null check if the main component had good checkeability
77 * In this implementation, we keep this feature.
78 * However, I never see any documentation mentioning that, but it's for
79 * sure a limitation.
80 */
81
82 final boolean[] nullability = persister.getPropertyNullability();
83 final boolean[] checkability = isUpdate ?
84 persister.getPropertyUpdateability() :
85 persister.getPropertyInsertability();
86 final Type[] propertyTypes = persister.getPropertyTypes();
87
88 for ( int i = 0; i < values.length; i++ ) {
89
90 if ( checkability[i] && values[i]!=LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
91 final Object value = values[i];
92 if ( !nullability[i] && value == null ) {
93
94 //check basic level one nullablilty
95 throw new PropertyValueException(
96 "not-null property references a null or transient value",
97 persister.getEntityName(),
98 persister.getPropertyNames()[i]
99 );
100
101 }
102 else if ( value != null ) {
103
104 //values is not null and is checkable, we'll look deeper
105 String breakProperties = checkSubElementsNullability( propertyTypes[i], value );
106 if ( breakProperties != null ) {
107 throw new PropertyValueException(
108 "not-null property references a null or transient value",
109 persister.getEntityName(),
110 buildPropertyPath( persister.getPropertyNames()[i], breakProperties )
111 );
112 }
113
114 }
115 }
116
117 }
118 }
119
120 /**
121 * check sub elements-nullability. Returns property path that break
122 * nullability or null if none
123 *
124 * @param propertyType type to check
125 * @param value value to check
126 *
127 * @return property path
128 * @throws HibernateException error while getting subcomponent values
129 */
130 private String checkSubElementsNullability(final Type propertyType, final Object value)
131 throws HibernateException {
132 //for non null args, check for components and elements containing components
133 if ( propertyType.isComponentType() ) {
134 return checkComponentNullability( value, (AbstractComponentType) propertyType );
135 }
136 else if ( propertyType.isCollectionType() ) {
137
138 //persistent collections may have components
139 CollectionType collectionType = (CollectionType) propertyType;
140 Type collectionElementType = collectionType.getElementType( session.getFactory() );
141 if ( collectionElementType.isComponentType() ) {
142 //check for all components values in the collection
143
144 AbstractComponentType componentType = (AbstractComponentType) collectionElementType;
145 Iterator iter = CascadingAction.getLoadedElementsIterator(session, collectionType, value);
146 while ( iter.hasNext() ) {
147 Object compValue = iter.next();
148 if (compValue != null) {
149 return checkComponentNullability(compValue, componentType);
150 }
151 }
152 }
153 }
154 return null;
155 }
156
157 /**
158 * check component nullability. Returns property path that break
159 * nullability or null if none
160 *
161 * @param value component properties
162 * @param compType component not-nullable type
163 *
164 * @return property path
165 * @throws HibernateException error while getting subcomponent values
166 */
167 private String checkComponentNullability(final Object value, final AbstractComponentType compType)
168 throws HibernateException {
169 /* will check current level if some of them are not null
170 * or sublevels if they exist
171 */
172 boolean[] nullability = compType.getPropertyNullability();
173 if ( nullability!=null ) {
174 //do the test
175 final Object[] values = compType.getPropertyValues( value, session.getEntityMode() );
176 final Type[] propertyTypes = compType.getSubtypes();
177 for ( int i=0; i<values.length; i++ ) {
178 final Object subvalue = values[i];
179 if ( !nullability[i] && subvalue==null ) {
180 return compType.getPropertyNames()[i];
181 }
182 else if ( subvalue != null ) {
183 String breakProperties = checkSubElementsNullability( propertyTypes[i], subvalue );
184 if ( breakProperties != null ) {
185 return buildPropertyPath( compType.getPropertyNames()[i], breakProperties );
186 }
187 }
188 }
189 }
190 return null;
191 }
192
193 /**
194 * Return a well formed property path.
195 * Basicaly, it will return parent.child
196 *
197 * @param parent parent in path
198 * @param child child in path
199 * @return parent-child path
200 */
201 private static String buildPropertyPath(String parent, String child) {
202 return new StringBuffer( parent.length() + child.length() + 1 )
203 .append(parent).append('.').append(child).toString();
204 }
205
206 }