View Javadoc

1   /*
2    * The SmartWeb Framework
3    * Copyright (C) 2004-2006
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   *
19   * For further informations on the SmartWeb Framework please visit
20   *
21   *                        http://smartweb.sourceforge.net
22   */
23  package net.smartlab.web;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Locale;
29  import java.util.StringTokenizer;
30  
31  import net.smartlab.web.bean.ConversionException;
32  import net.smartlab.web.bean.ConverterManager;
33  
34  import org.apache.commons.lang.builder.EqualsBuilder;
35  import org.apache.commons.lang.builder.HashCodeBuilder;
36  import org.apache.commons.lang.builder.ToStringBuilder;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  /**
41   * This interface should be implemented by classes which provide some form of
42   * persistence.
43   * 
44   * @author rlogiacco,gperrone
45   * @uml.dependency supplier="net.smartlab.web.DAOException"
46   */
47  public interface DataAccessObject {
48  
49  	/**
50  	 * Retrieves from the persistence tier the object which primary key equals
51  	 * the one specified.
52  	 * 
53  	 * @param key the primary key used to search the instance into the
54  	 *        persistence tier.
55  	 * @return an object representing the datas stored in the persistence tier
56  	 *         associated with the specified key.
57  	 * @throws DAOException if an error occur while accessing the persistence
58  	 *         tier.
59  	 * @throws UndefinedKeyException if the specified primary key is not present
60  	 *         on the persistence tier.
61  	 */
62  	public Object findByKey(Serializable key) throws DAOException;
63  
64  	/**
65  	 * Permanently deletes an instance from the persistence tier.
66  	 * 
67  	 * @param object the instance representing the informations to be deleted
68  	 *        from the store.
69  	 * @throws DAOException if an error occur while accessing the persistence
70  	 *         tier.
71  	 */
72  	public void remove(Object object) throws DAOException;
73  
74  	/**
75  	 * Ensures the persistence tier representation of the object is consistent
76  	 * with the in memory representation. If the object doesn't exist yet into
77  	 * the persistence tier it must be added and a new primary key assigned to
78  	 * the object.
79  	 * 
80  	 * @param object the object to be persisted.
81  	 * @throws DAOException if an error occur while accessing the persistence
82  	 *         tier
83  	 */
84  	public void update(Object object) throws DAOException;
85  
86  	/**
87  	 * Returns a collection of objects representing all the persistence tier
88  	 * informations matching the specified search criterias.
89  	 * 
90  	 * @param info the criterias to be used to search the persistence tier.
91  	 * @return a collection of matching entities.
92  	 * @throws DAOException if an error occur while accessing the persistence
93  	 *         tier
94  	 */
95  	public Collection list(SearchInfo info) throws DAOException;
96  
97  
98  	/**
99  	 * Instances of this class represents a set of criterias to be used in
100 	 * persistence tier searches. This class is providen as a support class used
101 	 * to define search criterias using request parameters and should not be
102 	 * used in place of Hibernate's <code>Criteria</code> or <code>Query</code>
103 	 * as it doesn't provide the same customizability or features.
104 	 * 
105 	 * @author rlogiacco
106 	 */
107 	public static class SearchInfo implements Serializable {
108 
109 		private static final long serialVersionUID = 1038393869915276007L;
110 
111 		private static final Log logger = LogFactory.getLog(SearchInfo.class);
112 
113 		/**
114 		 * Entity properties to be filtered.
115 		 * 
116 		 * @uml.property name="filters"
117 		 */
118 		private Collection filters = new ArrayList();
119 
120 		/**
121 		 * Property to be used for ordering.
122 		 * 
123 		 * @uml.property name="order"
124 		 */
125 		private String order = null;
126 
127 		/**
128 		 * The ordering direction.
129 		 * 
130 		 * @uml.property name="descendant"
131 		 */
132 		private boolean descendant = false;
133 
134 		/**
135 		 * The filtering style.
136 		 * 
137 		 * @uml.property name="union"
138 		 */
139 		private boolean union = false;
140 
141 		/**
142 		 * The locale used to convert filters.
143 		 */
144 		private Locale locale = Locale.getDefault();
145 
146 		/**
147 		 * Identifies a <code>greater</code> expression condition.
148 		 */
149 		public final static int EQUALS = 1;
150 
151 		/**
152 		 * Identifies an <code>equals</code> expression condition.
153 		 */
154 		public final static int GREATER = 2;
155 
156 		/**
157 		 * Identifies a <code>greater</code> expression condition.
158 		 */
159 		public final static int GREATER_EQUALS = 3;
160 
161 		/**
162 		 * Identifies a <code>greater equals</code> expression condition.
163 		 */
164 		public final static int LESSER = 4;
165 
166 		/**
167 		 * Identifies a <code>lesser</code> expression condition.
168 		 */
169 		public final static int LESSER_EQUALS = 5;
170 
171 		/**
172 		 * Identifies a <code>lesser equal</code> expression condition.
173 		 */
174 		public final static int NOT_EQUALS = -1;
175 
176 		/**
177 		 * Identifies a <code>like</code> expression condition.
178 		 */
179 		public final static int LIKE = 6;
180 
181 		/**
182 		 * Identifies an <code>ilike</code> expression condition.
183 		 */
184 		public final static int ILIKE = 7;
185 
186 		/**
187 		 * Identifies a <code>between</code> expression condition.
188 		 */
189 		public final static int BETWEEN = 8;
190 
191 		/**
192 		 * Identifies an <code>is null</code> expression condition.
193 		 */
194 		public final static int NULL = 9;
195 
196 		/**
197 		 * Identifies an <code>is not null</code> expression condition.
198 		 */
199 		public final static int NOT_NULL = -9;
200 
201 		/**
202 		 * Identifies an <code>in</code> expression condition.
203 		 */
204 		public final static int IN = 11;
205 
206 		/**
207 		 * Identifies a <code>not in</code> expression condition.
208 		 */
209 		public final static int NOT_IN = -11;
210 
211 
212 		/**
213 		 * @return returns the ordering direction.
214 		 * @uml.property name="descendant"
215 		 */
216 		public boolean isDescendant() {
217 			return descendant;
218 		}
219 
220 		/**
221 		 * @return returns the filters.
222 		 * @uml.property name="filters"
223 		 */
224 		public Collection getFilters() {
225 			return filters;
226 		}
227 
228 		/**
229 		 * @param filters the filters to set.
230 		 * @uml.property name="filters"
231 		 */
232 		public void setFilters(Collection filters) {
233 			this.filters = filters;
234 		}
235 
236 		/**
237 		 * @param filters the filters to set.
238 		 */
239 		public void setFilters(String[] filters) {
240 			if (filters != null) {
241 				for (int i = 0; i < filters.length; i++) {
242 					this.addFilter(filters[i]);
243 				}
244 			}
245 		}
246 
247 		/**
248 		 * Adds a filter.
249 		 * 
250 		 * @param filter the filter to add.
251 		 */
252 		public void addFilter(String filter) {
253 			try {
254 				for (int i = 0; i < filter.length(); i++) {
255 					switch (filter.charAt(i)) {
256 							// Not equals / Not in
257 						case '!':
258 							if (filter.charAt(i + 1) == '|') {
259 								filters.add(new Filter(filter.substring(0, i), NOT_IN, filter.substring(i + 2)));
260 							} else {
261 								filters.add(new Filter(filter.substring(0, i), NOT_EQUALS, filter.substring(i + 1)));
262 							}
263 							return;
264 							// Equal
265 						case '=':
266 							filters.add(new Filter(filter.substring(0, i), EQUALS, filter.substring(i + 1)));
267 							return;
268 							// Greater
269 						case '>':
270 							// Greater equal
271 							if (filter.charAt(i + 1) == '=') {
272 								filters
273 										.add(new Filter(filter.substring(0, i), GREATER_EQUALS, filter.substring(i + 2)));
274 							} else {
275 								filters.add(new Filter(filter.substring(0, i), GREATER, filter.substring(i + 1)));
276 							}
277 							return;
278 							// Lesser
279 						case '<':
280 							// Lesser equal
281 							if (filter.charAt(i + 1) == '=') {
282 								filters.add(new Filter(filter.substring(0, i), LESSER_EQUALS, filter.substring(i + 2)));
283 							} else {
284 								filters.add(new Filter(filter.substring(0, i), LESSER, filter.substring(i + 1)));
285 							}
286 							return;
287 							// Between / In
288 						case '|':
289 							if (filter.charAt(i + 1) == '|') {
290 								filters.add(new Filter(filter.substring(0, i), IN, filter.substring(i + 2)));
291 							} else {
292 								filters.add(new Filter(filter.substring(0, i), BETWEEN, filter.substring(i + 1)));
293 							}
294 							return;
295 							// Like / ILike
296 						case '%':
297 							if (filter.charAt(i + 1) == '%') {
298 								// ILike
299 								filters.add(new Filter(filter.substring(0, i), ILIKE, filter.substring(i + 2)));
300 								return;
301 							} else {
302 								// Like
303 								filters.add(new Filter(filter.substring(0, i), LIKE, filter.substring(i + 1)));
304 								return;
305 							}
306 						case '\\':
307 							i++;
308 							continue;
309 					}
310 				}
311 			} catch (SkipFilterException sfe) {
312 				logger.debug("skipping filter `" + filter + "`");
313 			}
314 		}
315 
316 		/**
317 		 * Adds a filter.
318 		 * 
319 		 * @param property the property to filter on.
320 		 * @param condition the expression condition to apply on the property.
321 		 * @param values a comma separated list of values to evaluate against
322 		 *        the condition.
323 		 */
324 		public void addFilter(String property, int condition, String values) {
325 			try {
326 				filters.add(new Filter(property, condition, values));
327 			} catch (SkipFilterException sfe) {
328 				logger.debug("skipping filter on `" + property + "` with condition " + condition + " and values `"
329 						+ values + "`");
330 			}
331 		}
332 		
333 		/**
334 		 * Adds a filter.
335 		 * 
336 		 * @param property the property to filter on.
337 		 * @param condition the expression condition to apply on the property.
338 		 * @param value a value to evaluate against the condition.
339 		 * @throws ConversionException 
340 		 */
341 		public void addFilter(String property, int condition, Object value) throws ConversionException {
342 			try {
343 				filters.add(new Filter(property, condition, (String)ConverterManager.getDefault().convert(String.class, value, locale)));
344 			} catch (SkipFilterException sfe) {
345 				logger.debug("skipping filter on `" + property + "` with condition " + condition + " and value `"
346 						+ value + "`");
347 			}
348 		}
349 
350 		/**
351 		 * Adds a filter.
352 		 * 
353 		 * @param property the property to filter on.
354 		 * @param condition the expression condition to apply on the property.
355 		 * @param values an array of values to evaluate against the condition.
356 		 */
357 		public void addFilter(String property, int condition, String[] values) {
358 			try {
359 				StringBuffer buffer = new StringBuffer();
360 				for (int i = 0; i < values.length; i++) {
361 					buffer.append(values[i]);
362 					buffer.append(',');
363 				}
364 				filters.add(new Filter(property, condition, buffer.toString()));
365 			} catch (SkipFilterException sfe) {
366 				logger.debug("skipping filter on `" + property + "` with condition " + condition + " and values `"
367 						+ values + "`");
368 			}
369 		}
370 
371 		/**
372 		 * Adds a filter.
373 		 * 
374 		 * @param property the property to filter on.
375 		 * @param condition the expression condition to apply on the property.
376 		 * @param values an array of values to evaluate against the condition.
377 		 * @throws ConversionException
378 		 */
379 		public void addFilter(String property, int condition, Object[] values) throws ConversionException {
380 			try {
381 				StringBuffer buffer = new StringBuffer();
382 				for (int i = 0; i < values.length; i++) {
383 					buffer.append(ConverterManager.getDefault().convert(String.class, values[i], locale));
384 					buffer.append(',');
385 				}
386 				filters.add(new Filter(property, condition, buffer.toString()));
387 			} catch (SkipFilterException sfe) {
388 				logger.debug("skipping filter on `" + property + "` with condition " + condition + " and values `"
389 						+ values + "`");
390 			}
391 		}
392 
393 		/**
394 		 * @return returns the order.
395 		 * @uml.property name="order"
396 		 */
397 		public String getOrder() {
398 			return order;
399 		}
400 
401 		/**
402 		 * Sets the property used to order the collection. By default the
403 		 * ordering is set to <code>descendant</code> unless an <b>! </b>
404 		 * <code>(exclamation mark)</code> is prefixed indicating an
405 		 * <code>ascendant</code> order must be used.
406 		 * 
407 		 * @param order the property used to order the collection, optionally
408 		 *        prefixed by <b>! </b> <code>(exclamation mark)</code> to
409 		 *        invert the sorting direction.
410 		 * @uml.property name="order"
411 		 */
412 		public void setOrder(String order) {
413 			if (order != null && order.length() > 0) {
414 				if (order.charAt(0) == '!') {
415 					this.order = order.substring(1);
416 					this.descendant = true;
417 				} else {
418 					this.order = order;
419 					this.descendant = false;
420 				}
421 			} else {
422 				this.order = null;
423 			}
424 		}
425 
426 		/**
427 		 * TODO documentation
428 		 * 
429 		 * @param style
430 		 */
431 		public void setUnion(String style) {
432 			if (style != null && style.length() > 0) {
433 				if (style.equalsIgnoreCase("OR") || style.equalsIgnoreCase("true") || style.equalsIgnoreCase("yes") || style.equalsIgnoreCase("y")) {
434 					this.union = true;
435 				} else {
436 					this.union = false;
437 				}
438 			}
439 		}
440 		
441 		/**
442 		 * TODO documentation
443 		 * 
444 		 * @return
445 		 * @since 1.2.12
446 		 * @uml.property name="union"
447 		 */
448 		public void setUnion(boolean union) {
449 			this.union = union;
450 		}
451 
452 		/**
453 		 * TODO documentation
454 		 * 
455 		 * @return
456 		 * @uml.property name="union"
457 		 */
458 		public boolean isUnion() {
459 			return union;
460 		}
461 
462 		/**
463 		 * Sets the locale used to convert filters.
464 		 * 
465 		 * @param locale the locale to set.
466 		 */
467 		public void setLocale(Locale locale) {
468 			this.locale = locale;
469 		}
470 
471 		/**
472 		 * Returns the locale used to convert filters.
473 		 * 
474 		 * @return the locale used to convert filters.
475 		 */
476 		public Locale getLocale() {
477 			return locale;
478 		}
479 
480 		/**
481 		 * @see java.lang.Object#toString()
482 		 */
483 		public String toString() {
484 			return new ToStringBuilder(this).append(this.filters).append(this.order).append(this.descendant).append(
485 					this.union).append(this.locale).toString();
486 		}
487 
488 
489 		/**
490 		 * Represents a filtering condition to be applied while performing
491 		 * searches.
492 		 * 
493 		 * @author rlogiacco
494 		 */
495 		protected class Filter {
496 
497 			/**
498 			 * The column.
499 			 * 
500 			 * @uml.property name="column"
501 			 */
502 			private String property;
503 
504 			/**
505 			 * The condition.
506 			 * 
507 			 * @uml.property name="condition"
508 			 */
509 			private int condition;
510 
511 			/**
512 			 * The values array.
513 			 * 
514 			 * @uml.property name="values"
515 			 */
516 			private String[] values;
517 
518 
519 			/**
520 			 * Creates a filter with a column, a condition and a comma separated
521 			 * list of values to check against.
522 			 * 
523 			 * @param property the property to filter on.
524 			 * @param condition the condition to apply.
525 			 * @param values a comma separated list of values to check against.
526 			 * @throws SkipFilterException if the filter must be skipped invalid
527 			 */
528 			private Filter(String property, int condition, String values) throws SkipFilterException {
529 				if (values == null) {
530 					throw new SkipFilterException();
531 				}
532 				this.property = property;
533 				this.condition = condition;
534 				StringTokenizer tokenizer = new StringTokenizer(values, ",", false);
535 				this.values = new String[tokenizer.countTokens()];
536 				for (int i = 0; tokenizer.hasMoreTokens(); i++) {
537 					this.values[i] = tokenizer.nextToken();
538 					if (this.values[i].indexOf('\\') > -1) {
539 						StringBuffer buffer = new StringBuffer();
540 						for (int j = 0; j < this.values[i].length(); j++) {
541 							char c = this.values[i].charAt(j);
542 							switch (c) {
543 								case '\\':
544 									if (this.values[i].length() > j + 1 && this.values[i].charAt(j + 1) == '\\') {
545 										buffer.append(c);
546 										j++;
547 									}
548 									break;
549 								default:
550 									buffer.append(c);
551 							}
552 						}
553 						this.values[i] = buffer.toString();
554 					}
555 				}
556 				if (this.values.length == 1 && this.values[0].equals("NULL")
557 						&& (condition == EQUALS || condition == NOT_EQUALS)) {
558 					if (condition == EQUALS) {
559 						this.condition = NULL;
560 					} else if (condition == NOT_EQUALS) {
561 						this.condition = NOT_NULL;
562 					}
563 					this.values = null;
564 				} else if (this.values.length == 0) {
565 					throw new SkipFilterException();
566 				}
567 				logger.trace("adding filter on `" + property + "` with condition " + condition + " and values `" + values
568 						+ "`");
569 			}
570 			
571 			/**
572 			 * @deprecated this was a misleading name.
573 			 * @see getProperty()
574 			 */
575 			protected String getColumn() {
576 				return property;
577 			}
578 			
579 			/**
580 			 * Returns the property.
581 			 * 
582 			 * @return the property.
583 			 * @uml.property name="property"
584 			 * @since 1.2.12
585 			 */
586 			protected String getProperty() {
587 				return property;
588 			}
589 
590 			/**
591 			 * Returns the condition.
592 			 * 
593 			 * @return the condition.
594 			 * @uml.property name="condition"
595 			 */
596 			protected int getCondition() {
597 				return condition;
598 			}
599 
600 			/**
601 			 * Returns the values.
602 			 * 
603 			 * @return the values array.
604 			 * @uml.property name="values"
605 			 */
606 			protected String[] getValues() {
607 				return values;
608 			}
609 
610 			/**
611 			 * Returns the n-th value in the list where the specified index is 0
612 			 * based.
613 			 * 
614 			 * @param index the 0 based index of the value to retrieve.
615 			 * @return the n-th value in the values list.
616 			 */
617 			protected String getValue(int index) {
618 				return values[index];
619 			}
620 
621 			/**
622 			 * @see java.lang.Object#equals(java.lang.Object)
623 			 */
624 			public boolean equals(Object object) {
625 				if (!(object instanceof Filter)) {
626 					return false;
627 				}
628 				Filter filter = (Filter)object;
629 				return new EqualsBuilder().append(this.property, filter.property).append(this.condition, filter.condition)
630 						.append(this.values, filter.values).isEquals();
631 			}
632 
633 			/**
634 			 * @see java.lang.Object#hashCode()
635 			 */
636 			public int hashCode() {
637 				return new HashCodeBuilder(-27848275, 353473951).append(this.property).append(this.condition).append(
638 						this.values).toHashCode();
639 			}
640 
641 			/**
642 			 * @see java.lang.Object#toString()
643 			 */
644 			public String toString() {
645 				return new ToStringBuilder(this).append(this.property).append(this.condition).append(this.values)
646 						.toString();
647 			}
648 		}
649 
650 		private static class SkipFilterException extends Exception {
651 
652 			private static final long serialVersionUID = 5576925342229697415L;
653 		}
654 	}
655 }