* AssociationExample criterion
[aramzamzam-commons.git] / aramzamzam-commons / commons-hibernate / src / main / java / net / aramzamzam / commons / hibernate / utils / AssociationExample.java
blob666923583d98652e932a724f9696f4b39ec5dffb
1 package net.aramzamzam.commons.hibernate.utils;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Set;
9 import org.hibernate.Criteria;
10 import org.hibernate.EntityMode;
11 import org.hibernate.HibernateException;
12 import org.hibernate.criterion.CriteriaQuery;
13 import org.hibernate.criterion.Criterion;
14 import org.hibernate.criterion.MatchMode;
15 import org.hibernate.criterion.Restrictions;
16 import org.hibernate.criterion.SimpleExpression;
17 import org.hibernate.engine.TypedValue;
18 import org.hibernate.persister.entity.EntityPersister;
19 import org.hibernate.type.AbstractComponentType;
20 import org.hibernate.type.Type;
21 import org.hibernate.util.StringHelper;
23 /**
24 * A copy of Hibernate's Example class, with modifications that allow you to
25 * include many-to-one and one-to-one associations in Query By Example (QBE)
26 * queries.
28 * @author original code by Gavin King, modified by jkelly
29 * @see <a href="http://forum.hibernate.org/viewtopic.php?t=942872">Example and
30 * association class </a>
32 public class AssociationExample implements Criterion {
33 private static final long serialVersionUID = 4909881342527136958L;
35 private final Object entity;
36 private final Set excludedProperties = new HashSet();
37 private PropertySelector selector;
38 private boolean isLikeEnabled;
39 private boolean isIgnoreCaseEnabled;
40 private MatchMode matchMode;
41 private boolean includeAssociations = true;
43 /**
44 * A strategy for choosing property values for inclusion in the query
45 * criteria
48 public static interface PropertySelector {
49 public boolean include(Object propertyValue, String propertyName,
50 Type type);
53 private static final PropertySelector NOT_NULL = new NotNullPropertySelector();
54 private static final PropertySelector ALL = new AllPropertySelector();
55 private static final PropertySelector NOT_NULL_OR_ZERO = new NotNullOrZeroPropertySelector();
57 static final class AllPropertySelector implements PropertySelector {
58 public boolean include(Object object, String propertyName, Type type) {
59 return true;
63 static final class NotNullPropertySelector implements PropertySelector {
64 public boolean include(Object object, String propertyName, Type type) {
65 return object != null;
69 static final class NotNullOrZeroPropertySelector implements
70 PropertySelector {
71 public boolean include(Object object, String propertyName, Type type) {
72 return object != null
73 && (!(object instanceof Number) || ((Number) object)
74 .longValue() != 0);
78 /**
79 * Set the property selector
81 public AssociationExample setPropertySelector(PropertySelector selector) {
82 this.selector = selector;
83 return this;
86 /**
87 * Exclude zero-valued properties
89 public AssociationExample excludeZeroes() {
90 setPropertySelector(NOT_NULL_OR_ZERO);
91 return this;
94 /**
95 * Don't exclude null or zero-valued properties
97 public AssociationExample excludeNone() {
98 setPropertySelector(ALL);
99 return this;
103 * Use the "like" operator for all string-valued properties
105 public AssociationExample enableLike(MatchMode matchMode) {
106 isLikeEnabled = true;
107 this.matchMode = matchMode;
108 return this;
112 * Use the "like" operator for all string-valued properties
114 public AssociationExample enableLike() {
115 return enableLike(MatchMode.EXACT);
119 * Ignore case for all string-valued properties
121 public AssociationExample ignoreCase() {
122 isIgnoreCaseEnabled = true;
123 return this;
127 * Exclude a particular named property
129 public AssociationExample excludeProperty(String name) {
130 excludedProperties.add(name);
131 return this;
135 * Create a new instance, which includes all non-null properties by default
137 * @param entity
138 * @return a new instance of <tt>Example</tt>
140 public static AssociationExample create(Object entity) {
141 if (entity == null)
142 throw new NullPointerException("null AssociationExample");
143 return new AssociationExample(entity, NOT_NULL);
146 protected AssociationExample(Object entity, PropertySelector selector) {
147 this.entity = entity;
148 this.selector = selector;
151 public String toString() {
152 return "example (" + entity + ')';
155 private boolean isPropertyIncluded(Object value, String name, Type type) {
156 return !excludedProperties.contains(name)
157 && selector.include(value, name, type)
158 && (!type.isAssociationType() || (type.isAssociationType()
159 && includeAssociations && !type.isCollectionType()));
162 public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery)
163 throws HibernateException {
165 StringBuffer buf = new StringBuffer().append('(');
166 EntityPersister meta = criteriaQuery.getFactory().getEntityPersister(
167 criteriaQuery.getEntityName(criteria));
168 List propertyNames = new ArrayList();
169 propertyNames.addAll(Arrays.asList(meta.getPropertyNames()));
171 List propertyTypes = new ArrayList();
172 propertyTypes.addAll(Arrays.asList(meta.getPropertyTypes()));
174 // TODO: get all properties, not just the fetched ones!
175 List propertyValues = new ArrayList();
176 propertyValues.addAll(Arrays.asList(meta.getPropertyValues(entity,
177 getEntityMode(criteria, criteriaQuery))));
178 if (meta.getIdentifierPropertyName() != null) {
179 propertyNames.add(meta.getIdentifierPropertyName());
180 propertyTypes.add(meta.getIdentifierType());
181 propertyValues.add(meta.getIdentifier(entity, getEntityMode(
182 criteria, criteriaQuery)));
185 for (int i = 0; i < propertyNames.size(); i++) {
186 Object propertyValue = propertyValues.get(i);
187 String propertyName = (String) propertyNames.get(i);
189 boolean isPropertyIncluded = i != meta.getVersionProperty()
190 && isPropertyIncluded(propertyValue, propertyName,
191 (Type) propertyTypes.get(i));
192 if (isPropertyIncluded) {
193 if (((Type) propertyTypes.get(i)).isComponentType()) {
194 appendComponentCondition(propertyName, propertyValue,
195 (AbstractComponentType) propertyTypes.get(i),
196 criteria, criteriaQuery, buf);
197 } else {
198 appendPropertyCondition(propertyName, propertyValue,
199 criteria, criteriaQuery, buf);
203 if (buf.length() == 1)
204 buf.append("1=1"); // yuck!
205 return buf.append(')').toString();
208 private static final Object[] TYPED_VALUES = new TypedValue[0];
210 public TypedValue[] getTypedValues(Criteria criteria,
211 CriteriaQuery criteriaQuery) throws HibernateException {
213 EntityPersister meta = criteriaQuery.getFactory().getEntityPersister(
214 criteriaQuery.getEntityName(criteria));
216 List propertyNames = new ArrayList();
217 propertyNames.addAll(Arrays.asList(meta.getPropertyNames()));
219 List propertyTypes = new ArrayList();
220 propertyTypes.addAll(Arrays.asList(meta.getPropertyTypes()));
222 // TODO: get all properties, not just the fetched ones!
223 List values = new ArrayList();
224 values.addAll(Arrays.asList(meta.getPropertyValues(entity,
225 getEntityMode(criteria, criteriaQuery))));
226 if (meta.getIdentifierPropertyName() != null) {
227 propertyNames.add(meta.getIdentifierPropertyName());
228 propertyTypes.add(meta.getIdentifierType());
229 values.add(meta.getIdentifier(entity, getEntityMode(criteria,
230 criteriaQuery)));
232 List list = new ArrayList();
233 for (int i = 0; i < propertyNames.size(); i++) {
234 Object value = values.get(i);
235 Type type = (Type) propertyTypes.get(i);
236 String name = (String) propertyNames.get(i);
238 boolean isPropertyIncluded = i != meta.getVersionProperty()
239 && isPropertyIncluded(value, name, type);
241 if (isPropertyIncluded) {
242 if (((Type) propertyTypes.get(i)).isComponentType()) {
243 addComponentTypedValues(name, value,
244 (AbstractComponentType) type, list, criteria,
245 criteriaQuery);
246 } else {
247 addPropertyTypedValue(value, type, list);
251 return (TypedValue[]) list.toArray(TYPED_VALUES);
254 private EntityMode getEntityMode(Criteria criteria,
255 CriteriaQuery criteriaQuery) {
256 EntityPersister meta = criteriaQuery.getFactory().getEntityPersister(
257 criteriaQuery.getEntityName(criteria));
258 EntityMode result = meta.guessEntityMode(entity);
259 if (result == null) {
260 throw new ClassCastException(entity.getClass().getName());
262 return result;
265 protected void addPropertyTypedValue(Object value, Type type, List list) {
266 if (value != null) {
267 if (value instanceof String) {
268 String string = (String) value;
269 if (isIgnoreCaseEnabled)
270 string = string.toLowerCase();
271 if (isLikeEnabled)
272 string = matchMode.toMatchString(string);
273 value = string;
275 list.add(new TypedValue(type, value, null));
279 protected void addComponentTypedValues(String path, Object component,
280 AbstractComponentType type, List list, Criteria criteria,
281 CriteriaQuery criteriaQuery) throws HibernateException {
283 if (component != null) {
284 String[] propertyNames = type.getPropertyNames();
285 Type[] subtypes = type.getSubtypes();
286 Object[] values = type.getPropertyValues(component, getEntityMode(
287 criteria, criteriaQuery));
288 for (int i = 0; i < propertyNames.length; i++) {
289 Object value = values[i];
290 Type subtype = subtypes[i];
291 String subpath = StringHelper.qualify(path, propertyNames[i]);
292 if (isPropertyIncluded(value, subpath, subtype)) {
293 if (subtype.isComponentType()) {
294 addComponentTypedValues(subpath, value,
295 (AbstractComponentType) subtype, list,
296 criteria, criteriaQuery);
297 } else {
298 addPropertyTypedValue(value, subtype, list);
305 protected void appendPropertyCondition(String propertyName,
306 Object propertyValue, Criteria criteria, CriteriaQuery cq,
307 StringBuffer buf) throws HibernateException {
308 Criterion crit;
309 if (propertyValue != null) {
310 boolean isString = propertyValue instanceof String;
311 SimpleExpression se = (isLikeEnabled && isString) ? Restrictions
312 .like(propertyName, propertyValue) : Restrictions.eq(
313 propertyName, propertyValue);
314 crit = (isIgnoreCaseEnabled && isString) ? se.ignoreCase() : se;
315 } else {
316 crit = Restrictions.isNull(propertyName);
318 String critCondition = crit.toSqlString(criteria, cq);
319 if (buf.length() > 1 && critCondition.trim().length() > 0)
320 buf.append(" and ");
321 buf.append(critCondition);
324 protected void appendComponentCondition(String path, Object component,
325 AbstractComponentType type, Criteria criteria,
326 CriteriaQuery criteriaQuery, StringBuffer buf)
327 throws HibernateException {
329 if (component != null) {
330 String[] propertyNames = type.getPropertyNames();
331 Object[] values = type.getPropertyValues(component, getEntityMode(
332 criteria, criteriaQuery));
333 Type[] subtypes = type.getSubtypes();
334 for (int i = 0; i < propertyNames.length; i++) {
335 String subpath = StringHelper.qualify(path, propertyNames[i]);
336 Object value = values[i];
337 if (isPropertyIncluded(value, subpath, subtypes[i])) {
338 Type subtype = subtypes[i];
339 if (subtype.isComponentType()) {
340 appendComponentCondition(subpath, value,
341 (AbstractComponentType) subtype, criteria,
342 criteriaQuery, buf);
343 } else {
344 appendPropertyCondition(subpath, value, criteria,
345 criteriaQuery, buf);
352 public boolean isIncludeAssociations() {
353 return includeAssociations;
356 public void setIncludeAssociations(boolean includeAssociations) {
357 this.includeAssociations = includeAssociations;