001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package info.jtrac.domain;
018:
019: import info.jtrac.domain.FilterCriteria.Expression;
020: import info.jtrac.wicket.ComponentUtils;
021: import info.jtrac.wicket.JtracCheckBoxMultipleChoice;
022: import info.jtrac.wicket.yui.YuiCalendar;
023: import java.io.Serializable;
024: import java.util.ArrayList;
025: import java.util.List;
026: import java.util.Map;
027: import org.apache.wicket.Component;
028: import org.apache.wicket.markup.html.WebMarkupContainer;
029: import org.apache.wicket.markup.html.form.IChoiceRenderer;
030: import org.apache.wicket.markup.html.form.TextField;
031: import org.apache.wicket.markup.html.panel.Fragment;
032: import org.apache.wicket.model.PropertyModel;
033: import org.hibernate.criterion.DetachedCriteria;
034: import org.hibernate.criterion.MatchMode;
035: import org.hibernate.criterion.Restrictions;
036:
037: /**
038: * used to render columns in the search results table
039: * and also in the search filter screen
040: */
041: public class ColumnHeading implements Serializable {
042:
043: public static final String ID = "id";
044: public static final String SUMMARY = "summary";
045: public static final String DETAIL = "detail";
046: public static final String LOGGED_BY = "loggedBy";
047: public static final String STATUS = "status";
048: public static final String ASSIGNED_TO = "assignedTo";
049: public static final String TIME_STAMP = "timeStamp";
050: public static final String SPACE = "space";
051:
052: private Field field;
053: private String name;
054: private String label;
055: private boolean visible = true;
056:
057: private FilterCriteria filterCriteria = new FilterCriteria();
058:
059: public ColumnHeading(String name) {
060: this .name = name;
061: if (name.equals(DETAIL) || name.equals(SPACE)) {
062: visible = false;
063: }
064: }
065:
066: public ColumnHeading(Field field) {
067: this .field = field;
068: this .name = field.getName().getText();
069: this .label = field.getLabel();
070: }
071:
072: public boolean isField() {
073: return field != null;
074: }
075:
076: public static List<ColumnHeading> getColumnHeadings(Space s) {
077: List<ColumnHeading> list = new ArrayList<ColumnHeading>();
078: list.add(new ColumnHeading(ID));
079: list.add(new ColumnHeading(SUMMARY));
080: list.add(new ColumnHeading(DETAIL));
081: list.add(new ColumnHeading(STATUS));
082: list.add(new ColumnHeading(LOGGED_BY));
083: list.add(new ColumnHeading(ASSIGNED_TO));
084: for (Field f : s.getMetadata().getFieldList()) {
085: list.add(new ColumnHeading(f));
086: }
087: list.add(new ColumnHeading(TIME_STAMP));
088: return list;
089: }
090:
091: public static List<ColumnHeading> getColumnHeadings(User u) {
092: List<ColumnHeading> list = new ArrayList<ColumnHeading>();
093: list.add(new ColumnHeading(ID));
094: list.add(new ColumnHeading(SPACE));
095: list.add(new ColumnHeading(SUMMARY));
096: list.add(new ColumnHeading(DETAIL));
097: list.add(new ColumnHeading(LOGGED_BY));
098: list.add(new ColumnHeading(ASSIGNED_TO));
099: list.add(new ColumnHeading(TIME_STAMP));
100: return list;
101: }
102:
103: public List<Expression> getValidFilterExpressions() {
104: return (List<Expression>) process(null, null);
105: }
106:
107: public Component getFilterUiFragment(Component c) {
108: return (Fragment) process(c, null);
109: }
110:
111: public void addRestrictions(DetachedCriteria criteria,
112: ItemSearch itemSearch) {
113: process(null, criteria);
114: }
115:
116: // TODO use some elegant factory pattern here if possible
117: // TODO reduce redundant code
118: // this routine is a massive if-then that has 3 responsibilities
119: // based on column type:
120: // 1) return the possible expressions (equals, greater-than etc) to show on filter UI for selection
121: // 2) or return the wicket ui fragment that will be shown over ajax (based on selected expression)
122: // 3) or add restrictions to the hibernate detached criteria that will be used to query the database
123: // putting all these things into one place, makes it easy to maintain, as the 3 responsibilities
124: // are closely interdependent
125: private Object process(Component c, DetachedCriteria criteria) {
126: boolean forFragment = c != null;
127: List<Expression> list = new ArrayList<Expression>();
128: Fragment fragment = null;
129: List values = filterCriteria.getValues();
130: Object value = filterCriteria.getValue();
131: Object value2 = filterCriteria.getValue2();
132: Expression expression = filterCriteria.getExpression();
133: if (isField()) {
134: switch (field.getName().getType()) {
135: case 1:
136: case 2:
137: case 3:
138: list.add(Expression.IN);
139: if (forFragment) {
140: fragment = new Fragment("fragParent", "multiSelect");
141: final Map<String, String> options = field
142: .getOptions();
143: JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice(
144: "values", new ArrayList(options.keySet()),
145: new IChoiceRenderer() {
146: public Object getDisplayValue(Object o) {
147: return options.get(o);
148: }
149:
150: public String getIdValue(Object o, int i) {
151: return o.toString();
152: }
153: });
154: fragment.add(choice);
155: choice.setModel(new PropertyModel(this ,
156: "filterCriteria.values"));
157: }
158: if (filterHasValueList(criteria)) {
159: List<Integer> keys = new ArrayList<Integer>(values
160: .size());
161: for (Object o : values) {
162: keys.add(new Integer(o.toString()));
163: }
164: criteria.add(Restrictions.in(name, keys));
165: }
166: break; // drop down list
167: case 4: // decimal number
168: list.add(Expression.EQ);
169: list.add(Expression.NOT_EQ);
170: list.add(Expression.GT);
171: list.add(Expression.LT);
172: list.add(Expression.BETWEEN);
173: if (forFragment) {
174: fragment = new Fragment("fragParent", "textField");
175: TextField textField = new TextField("value",
176: Double.class);
177: textField.setModel(new PropertyModel(this ,
178: "filterCriteria.value"));
179: fragment.add(textField);
180: if (expression == Expression.BETWEEN) {
181: TextField textField2 = new TextField("value2",
182: Double.class);
183: textField.setModel(new PropertyModel(this ,
184: "filterCriteria.value2"));
185: fragment.add(textField2);
186: } else {
187: fragment.add(new WebMarkupContainer("value2")
188: .setVisible(false));
189: }
190: }
191: if (filterHasValue(criteria)) {
192: switch (expression) {
193: case EQ:
194: criteria.add(Restrictions.eq(name, value));
195: break;
196: case NOT_EQ:
197: criteria.add(Restrictions.not(Restrictions.eq(
198: name, value)));
199: break;
200: case GT:
201: criteria.add(Restrictions.gt(name, value));
202: break;
203: case LT:
204: criteria.add(Restrictions.lt(name, value));
205: break;
206: case BETWEEN:
207: criteria.add(Restrictions.gt(name, value));
208: criteria.add(Restrictions.lt(name, value2));
209: break;
210: default:
211: }
212: }
213: break;
214: case 6: // date
215: list.add(Expression.EQ);
216: list.add(Expression.NOT_EQ);
217: list.add(Expression.GT);
218: list.add(Expression.LT);
219: list.add(Expression.BETWEEN);
220: if (forFragment) {
221: fragment = new Fragment("fragParent", "dateField");
222: YuiCalendar calendar = new YuiCalendar("value",
223: new PropertyModel(this ,
224: "filterCriteria.value"), false);
225: fragment.add(calendar);
226: if (filterCriteria.getExpression() == Expression.BETWEEN) {
227: YuiCalendar calendar2 = new YuiCalendar(
228: "value2", new PropertyModel(this ,
229: "filterCriteria.value2"), false);
230: fragment.add(calendar2);
231: } else {
232: fragment.add(new WebMarkupContainer("value2")
233: .setVisible(false));
234: }
235: }
236: if (filterHasValue(criteria)) {
237: switch (expression) {
238: case EQ:
239: criteria.add(Restrictions.eq(name, value));
240: break;
241: case NOT_EQ:
242: criteria.add(Restrictions.not(Restrictions.eq(
243: name, value)));
244: break;
245: case GT:
246: criteria.add(Restrictions.gt(name, value));
247: break;
248: case LT:
249: criteria.add(Restrictions.lt(name, value));
250: break;
251: case BETWEEN:
252: criteria.add(Restrictions.gt(name, value));
253: criteria.add(Restrictions.lt(name, value2));
254: break;
255: default:
256: }
257: }
258: break;
259: case 5: // free text
260: list.add(Expression.CONTAINS);
261: if (forFragment) {
262: fragment = new Fragment("fragParent", "textField");
263: TextField textField = new TextField("value",
264: String.class);
265: textField.setModel(new PropertyModel(this ,
266: "filterCriteria.value"));
267: fragment.add(textField);
268: fragment.add(new WebMarkupContainer("value2")
269: .setVisible(false));
270: }
271: if (filterHasValue(criteria)) {
272: criteria.add(Restrictions.ilike(name,
273: (String) value, MatchMode.ANYWHERE));
274: }
275: break;
276: default:
277: throw new RuntimeException("Unknown Column Heading "
278: + name);
279: }
280: } else {
281: if (name.equals(ID)) {
282: list.add(Expression.EQ);
283: if (forFragment) {
284: fragment = new Fragment("fragParent", "textField");
285: TextField textField = new TextField("value",
286: String.class);
287: textField.setModel(new PropertyModel(this ,
288: "filterCriteria.value"));
289: fragment.add(textField);
290: fragment.add(new WebMarkupContainer("value2")
291: .setVisible(false));
292: }
293: // should never come here for criteria: see ItemSearch#getRefId()
294: } else if (name.equals(SUMMARY)) {
295: list.add(Expression.CONTAINS);
296: if (forFragment) {
297: fragment = new Fragment("fragParent", "textField");
298: TextField textField = new TextField("value",
299: String.class);
300: textField.setModel(new PropertyModel(this ,
301: "filterCriteria.value"));
302: fragment.add(textField);
303: fragment.add(new WebMarkupContainer("value2")
304: .setVisible(false));
305: }
306: if (filterHasValue(criteria)) {
307: criteria.add(Restrictions.ilike(name,
308: (String) value, MatchMode.ANYWHERE));
309: }
310: } else if (name.equals(DETAIL)) {
311: list.add(Expression.CONTAINS);
312: if (forFragment) {
313: fragment = new Fragment("fragParent", "textField");
314: TextField textField = new TextField("value",
315: String.class);
316: textField.setModel(new PropertyModel(this ,
317: "filterCriteria.value"));
318: fragment.add(textField);
319: fragment.add(new WebMarkupContainer("value2")
320: .setVisible(false));
321: }
322: // should never come here for criteria: see ItemSearch#getSearchText()
323: } else if (name.equals(STATUS)) {
324: list.add(Expression.IN);
325: if (forFragment) {
326: fragment = new Fragment("fragParent", "multiSelect");
327: final Map<Integer, String> options = ComponentUtils
328: .getCurrentSpace(c).getMetadata()
329: .getStates();
330: options.remove(State.NEW);
331: JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice(
332: "values", new ArrayList(options.keySet()),
333: new IChoiceRenderer() {
334: public Object getDisplayValue(Object o) {
335: return options.get(o);
336: }
337:
338: public String getIdValue(Object o, int i) {
339: return o.toString();
340: }
341: });
342: fragment.add(choice);
343: choice.setModel(new PropertyModel(this ,
344: "filterCriteria.values"));
345: }
346: if (filterHasValueList(criteria)) {
347: criteria.add(Restrictions.in(name, filterCriteria
348: .getValues()));
349: }
350: } else if (name.equals(ASSIGNED_TO)
351: || name.equals(LOGGED_BY)) {
352: list.add(Expression.IN);
353: if (forFragment) {
354: fragment = new Fragment("fragParent", "multiSelect");
355: List<User> users = null;
356: Space s = ComponentUtils.getCurrentSpace(c);
357: if (s == null) {
358: User u = ComponentUtils.getPrincipal(c);
359: users = ComponentUtils.getJtrac(c)
360: .findUsersForUser(u);
361: } else {
362: users = ComponentUtils.getJtrac(c)
363: .findUsersForSpace(s.getId());
364: }
365: JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice(
366: "values", users, new IChoiceRenderer() {
367: public Object getDisplayValue(Object o) {
368: return ((User) o).getName();
369: }
370:
371: public String getIdValue(Object o, int i) {
372: return ((User) o).getId() + "";
373: }
374: });
375: fragment.add(choice);
376: choice.setModel(new PropertyModel(this ,
377: "filterCriteria.values"));
378: }
379: if (filterHasValueList(criteria)) {
380: criteria.add(Restrictions.in(name, filterCriteria
381: .getValues()));
382: }
383: } else if (name.equals(TIME_STAMP)) {
384: list.add(Expression.BETWEEN);
385: list.add(Expression.GT);
386: list.add(Expression.LT);
387: if (forFragment) {
388: fragment = new Fragment("fragParent", "dateField");
389: YuiCalendar calendar = new YuiCalendar("value",
390: new PropertyModel(this ,
391: "filterCriteria.value"), false);
392: fragment.add(calendar);
393: if (expression == Expression.BETWEEN) {
394: YuiCalendar calendar2 = new YuiCalendar(
395: "value2", new PropertyModel(this ,
396: "filterCriteria.value2"), false);
397: fragment.add(calendar2);
398: } else {
399: fragment.add(new WebMarkupContainer("value2")
400: .setVisible(false));
401: }
402: }
403: if (filterHasValue(criteria)) {
404: switch (expression) {
405: case GT:
406: criteria.add(Restrictions.gt(name, value));
407: break;
408: case LT:
409: criteria.add(Restrictions.lt(name, value));
410: break;
411: case BETWEEN:
412: criteria.add(Restrictions.gt(name, value));
413: criteria.add(Restrictions.lt(name, value2));
414: break;
415: default:
416: }
417: }
418: } else if (name.equals(SPACE)) {
419: list.add(Expression.IN);
420: if (forFragment) {
421: fragment = new Fragment("fragParent", "multiSelect");
422: List<Space> spaces = new ArrayList(ComponentUtils
423: .getPrincipal(c).getSpaces());
424: JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice(
425: "values", spaces, new IChoiceRenderer() {
426: public Object getDisplayValue(Object o) {
427: return ((Space) o).getName();
428: }
429:
430: public String getIdValue(Object o, int i) {
431: return ((Space) o).getId() + "";
432: }
433: });
434: fragment.add(choice);
435: choice.setModel(new PropertyModel(this ,
436: "filterCriteria.values"));
437: }
438: // should never come here for criteria: see ItemSearch#getSelectedSpaces()
439: } else {
440: throw new RuntimeException("Unknown Column Heading "
441: + name);
442: }
443: }
444: if (forFragment) {
445: return fragment;
446: } else {
447: return list;
448: }
449: }
450:
451: private boolean filterHasValueList(DetachedCriteria criteria) {
452: if (criteria != null) {
453: if (filterCriteria.getExpression() != null
454: && filterCriteria.getValues() != null
455: && filterCriteria.getValues().size() > 0) {
456: return true;
457: } else {
458: filterCriteria.setExpression(null);
459: return false;
460: }
461: }
462: return false;
463: }
464:
465: private boolean filterHasValue(DetachedCriteria criteria) {
466: if (criteria != null) {
467: Object value = filterCriteria.getValue();
468: if (filterCriteria.getExpression() != null && value != null
469: && value.toString().trim().length() > 0) {
470: return true;
471: } else {
472: filterCriteria.setExpression(null);
473: return false;
474: }
475: }
476: return false;
477: }
478:
479: //==========================================================================
480:
481: public Field getField() {
482: return field;
483: }
484:
485: public String getName() {
486: return name;
487: }
488:
489: public String getLabel() {
490: return label;
491: }
492:
493: public FilterCriteria getFilterCriteria() {
494: return filterCriteria;
495: }
496:
497: public void setFilterCriteria(FilterCriteria filterCriteria) {
498: this .filterCriteria = filterCriteria;
499: }
500:
501: public boolean isVisible() {
502: return visible;
503: }
504:
505: public void setVisible(boolean visible) {
506: this .visible = visible;
507: }
508:
509: @Override
510: public int hashCode() {
511: return name.hashCode();
512: }
513:
514: @Override
515: public boolean equals(Object o) {
516: if (this == o) {
517: return true;
518: }
519: if (!(o instanceof ColumnHeading)) {
520: return false;
521: }
522: final ColumnHeading ch = (ColumnHeading) o;
523: return ch.getName().equals(name);
524: }
525:
526: @Override
527: public String toString() {
528: StringBuilder sb = new StringBuilder();
529: sb.append("name [").append(name);
530: sb.append("]; filterCriteria [").append(filterCriteria);
531: sb.append("]");
532: return sb.toString();
533: }
534:
535: }
|