View Javadoc
1   package org.argeo.activities.ui;
2   
3   import static org.argeo.eclipse.ui.jcr.JcrUiUtils.getNodeSelectionAdapter;
4   
5   import java.text.DateFormat;
6   import java.text.SimpleDateFormat;
7   import java.util.Calendar;
8   import java.util.GregorianCalendar;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import javax.jcr.Node;
14  import javax.jcr.NodeIterator;
15  import javax.jcr.Property;
16  import javax.jcr.PropertyType;
17  import javax.jcr.RepositoryException;
18  import javax.jcr.Session;
19  import javax.jcr.Value;
20  import javax.jcr.query.Query;
21  import javax.jcr.query.QueryResult;
22  
23  import org.argeo.activities.ActivitiesException;
24  import org.argeo.activities.ActivitiesNames;
25  import org.argeo.activities.ActivitiesService;
26  import org.argeo.activities.ActivitiesTypes;
27  import org.argeo.activities.core.ActivityUtils;
28  import org.argeo.cms.util.CmsUtils;
29  import org.argeo.connect.ConnectConstants;
30  import org.argeo.connect.UserAdminService;
31  import org.argeo.connect.resources.ResourcesService;
32  import org.argeo.connect.ui.ConnectWorkbenchUtils;
33  import org.argeo.connect.ui.SystemWorkbenchService;
34  import org.argeo.connect.ui.util.HtmlListRwtAdapter;
35  import org.argeo.connect.util.ConnectJcrUtils;
36  import org.argeo.connect.util.ConnectUtils;
37  import org.argeo.connect.util.XPathUtils;
38  import org.argeo.eclipse.ui.EclipseUiUtils;
39  import org.argeo.jcr.JcrUtils;
40  import org.eclipse.jface.layout.TableColumnLayout;
41  import org.eclipse.jface.viewers.ColumnLabelProvider;
42  import org.eclipse.jface.viewers.ColumnWeightData;
43  import org.eclipse.jface.viewers.IStructuredContentProvider;
44  import org.eclipse.jface.viewers.TableViewer;
45  import org.eclipse.jface.viewers.TableViewerColumn;
46  import org.eclipse.jface.viewers.Viewer;
47  import org.eclipse.swt.SWT;
48  import org.eclipse.swt.widgets.Composite;
49  import org.eclipse.swt.widgets.Table;
50  import org.eclipse.swt.widgets.TableColumn;
51  
52  /** Basic implementation of a table that displays both activities and tasks */
53  public class ActivityTable extends Composite {
54  	private static final long serialVersionUID = 1L;
55  
56  	private TableViewer tableViewer;
57  	private Session session;
58  	private Node entity;
59  	private UserAdminService userAdminService;
60  	private ResourcesService resourceService;
61  	private ActivitiesService activitiesService;
62  	private SystemWorkbenchService systemWorkbenchService;
63  
64  	/**
65  	 * Default table with a filter
66  	 * 
67  	 * @param parent
68  	 * @param style
69  	 *            the style of the table
70  	 * @param session
71  	 */
72  	public ActivityTable(Composite parent, int style, UserAdminService userAdminService,
73  			ResourcesService resourceService, ActivitiesService activityService,
74  			SystemWorkbenchService systemWorkbenchService, Node entity) {
75  		super(parent, SWT.NONE);
76  		this.entity = entity;
77  		session = ConnectJcrUtils.getSession(entity);
78  		this.userAdminService = userAdminService;
79  		this.resourceService = resourceService;
80  		this.activitiesService = activityService;
81  		this.systemWorkbenchService = systemWorkbenchService;
82  
83  		this.setLayout(EclipseUiUtils.noSpaceGridLayout());
84  		Composite tableComp = new Composite(this, SWT.NO_FOCUS);
85  		tableViewer = createActivityViewer(tableComp, style);
86  		tableComp.setLayoutData(EclipseUiUtils.fillAll());
87  		// refreshFilteredList();
88  	}
89  
90  	private TableViewer createActivityViewer(final Composite parent, int style) {
91  		TableViewer viewer = new TableViewer(parent, SWT.V_SCROLL | style);
92  		TableColumnLayout tableColumnLayout = new TableColumnLayout();
93  
94  		Table table = viewer.getTable();
95  		table.setLayoutData(EclipseUiUtils.fillAll());
96  		CmsUtils.markup(table);
97  		CmsUtils.setItemHeight(table, 54);
98  		table.setHeaderVisible(false);
99  		table.setLinesVisible(true);
100 
101 		Map<String, ColumnLabelProvider> lpMap = new HashMap<String, ColumnLabelProvider>();
102 		lpMap.put(ActivitiesNames.ACTIVITIES_ASSIGNED_TO, new UsersLabelProvider());
103 		lpMap.put(ActivitiesNames.ACTIVITIES_RELATED_TO, new AlsoRelatedToLP());
104 
105 		ActivityViewerComparator comparator = new ActivityViewerComparator(activitiesService, lpMap);
106 
107 		TableColumn col;
108 		TableViewerColumn tvCol;
109 		int colIndex = 0;
110 
111 		// Types
112 		col = new TableColumn(table, SWT.LEFT);
113 		tableColumnLayout.setColumnData(col, new ColumnWeightData(30, 120, true));
114 		tvCol = new TableViewerColumn(viewer, col);
115 		tvCol.setLabelProvider(new TypeLabelProvider());
116 		col.addSelectionListener(getNodeSelectionAdapter(colIndex++, PropertyType.STRING, Property.JCR_PRIMARY_TYPE,
117 				comparator, viewer));
118 
119 		// Dates
120 		col = new TableColumn(table, SWT.LEFT);
121 		tableColumnLayout.setColumnData(col, new ColumnWeightData(60, 145, true));
122 		tvCol = new TableViewerColumn(viewer, col);
123 		tvCol.setLabelProvider(new DateLabelProvider());
124 		col.addSelectionListener(getNodeSelectionAdapter(colIndex++, PropertyType.DATE,
125 				ActivityViewerComparator.RELEVANT_DATE, comparator, viewer));
126 
127 		// relevant users
128 		col = new TableColumn(table, SWT.LEFT);
129 		col.setText("Reported by");
130 		col.addSelectionListener(getNodeSelectionAdapter(colIndex++, PropertyType.STRING,
131 				ActivitiesNames.ACTIVITIES_ASSIGNED_TO, comparator, viewer));
132 		tableColumnLayout.setColumnData(col, new ColumnWeightData(60, 180, true));
133 		tvCol = new TableViewerColumn(viewer, col);
134 		tvCol.setLabelProvider(new UsersLabelProvider());
135 
136 		// Also related to
137 		col = new TableColumn(table, SWT.LEFT | SWT.WRAP);
138 		col.setText("Also related to");
139 		tableColumnLayout.setColumnData(col, new ColumnWeightData(80, 80, true));
140 		tvCol = new TableViewerColumn(viewer, col);
141 		tvCol.setLabelProvider(new AlsoRelatedToLP());
142 		col.setToolTipText("Also related to these entities");
143 
144 		// Title / description
145 		col = new TableColumn(table, SWT.LEFT | SWT.WRAP);
146 		tableColumnLayout.setColumnData(col, new ColumnWeightData(200, 150, true));
147 		// col.addSelectionListener(ConnectJcrUtils.getNodeSelectionAdapter(colIndex++,
148 		// PropertyType.STRING, Property.JCR_TITLE, comparator, viewer));
149 		tvCol = new TableViewerColumn(viewer, col);
150 		tvCol.setLabelProvider(new TitleDescLabelProvider());
151 
152 		//
153 		// IMPORTANT: initialize comparator before setting it
154 		comparator.setColumn(PropertyType.DATE, ActivityViewerComparator.RELEVANT_DATE);
155 		// 2 times to force descending
156 		comparator.setColumn(PropertyType.DATE, ActivityViewerComparator.RELEVANT_DATE);
157 		viewer.setComparator(comparator);
158 
159 		table.addSelectionListener(new HtmlListRwtAdapter(systemWorkbenchService));
160 		// EclipseUiSpecificUtils.enableToolTipSupport(viewer);
161 		viewer.setContentProvider(new MyTableContentProvider());
162 
163 		// Warning: don't forget to set the generated layout.
164 		parent.setLayout(tableColumnLayout);
165 		return viewer;
166 	}
167 
168 	/**
169 	 * Refresh the list: caller might overwrite in order to display a subset of
170 	 * all nodes
171 	 */
172 	protected void refreshFilteredList() {
173 		try {
174 			List<Node> nodes = JcrUtils.nodeIteratorToList(listFilteredElements(session, null));
175 			tableViewer.setInput(nodes.toArray());
176 		} catch (RepositoryException e) {
177 			throw new ActivitiesException("Unable to list activities", e);
178 		}
179 	}
180 
181 	/** Returns the User table viewer, typically to add doubleclick listener */
182 	public TableViewer getTableViewer() {
183 		return tableViewer;
184 	}
185 
186 	/**
187 	 * Build repository request : caller might overwrite in order to display a
188 	 * subset
189 	 */
190 	protected NodeIterator listFilteredElements(Session session, String filter) throws RepositoryException {
191 		String xpathQueryStr = "//element(*, " + ActivitiesTypes.ACTIVITIES_ACTIVITY + ")";
192 		String attrQuery = XPathUtils.getFreeTextConstraint(filter);
193 		if (EclipseUiUtils.notEmpty(attrQuery))
194 			xpathQueryStr += "[" + attrQuery + "]";
195 		Query xpathQuery = XPathUtils.createQuery(session, xpathQueryStr);
196 		QueryResult result = xpathQuery.execute();
197 		return result.getNodes();
198 	}
199 
200 	// /////////////////////////
201 	// LOCAL CLASSES
202 	private class TypeLabelProvider extends ColumnLabelProvider {
203 		private static final long serialVersionUID = 1L;
204 
205 		@Override
206 		public String getText(Object element) {
207 			Node currNode = (Node) element;
208 
209 			try {
210 				StringBuilder builder = new StringBuilder();
211 				// if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_TASK)) {
212 				// builder.append("<b>");
213 				// builder.append(resourceService.getItemDefaultEnLabel(currNode.getPrimaryNodeType().getName()));
214 				// builder.append("</b>");
215 				// builder.append("<br />");
216 				// builder.append(ConnectJcrUtils.get(currNode,
217 				// ActivitiesNames.ACTIVITIES_TASK_STATUS));
218 				//
219 				// } else
220 
221 				if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_ACTIVITY)) {
222 					builder.append("<b>");
223 					builder.append(activitiesService.getActivityLabel(currNode));
224 
225 					// specific for rate
226 					if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_RATE)) {
227 						Long rate = ConnectJcrUtils.getLongValue(currNode, ActivitiesNames.ACTIVITIES_RATE);
228 						if (rate != null)
229 							builder.append(": ").append(rate);
230 					} else
231 					// specific for tasks
232 					if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_TASK))
233 						builder.append(": ")
234 								.append(ConnectJcrUtils.get(currNode, ActivitiesNames.ACTIVITIES_TASK_STATUS));
235 					builder.append("</b>");
236 				}
237 				return builder.toString();
238 			} catch (RepositoryException re) {
239 				throw new ActivitiesException("Unable to get type snippet for " + currNode, re);
240 			}
241 		}
242 	}
243 
244 	private class DateLabelProvider extends ColumnLabelProvider {
245 		private static final long serialVersionUID = 1L;
246 
247 		@Override
248 		public String getText(Object element) {
249 			Node activityNode = (Node) element;
250 			try {
251 				Calendar date = null;
252 				StringBuilder builder = new StringBuilder();
253 				// VARIOUS WF LAYOUT
254 				if (activityNode.isNodeType(ActivitiesTypes.ACTIVITIES_TASK)) {
255 					// done task
256 					if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_CLOSE_DATE)) {
257 						date = activityNode.getProperty(ActivitiesNames.ACTIVITIES_CLOSE_DATE).getDate();
258 						builder.append(funkyFormat(date)).append(" (Done date)").append("<br />");
259 
260 						if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_DUE_DATE)) {
261 							date = activityNode.getProperty(ActivitiesNames.ACTIVITIES_DUE_DATE).getDate();
262 							builder.append(funkyFormat(date)).append(" (Due date)");
263 
264 						}
265 					} else if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_DUE_DATE)) {
266 						date = activityNode.getProperty(ActivitiesNames.ACTIVITIES_DUE_DATE).getDate();
267 						builder.append(funkyFormat(date)).append(" (Due date)").append("<br />");
268 
269 						boolean sleeping = false;
270 						if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_WAKE_UP_DATE)) {
271 							date = activityNode.getProperty(ActivitiesNames.ACTIVITIES_WAKE_UP_DATE).getDate();
272 							Calendar now = GregorianCalendar.getInstance();
273 							if (date.after(now)) {
274 								builder.append(funkyFormat(date)).append(" (Sleep until)");
275 								sleeping = true;
276 							}
277 						}
278 
279 						if (activityNode.hasProperty(Property.JCR_LAST_MODIFIED) && !sleeping) {
280 							date = activityNode.getProperty(Property.JCR_LAST_MODIFIED).getDate();
281 							builder.append(funkyFormat(date)).append(" (Last update)");
282 						}
283 					} else {
284 						if (activityNode.hasProperty(Property.JCR_LAST_MODIFIED)) {
285 							date = activityNode.getProperty(Property.JCR_LAST_MODIFIED).getDate();
286 							builder.append(funkyFormat(date)).append(" (Last update)").append("<br />");
287 						}
288 
289 						if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_ACTIVITY_DATE)) {
290 							date = activityNode.getProperty(ActivitiesNames.ACTIVITIES_ACTIVITY_DATE).getDate();
291 							builder.append(funkyFormat(date)).append(" (Creation date)");
292 						} else if (activityNode.hasProperty(Property.JCR_CREATED)) {
293 							date = activityNode.getProperty(Property.JCR_CREATED).getDate();
294 							builder.append(funkyFormat(date)).append(" (Creation date)");
295 						}
296 					}
297 				} else if (activityNode.isNodeType(ActivitiesTypes.ACTIVITIES_ACTIVITY)) {
298 					Calendar happened = null;
299 					// Calendar created = null;
300 					String happenedLbl = "";
301 					Calendar lastMod = null;
302 					if (activityNode.hasProperty(Property.JCR_LAST_MODIFIED))
303 						lastMod = activityNode.getProperty(Property.JCR_LAST_MODIFIED).getDate();
304 
305 					if (activityNode.hasProperty(ActivitiesNames.ACTIVITIES_ACTIVITY_DATE)) {
306 						happened = activityNode.getProperty(ActivitiesNames.ACTIVITIES_ACTIVITY_DATE).getDate();
307 						happenedLbl = " (Done date)";
308 					} else if (activityNode.hasProperty(Property.JCR_CREATED)) {
309 						happened = activityNode.getProperty(Property.JCR_CREATED).getDate();
310 						happenedLbl = " (Creation date)";
311 					}
312 					boolean addUpdateDt = happened == null;
313 					if (!addUpdateDt) {
314 						date = (Calendar) happened.clone();
315 						date.add(Calendar.MINUTE, 5);
316 						if (lastMod != null)
317 							addUpdateDt = lastMod.after(date);
318 					}
319 					if (addUpdateDt)
320 						builder.append(funkyFormat(lastMod)).append(" (Last update)").append("<br />");
321 					if (happened != null)
322 						builder.append(funkyFormat(happened)).append(happenedLbl);
323 				}
324 				return builder.toString();
325 			} catch (RepositoryException e) {
326 				throw new ActivitiesException("Unable to get date label for " + activityNode, e);
327 			}
328 		}
329 	}
330 
331 	private String getDisplayName(String id) {
332 		return userAdminService.getUserDisplayName(id);
333 	}
334 
335 	private String getDNameFromProp(Node node, String propName) {
336 		String id = ConnectJcrUtils.get(node, propName);
337 		if (EclipseUiUtils.notEmpty(id))
338 			return userAdminService.getUserDisplayName(id);
339 		return "";
340 	}
341 
342 	private class UsersLabelProvider extends ColumnLabelProvider {
343 		private static final long serialVersionUID = 1L;
344 
345 		@Override
346 		public String getText(Object element) {
347 			Node activityNode = (Node) element;
348 			try {
349 				String value = "";
350 				StringBuilder builder = new StringBuilder();
351 				if (activityNode.isNodeType(ActivitiesTypes.ACTIVITIES_TASK)) {
352 					// done task
353 					if (activitiesService.isTaskDone(activityNode)) {
354 						value = getDNameFromProp(activityNode, ActivitiesNames.ACTIVITIES_CLOSED_BY);
355 						if (EclipseUiUtils.notEmpty(value))
356 							builder.append(value).append(" (Closed by)").append("<br />");
357 						value = activitiesService.getAssignedToDisplayName(activityNode);
358 						if (EclipseUiUtils.notEmpty(value))
359 							builder.append(value).append(" (Assignee)").append("<br />");
360 					} else {
361 						value = activitiesService.getAssignedToDisplayName(activityNode);
362 						if (EclipseUiUtils.notEmpty(value))
363 							builder.append(value).append(" (Assignee)").append("<br />");
364 
365 						value = ConnectJcrUtils.get(activityNode, Property.JCR_LAST_MODIFIED_BY);
366 						if (EclipseUiUtils.notEmpty(value))
367 							builder.append(getDisplayName(value)).append(" (Last updater)");
368 					}
369 				} else if (activityNode.isNodeType(ActivitiesTypes.ACTIVITIES_ACTIVITY)) {
370 					String reporter = getDNameFromProp(activityNode, ActivitiesNames.ACTIVITIES_REPORTED_BY);
371 					String updater = getDNameFromProp(activityNode, Property.JCR_LAST_MODIFIED_BY);
372 
373 					if (EclipseUiUtils.isEmpty(reporter))
374 						reporter = getDNameFromProp(activityNode, Property.JCR_CREATED_BY);
375 
376 					if (EclipseUiUtils.notEmpty(reporter))
377 						builder.append(reporter).append(" (Reporter)").append("<br />");
378 					if (EclipseUiUtils.notEmpty(updater) && (reporter == null || !reporter.equals(updater)))
379 						builder.append(updater).append(" (Last updater)").append("<br />");
380 				}
381 				return builder.toString();
382 			} catch (RepositoryException e) {
383 				throw new ActivitiesException("Unable to get related users snippet for " + activityNode, e);
384 			}
385 		}
386 	}
387 
388 	private class AlsoRelatedToLP extends ColumnLabelProvider {
389 		private static final long serialVersionUID = 1L;
390 
391 		@Override
392 		public String getText(Object element) {
393 			try {
394 				Node currNode = (Node) element;
395 				if (currNode.hasProperty(ActivitiesNames.ACTIVITIES_RELATED_TO)) {
396 					StringBuilder builder = new StringBuilder();
397 					Value[] refs = currNode.getProperty(ActivitiesNames.ACTIVITIES_RELATED_TO).getValues();
398 					if (refs.length > 0) {
399 						String currEntityId = null;
400 						if (entity != null)
401 							currEntityId = entity.getIdentifier();
402 						for (Value value : refs) {
403 							String id = value.getString();
404 							if (!id.equals(currEntityId)) {
405 								Node currReferenced = session.getNodeByIdentifier(id);
406 								String label = ConnectWorkbenchUtils.getOpenEditorSnippet(
407 										systemWorkbenchService.getOpenEntityEditorCmdId(), currReferenced,
408 										ConnectJcrUtils.get(currReferenced, Property.JCR_TITLE));
409 								builder.append(label).append(", ");
410 							}
411 						}
412 						if (builder.lastIndexOf(", ") != -1) {
413 							String value = ConnectUtils
414 									.replaceAmpersand(builder.substring(0, builder.lastIndexOf(", ")));
415 							return wrapThis(value);
416 						}
417 					}
418 
419 				}
420 				return "";
421 			} catch (RepositoryException re) {
422 				throw new ActivitiesException("Unable to get date from node " + element, re);
423 			}
424 		}
425 	}
426 
427 	private class TitleDescLabelProvider extends ColumnLabelProvider {
428 		private static final long serialVersionUID = 1L;
429 
430 		@Override
431 		public String getText(Object element) {
432 			try {
433 				Node currNode = (Node) element;
434 
435 				if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_ACTIVITY)) {
436 					String title = ConnectJcrUtils.get(currNode, Property.JCR_TITLE);
437 					// Specific behaviour for polls
438 					if (currNode.isNodeType(ActivitiesTypes.ACTIVITIES_POLL)) {
439 						title = ConnectJcrUtils.get(currNode, ActivitiesNames.ACTIVITIES_POLL_NAME) + ": "
440 								+ ActivityUtils.getAvgRating(currNode);
441 					}
442 
443 					String desc = ConnectJcrUtils.get(currNode, Property.JCR_DESCRIPTION);
444 					String res = ConnectJcrUtils.concatIfNotEmpty(title, desc, " - ");
445 					return wrapThis(res);
446 				}
447 				return "";
448 			} catch (RepositoryException re) {
449 				throw new ActivitiesException("Unable to get date from node " + element, re);
450 			}
451 		}
452 	}
453 
454 	private DateFormat todayFormat = new SimpleDateFormat("HH:mm");
455 	private DateFormat inMonthFormat = new SimpleDateFormat("dd MMM");
456 	private DateFormat dateFormat = new SimpleDateFormat(ConnectConstants.DEFAULT_SHORT_DATE_FORMAT);
457 
458 	private String funkyFormat(Calendar date) {
459 		Calendar now = GregorianCalendar.getInstance();
460 		if (date.get(Calendar.YEAR) == now.get(Calendar.YEAR) && date.get(Calendar.MONTH) == now.get(Calendar.MONTH))
461 			if (date.get(Calendar.DAY_OF_MONTH) == now.get(Calendar.DAY_OF_MONTH))
462 				return todayFormat.format(date.getTime());
463 			else
464 				return inMonthFormat.format(date.getTime());
465 		else
466 			return dateFormat.format(date.getTime());
467 
468 	}
469 
470 	private class MyTableContentProvider implements IStructuredContentProvider {
471 		private static final long serialVersionUID = 7164029504991808317L;
472 
473 		public Object[] getElements(Object inputElement) {
474 			return (Object[]) inputElement;
475 		}
476 
477 		public void dispose() {
478 		}
479 
480 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
481 		}
482 	}
483 
484 	private final String LIST_WRAP_STYLE = "style='float:left;padding:0px;white-space:pre-wrap;'";
485 
486 	private String wrapThis(String value) {
487 		String wrapped = "<span " + LIST_WRAP_STYLE + " >" + ConnectUtils.replaceAmpersand(value) + "</span>";
488 		return wrapped;
489 	}
490 
491 	// //////////////////////
492 	// Life cycle management
493 	@Override
494 	public boolean setFocus() {
495 		tableViewer.getTable().setFocus();
496 		return true;
497 	}
498 
499 	@Override
500 	public void dispose() {
501 		super.dispose();
502 	}
503 
504 	public void refresh() {
505 		refreshFilteredList();
506 	}
507 }