View Javadoc
1   package org.argeo.tracker.core;
2   
3   import static org.argeo.activities.ActivitiesNames.ACTIVITIES_DUE_DATE;
4   import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
5   
6   import java.text.DateFormat;
7   import java.text.SimpleDateFormat;
8   import java.util.ArrayList;
9   import java.util.Calendar;
10  import java.util.Collections;
11  import java.util.GregorianCalendar;
12  import java.util.HashMap;
13  import java.util.LinkedHashMap;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.Map.Entry;
17  import java.util.stream.Collectors;
18  
19  import javax.jcr.Node;
20  import javax.jcr.NodeIterator;
21  import javax.jcr.Property;
22  import javax.jcr.RepositoryException;
23  import javax.jcr.Session;
24  import javax.jcr.query.Query;
25  import javax.jcr.query.QueryManager;
26  import javax.jcr.query.QueryResult;
27  
28  import org.argeo.activities.ActivitiesException;
29  import org.argeo.activities.ActivitiesNames;
30  import org.argeo.activities.ActivitiesService;
31  import org.argeo.activities.ActivitiesTypes;
32  import org.argeo.connect.AppService;
33  import org.argeo.connect.ConnectConstants;
34  import org.argeo.connect.ConnectNames;
35  import org.argeo.connect.UserAdminService;
36  import org.argeo.connect.util.ConnectJcrUtils;
37  import org.argeo.connect.util.ConnectUtils;
38  import org.argeo.connect.util.XPathUtils;
39  import org.argeo.eclipse.ui.EclipseUiUtils;
40  import org.argeo.tracker.TrackerConstants;
41  import org.argeo.tracker.TrackerException;
42  import org.argeo.tracker.TrackerNames;
43  import org.argeo.tracker.TrackerService;
44  import org.argeo.tracker.TrackerTypes;
45  
46  /** Centralise methods to ease implementation of the Tracker App */
47  public class TrackerUtils {
48  
49  	public static final Map<String, String> DEFAULT_COMPONENTS;
50  	static {
51  		Map<String, String> tmpMap = new LinkedHashMap<String, String>();
52  		tmpMap.put("Model", "Specification, design and data model");
53  		tmpMap.put("Backend", "Core components");
54  		tmpMap.put("Demo", "A basic instance for this project that can be freely shown");
55  		tmpMap.put("UI", "The user interface");
56  		tmpMap.put("Documentation", "Reference documentation");
57  		tmpMap.put("Integration", "Import, export, external APIs");
58  		tmpMap.put("QA", "Build, deployment, testing, documentation");
59  		DEFAULT_COMPONENTS = Collections.unmodifiableMap(tmpMap);
60  	}
61  
62  	// Define Issue status labels
63  	public static final Map<String, String> MAPS_ISSUE_PRIORITIES;
64  	static {
65  		Map<String, String> tmpMap = new LinkedHashMap<String, String>();
66  		tmpMap.put(TrackerConstants.TRACKER_PRIORITY_LOWEST + "", "Lowest");
67  		tmpMap.put(TrackerConstants.TRACKER_PRIORITY_LOW + "", "Low");
68  		tmpMap.put(TrackerConstants.TRACKER_PRIORITY_NORMAL + "", "Normal");
69  		tmpMap.put(TrackerConstants.TRACKER_PRIORITY_HIGH + "", "High");
70  		tmpMap.put(TrackerConstants.TRACKER_PRIORITY_HIGHEST + "", "Highest");
71  		MAPS_ISSUE_PRIORITIES = Collections.unmodifiableMap(tmpMap);
72  	}
73  
74  	public static final Map<String, String> MAPS_ISSUE_IMPORTANCES;
75  	static {
76  		Map<String, String> tmpMap = new LinkedHashMap<String, String>();
77  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_ENHANCEMENT + "", "Enhancement");
78  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_TRIVIAL + "", "Trivial");
79  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_MINOR + "", "Minor");
80  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_NORMAL + "", "Normal");
81  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_MAJOR + "", "Major");
82  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_CRITICAL + "", "Critical");
83  		tmpMap.put(TrackerConstants.TRACKER_IMPORTANCE_BLOCKER + "", "Blocker");
84  		MAPS_ISSUE_IMPORTANCES = Collections.unmodifiableMap(tmpMap);
85  	}
86  
87  	public static String issuesRelPath() {
88  		return TrackerNames.TRACKER_ISSUES;
89  	}
90  
91  	public static String componentsRelPath() {
92  		return TrackerNames.TRACKER_COMPONENTS;
93  	}
94  
95  	public static String versionsRelPath() {
96  		return TrackerNames.TRACKER_MILESTONES;
97  	}
98  
99  	public static String getRelevantPropName(Node category) {
100 		try {
101 			if (category.isNodeType(TrackerTypes.TRACKER_COMPONENT))
102 				return TrackerNames.TRACKER_COMPONENT_IDS;
103 			else if (category.isNodeType(TrackerTypes.TRACKER_MILESTONE))
104 				return TrackerNames.TRACKER_MILESTONE_UID;
105 			else if (category.isNodeType(TrackerTypes.TRACKER_VERSION)) {
106 				// TODO enhance the choice between milestone and version
107 				if (category.hasProperty(TrackerNames.TRACKER_RELEASE_DATE))
108 					return TrackerNames.TRACKER_VERSION_IDS;
109 				else
110 					return TrackerNames.TRACKER_MILESTONE_UID;
111 			} else
112 				throw new TrackerException("Unsupported category node type " + category.getMixinNodeTypes().toString()
113 						+ " for " + category.getPath());
114 		} catch (RepositoryException e) {
115 			throw new TrackerException("Cannot get relevant property name for category " + category, e);
116 		}
117 	}
118 
119 	/** Retrieve all projects that are visible for this session */
120 	public static NodeIterator getProjects(Session session, String projectParentPath) {
121 		try {
122 			StringBuilder builder = new StringBuilder();
123 			if (EclipseUiUtils.notEmpty(projectParentPath))
124 				builder.append(XPathUtils.descendantFrom(projectParentPath));
125 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_PROJECT).append(")");
126 			builder.append(" order by @").append(Property.JCR_TITLE);
127 			QueryResult result = XPathUtils.createQuery(session, builder.toString()).execute();
128 			return result.getNodes();
129 		} catch (RepositoryException e) {
130 			throw new TrackerException(
131 					"Unable to get projects under " + projectParentPath + " for session " + session.getUserID(), e);
132 		}
133 	}
134 
135 	public static NodeIterator getOpenMilestones(Node project, String filter) {
136 		try {
137 			StringBuilder builder = new StringBuilder();
138 			builder.append(XPathUtils.descendantFrom(project.getPath()));
139 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_MILESTONE).append(")");
140 			builder.append("[not(@").append(ConnectNames.CONNECT_CLOSE_DATE).append(")");
141 			if (EclipseUiUtils.notEmpty(filter))
142 				builder.append(" and ").append(XPathUtils.getFreeTextConstraint(filter));
143 			builder.append("]");
144 			builder.append(" order by @").append(Property.JCR_TITLE).append(" ascending");
145 			QueryResult result = XPathUtils.createQuery(project.getSession(), builder.toString()).execute();
146 			return result.getNodes();
147 		} catch (RepositoryException e) {
148 			throw new TrackerException("Unable to get milestones on " + project + " with filter:" + filter, e);
149 		}
150 	}
151 
152 	/**
153 	 * Simply returns the milestones defined for this project. Note that ordering by
154 	 * ID use lexical order that won't work for version, f.i. 2.1.23 & 2.1.4, it is
155 	 * caller duty to use a correct version comparator if the order is important
156 	 */
157 	public static NodeIterator getMilestones(Node project, String filter) {
158 		try {
159 			StringBuilder builder = new StringBuilder();
160 			Node parent = project.getNode(versionsRelPath());
161 			builder.append(XPathUtils.descendantFrom(parent.getPath()));
162 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_VERSION).append(")");
163 			builder.append("[not(@").append(TrackerNames.TRACKER_RELEASE_DATE).append(")");
164 			if (EclipseUiUtils.notEmpty(filter))
165 				builder.append(" and ").append(XPathUtils.getFreeTextConstraint(filter));
166 			builder.append("]");
167 			builder.append(" order by @").append(TrackerNames.TRACKER_ID).append(" descending");
168 			QueryResult result = XPathUtils.createQuery(parent.getSession(), builder.toString()).execute();
169 			return result.getNodes();
170 		} catch (RepositoryException e) {
171 			throw new TrackerException("Unable to get milestones on " + project + " with filter:" + filter, e);
172 		}
173 	}
174 
175 	public static List<String> getMilestoneIds(Node project, String filter) {
176 		NodeIterator nit = getMilestones(project, filter);
177 		List<String> milestoneIds = new ArrayList<String>();
178 		while (nit.hasNext()) {
179 			Node currNode = nit.nextNode();
180 			milestoneIds.add(ConnectJcrUtils.get(currNode, TrackerNames.TRACKER_ID));
181 		}
182 		return milestoneIds;
183 	}
184 
185 	public static NodeIterator getVersions(Node project, String filter) throws RepositoryException {
186 		StringBuilder builder = new StringBuilder();
187 		// Node parent = project.getNode(versionsRelPath());
188 		builder.append(XPathUtils.descendantFrom(project.getPath()));
189 		builder.append("//element(*, ").append(TrackerTypes.TRACKER_VERSION).append(")");
190 		builder.append("[@").append(TrackerNames.TRACKER_RELEASE_DATE);
191 		if (EclipseUiUtils.notEmpty(filter))
192 			builder.append(" and ").append(XPathUtils.getFreeTextConstraint(filter));
193 		builder.append("]");
194 		builder.append(" order by @").append(TrackerNames.TRACKER_ID).append(" descending");
195 		QueryResult result = XPathUtils.createQuery(project.getSession(), builder.toString()).execute();
196 		return result.getNodes();
197 	}
198 
199 	public static List<String> getVersionIds(Node project, String filter) {
200 		try {
201 			NodeIterator nit = getVersions(project, filter);
202 			List<String> versionIds = new ArrayList<String>();
203 			while (nit.hasNext()) {
204 				Node currNode = nit.nextNode();
205 				versionIds.add(ConnectJcrUtils.get(currNode, TrackerNames.TRACKER_ID));
206 			}
207 			return versionIds;
208 		} catch (RepositoryException e) {
209 			throw new TrackerException("Unable to get version ids on " + project + " with filter:" + filter, e);
210 		}
211 	}
212 
213 	public static NodeIterator getTasks(Node project, String filter) {
214 		try {
215 			StringBuilder builder = new StringBuilder();
216 			builder.append(XPathUtils.descendantFrom(project.getPath()));
217 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_TASK).append(")");
218 			if (EclipseUiUtils.notEmpty(filter))
219 				builder.append("[").append(XPathUtils.getFreeTextConstraint(filter)).append("]");
220 			builder.append(" order by @").append(TrackerNames.TRACKER_ID);
221 			QueryResult result = XPathUtils.createQuery(project.getSession(), builder.toString()).execute();
222 			return result.getNodes();
223 		} catch (RepositoryException e) {
224 			throw new TrackerException("Unable to get issues for " + project + " with filter: " + filter, e);
225 		}
226 	}
227 
228 	public static NodeIterator getIssues(Node project, String filter) {
229 		try {
230 			StringBuilder builder = new StringBuilder();
231 			Node parent = project.getNode(issuesRelPath());
232 			builder.append(XPathUtils.descendantFrom(parent.getPath()));
233 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_ISSUE).append(")");
234 			if (EclipseUiUtils.notEmpty(filter))
235 				builder.append("[").append(XPathUtils.getFreeTextConstraint(filter)).append("]");
236 			builder.append(" order by @").append(TrackerNames.TRACKER_ID);
237 			// .append(" descending");
238 			QueryManager qm = project.getSession().getWorkspace().getQueryManager();
239 			QueryResult result = qm.createQuery(builder.toString(), ConnectConstants.QUERY_XPATH).execute();
240 			return result.getNodes();
241 		} catch (RepositoryException e) {
242 			throw new TrackerException("Unable to get issues for " + project + " with filter: " + filter, e);
243 		}
244 	}
245 
246 	/**
247 	 * Simply requests all issues of a project that are referencing this category (a
248 	 * version, a milestone or a component)
249 	 */
250 	public static NodeIterator getIssues(Node project, String filter, String propName, String catId) {
251 		return getIssues(project, filter, propName, catId, false);
252 	}
253 
254 	/** Simply checks if an issue is closed */
255 	public static boolean isIssueClosed(Node issue) {
256 		try {
257 			// TODO enhance definition of closed status
258 			return issue.hasProperty(ConnectNames.CONNECT_CLOSE_DATE);
259 		} catch (RepositoryException e) {
260 			throw new TrackerException("Unable to check closed status of " + issue, e);
261 		}
262 	}
263 
264 	public static NodeIterator getIssues(Node project, String filter, String propName, String catId,
265 			boolean onlyOpenTasks) {
266 		try {
267 			StringBuilder builder = new StringBuilder();
268 			// Node parent = project.getNode(issuesRelPath());
269 			builder.append(XPathUtils.descendantFrom(project.getPath()));
270 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_TASK).append(")");
271 
272 			StringBuilder tmpBuilder = new StringBuilder();
273 
274 			String andStr = " and ";
275 			if (EclipseUiUtils.notEmpty(catId)) {
276 				tmpBuilder.append(XPathUtils.getPropertyEquals(propName, catId));
277 				tmpBuilder.append(andStr);
278 			}
279 
280 			if (EclipseUiUtils.notEmpty(filter)) {
281 				tmpBuilder.append(XPathUtils.getFreeTextConstraint(filter));
282 				tmpBuilder.append(andStr);
283 			}
284 
285 			if (onlyOpenTasks) {
286 				tmpBuilder.append(" not(@");
287 				tmpBuilder.append(ConnectNames.CONNECT_CLOSE_DATE);
288 				tmpBuilder.append(")");
289 				tmpBuilder.append(andStr);
290 			}
291 
292 			if (tmpBuilder.length() > 0)
293 				builder.append("[").append(tmpBuilder.substring(0, tmpBuilder.length() - andStr.length())).append("]");
294 
295 			builder.append(" order by @" + TrackerNames.TRACKER_ID);
296 			Query xpathQuery = XPathUtils.createQuery(project.getSession(), builder.toString());
297 			QueryResult result = xpathQuery.execute();
298 			return result.getNodes();
299 		} catch (RepositoryException e) {
300 			throw new TrackerException("Unable to get issues for " + project + " with filter: " + filter, e);
301 		}
302 	}
303 
304 	private final static String UNKNOWN = "Unknown";
305 	private final static String OTHERS = "Others";
306 
307 	public static Map<String, String> getOpenTasksByAssignee(UserAdminService uas, Node project, String milestoneUid,
308 			int maxSize) {
309 		try {
310 			StringBuilder builder = new StringBuilder();
311 			builder.append(XPathUtils.descendantFrom(project.getPath()));
312 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_TASK).append(")");
313 			builder.append("[");
314 			if (EclipseUiUtils.notEmpty(milestoneUid)) {
315 				builder.append(XPathUtils.getPropertyEquals(TrackerNames.TRACKER_MILESTONE_UID, milestoneUid));
316 				builder.append(" and ");
317 			}
318 			builder.append("( not(@").append(ConnectNames.CONNECT_CLOSE_DATE).append("))");
319 			builder.append("]");
320 			QueryResult result = XPathUtils.createQuery(project.getSession(), builder.toString()).execute();
321 
322 			Map<String, Long> nodeCount = countAndLimit(result.getNodes(), ActivitiesNames.ACTIVITIES_ASSIGNED_TO,
323 					maxSize);
324 			Map<String, String> openTasks = new LinkedHashMap<String, String>();
325 			for (String key : nodeCount.keySet()) {
326 				String dName;
327 				if (OTHERS.equals(key) || UNKNOWN.equals(key))
328 					dName = key;
329 				else
330 					dName = uas.getUserDisplayName(key);
331 				openTasks.put(dName, nodeCount.get(key).toString());
332 			}
333 			return openTasks;
334 		} catch (RepositoryException e) {
335 			throw new TrackerException("Unable to get issues for " + project + " with filter: ", e);
336 		}
337 	}
338 
339 	private static Map<String, Long> countAndLimit(NodeIterator nodes, String propName, int limit) {
340 		// First: count occurrences:
341 		Map<String, Long> nodeCount = new HashMap<>();
342 		while (nodes.hasNext()) {
343 			Node currNode = nodes.nextNode();
344 			String id = ConnectJcrUtils.get(currNode, propName);
345 			if (EclipseUiUtils.isEmpty(id))
346 				id = UNKNOWN;
347 			if (nodeCount.containsKey(id))
348 				nodeCount.replace(id, nodeCount.get(id) + 1);
349 			else
350 				nodeCount.put(id, 1l);
351 		}
352 
353 		if (nodeCount.size() < limit)
354 			return nodeCount;
355 
356 		Map<String, Long> orderedCount = sortByValue(nodeCount);
357 		Map<String, Long> limitedCount = new LinkedHashMap<String, Long>();
358 
359 		int i = 0;
360 		long otherCount = 0;
361 		for (String key : orderedCount.keySet()) {
362 			if (i < limit)
363 				limitedCount.put(key, orderedCount.get(key));
364 			else
365 				otherCount += orderedCount.get(key);
366 			i++;
367 		}
368 		limitedCount.put(OTHERS, otherCount);
369 		return limitedCount;
370 	}
371 
372 	private static Map<String, Long> sortByValue(Map<String, Long> map) {
373 		return map.entrySet().stream().sorted(Map.Entry.comparingByValue(Collections.reverseOrder()))
374 				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
375 	}
376 
377 	public static NodeIterator getAllVersions(Node project, String filter) {
378 		try {
379 			StringBuilder builder = new StringBuilder();
380 			Node parent = project.getNode(versionsRelPath());
381 			builder.append(XPathUtils.descendantFrom(parent.getPath()));
382 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_VERSION).append(")");
383 			if (EclipseUiUtils.notEmpty(filter))
384 				builder.append("[").append(XPathUtils.getFreeTextConstraint(filter)).append("]");
385 			builder.append(" order by @").append(TrackerNames.TRACKER_ID).append(" descending");
386 			QueryManager qm = parent.getSession().getWorkspace().getQueryManager();
387 			QueryResult result = qm.createQuery(builder.toString(), ConnectConstants.QUERY_XPATH).execute();
388 			return result.getNodes();
389 		} catch (RepositoryException e) {
390 			throw new TrackerException("Unable to get version for " + project + " with filter: " + filter, e);
391 		}
392 	}
393 
394 	public static List<String> getComponentIds(Node project, String filter) {
395 		NodeIterator nit = getComponents(project, filter);
396 		List<String> componentIds = new ArrayList<String>();
397 		while (nit.hasNext()) {
398 			Node currNode = nit.nextNode();
399 			componentIds.add(ConnectJcrUtils.get(currNode, TrackerNames.TRACKER_ID));
400 		}
401 		return componentIds;
402 	}
403 
404 	public static NodeIterator getComponents(Node project, String filter) {
405 		try {
406 			StringBuilder builder = new StringBuilder();
407 			builder.append(XPathUtils.descendantFrom(project.getPath()));
408 			builder.append("//element(*, ").append(TrackerTypes.TRACKER_COMPONENT).append(")");
409 			if (EclipseUiUtils.notEmpty(filter))
410 				builder.append("[").append(XPathUtils.getFreeTextConstraint(filter)).append("]");
411 			builder.append(" order by @").append(TrackerNames.TRACKER_ID).append(" ascending");
412 			QueryResult result = XPathUtils.createQuery(project.getSession(), builder.toString()).execute();
413 			return result.getNodes();
414 		} catch (RepositoryException e) {
415 			throw new TrackerException("Unable to get components for " + project + " with filter: " + filter, e);
416 		}
417 	}
418 
419 	public static Node getVersionById(Node project, String versionId) {
420 		try {
421 			// Node parent = project.getNode(versionsRelPath());
422 			String xpathQueryStr = XPathUtils.descendantFrom(project.getPath());
423 			xpathQueryStr += "//element(*, " + TrackerTypes.TRACKER_VERSION + ")";
424 			xpathQueryStr += "[" + XPathUtils.getPropertyEquals(TrackerNames.TRACKER_ID, versionId) + "]";
425 			Query xpathQuery = XPathUtils.createQuery(project.getSession(), xpathQueryStr);
426 			NodeIterator results = xpathQuery.execute().getNodes();
427 			if (!results.hasNext())
428 				return null;
429 			else if (results.getSize() > 1)
430 				throw new TrackerException(
431 						"Found " + results.getSize() + " versions with Id " + versionId + " under " + project);
432 			else
433 				return results.nextNode();
434 		} catch (RepositoryException e) {
435 			throw new TrackerException("Unable to get version " + versionId + " under " + project, e);
436 		}
437 	}
438 
439 	public static Node getComponentById(Node project, String officeId) {
440 		try {
441 			Node parent = project.getNode(componentsRelPath());
442 			String xpathQueryStr = XPathUtils.descendantFrom(parent.getPath());
443 			xpathQueryStr += "//element(*, " + TrackerTypes.TRACKER_COMPONENT + ")";
444 			xpathQueryStr += "[" + XPathUtils.getPropertyEquals(TrackerNames.TRACKER_ID, officeId) + "]";
445 			QueryManager qm = parent.getSession().getWorkspace().getQueryManager();
446 			Query xpathQuery = qm.createQuery(xpathQueryStr, ConnectConstants.QUERY_XPATH);
447 			NodeIterator results = xpathQuery.execute().getNodes();
448 			if (!results.hasNext())
449 				return null;
450 			else if (results.getSize() > 1)
451 				throw new TrackerException(
452 						"Found " + results.getSize() + " versions with Id " + officeId + " under " + project);
453 			else
454 				return results.nextNode();
455 		} catch (RepositoryException e) {
456 			throw new TrackerException("Unable to get component " + officeId + " under " + project, e);
457 		}
458 	}
459 
460 	public static long getIssueNb(Node category, boolean onlyOpen) {
461 		Node project = getProjectFromChild(category);
462 		String relProp = getRelevantPropName(category);
463 		NodeIterator nit = getIssues(project, null, relProp, ConnectJcrUtils.get(category, TrackerNames.TRACKER_ID),
464 				onlyOpen);
465 		return nit.getSize();
466 	}
467 
468 	public static Node getRelatedProject(AppService appService, Node node) {
469 		String refUid = ConnectJcrUtils.get(node, TrackerNames.TRACKER_PROJECT_UID);
470 		if (EclipseUiUtils.notEmpty(refUid))
471 			return appService.getEntityByUid(ConnectJcrUtils.getSession(node), null, refUid);
472 		else
473 			return null;
474 	}
475 
476 	public static Node getMilestone(AppService appService, Node task) {
477 		String muid = ConnectJcrUtils.get(task, TrackerNames.TRACKER_MILESTONE_UID);
478 		if (EclipseUiUtils.notEmpty(muid))
479 			return appService.getEntityByUid(ConnectJcrUtils.getSession(task), null, muid);
480 		return null;
481 	}
482 
483 	public static Node getProjectFromChild(Node issue) {
484 		try {
485 			// Not very clean. Will fail in the case of a draft issue that is
486 			// still in the home of current user, among others
487 			Node parent = issue;
488 			while (!parent.isNodeType(TrackerTypes.TRACKER_PROJECT))
489 				parent = parent.getParent();
490 			return parent;
491 		} catch (RepositoryException e) {
492 			throw new TrackerException("Unable to get project for " + issue, e);
493 		}
494 	}
495 
496 	public static String getImportanceLabel(Node issue) {
497 		Long importance = ConnectJcrUtils.getLongValue(issue, TrackerNames.TRACKER_IMPORTANCE);
498 		if (importance != null)
499 			return TrackerUtils.MAPS_ISSUE_IMPORTANCES.get(importance.toString());
500 		else
501 			return "";
502 		// try {
503 		// int importance = (int)
504 		// issue.getProperty(TrackerNames.TRACKER_IMPORTANCE).getLong();
505 		// Integer importanceI = new Integer(importance);
506 		// return TrackerUtils.MAPS_ISSUE_IMPORTANCES.get(importanceI);
507 		// } catch (RepositoryException e) {
508 		// throw new TrackerException("Unable to get importance label for " +
509 		// issue, e);
510 		// }
511 	}
512 
513 	public static String getPriorityLabel(Node issue) {
514 		Long priority = ConnectJcrUtils.getLongValue(issue, TrackerNames.TRACKER_PRIORITY);
515 		if (priority != null)
516 			return TrackerUtils.MAPS_ISSUE_PRIORITIES.get(priority.toString());
517 		else
518 			return "";
519 		// try {
520 		// int priority = (int)
521 		// issue.getProperty(TrackerNames.TRACKER_PRIORITY).getLong();
522 		// Integer priorityI = new Integer(priority);
523 		// return TrackerUtils.MAPS_ISSUE_PRIORITIES.get(priorityI);
524 		// } catch (RepositoryException e) {
525 		// throw new TrackerException("Unable to get priority label for " +
526 		// issue, e);
527 		// }
528 	}
529 
530 	public static String getCreationLabel(UserAdminService userAdminService, Node issue) {
531 		try {
532 			String result = "";
533 			if (issue.hasProperty(Property.JCR_CREATED_BY)) {
534 				String userId = issue.getProperty(Property.JCR_CREATED_BY).getString();
535 				String displayName = userAdminService.getUserDisplayName(userId);
536 				if (EclipseUiUtils.notEmpty(displayName))
537 					result = displayName;
538 			}
539 
540 			if (issue.hasProperty(Property.JCR_CREATED)) {
541 				result += " on " + ConnectJcrUtils.getDateFormattedAsString(issue, Property.JCR_CREATED,
542 						ConnectConstants.DEFAULT_DATE_TIME_FORMAT);
543 			}
544 			return result;
545 		} catch (RepositoryException e) {
546 			throw new TrackerException("Unable to get creation label for " + issue, e);
547 		}
548 	}
549 
550 	private static DateFormat dtFormat = new SimpleDateFormat(ConnectConstants.DEFAULT_DATE_TIME_FORMAT);
551 
552 	public static String getStatusText(UserAdminService userAdminService, ActivitiesService activityService,
553 			Node issue) {
554 		try {
555 			StringBuilder builder = new StringBuilder();
556 
557 			// status, importance, priority
558 			builder.append("<b> Status: </b>")
559 					.append(ConnectJcrUtils.get(issue, ActivitiesNames.ACTIVITIES_TASK_STATUS)).append(" ");
560 			builder.append("[").append(TrackerUtils.getImportanceLabel(issue)).append("/")
561 					.append(TrackerUtils.getPriorityLabel(issue)).append("] - ");
562 
563 			// milestone, version
564 			Node milestone = TrackerUtils.getMilestone(activityService, issue);
565 			if (milestone != null)
566 				builder.append("<b>Target milestone: </b> ").append(ConnectJcrUtils.get(milestone, Property.JCR_TITLE))
567 						.append(" - ");
568 			String versionId = ConnectJcrUtils.getMultiAsString(issue, TrackerNames.TRACKER_VERSION_IDS, ", ");
569 			builder.append(" - ");
570 			if (notEmpty(versionId))
571 				builder.append("<b>Affect version: </b> ").append(versionId).append(" - ");
572 
573 			// assigned to
574 			if (activityService.isTaskDone(issue)) {
575 				String closeBy = ConnectJcrUtils.get(issue, ConnectNames.CONNECT_CLOSED_BY);
576 				Calendar closedDate = issue.getProperty(ConnectNames.CONNECT_CLOSE_DATE).getDate();
577 				builder.append(" - Marked as closed by ").append(closeBy);
578 				builder.append(" on ").append(dtFormat.format(closedDate.getTime())).append(".");
579 			} else {
580 				String assignedToId = ConnectJcrUtils.get(issue, ActivitiesNames.ACTIVITIES_ASSIGNED_TO);
581 				String dName = userAdminService.getUserDisplayName(assignedToId);
582 				if (notEmpty(dName))
583 					builder.append("<b>Assigned to: </b>").append(dName);
584 			}
585 			return ConnectUtils.replaceAmpersand(builder.toString());
586 		} catch (RepositoryException e) {
587 			throw new TrackerException("Unable to get status text for issue " + issue, e);
588 		}
589 	}
590 
591 	// DEFAULT CONFIGURATIONS
592 	public static void createDefaultComponents(TrackerService issueService, Node parentProject)
593 			throws RepositoryException {
594 		for (String title : DEFAULT_COMPONENTS.keySet()) {
595 			issueService.createComponent(parentProject, title, title, DEFAULT_COMPONENTS.get(title));
596 		}
597 	}
598 
599 	/**
600 	 * Provides the key for a given value. we assume it is a bijection each value is
601 	 * only linked to one key
602 	 */
603 	public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
604 		for (Entry<T, E> entry : map.entrySet()) {
605 			if (value.equals(entry.getValue())) {
606 				return entry.getKey();
607 			}
608 		}
609 		return null;
610 	}
611 
612 	public static String normalizeDn(String dn) {
613 		// FIXME dirty workaround for the DN key case issue
614 		String lowerCased = dn.replaceAll("UID=", "uid=").replaceAll("CN=", "cn=").replaceAll("DC=", "dc=")
615 				.replaceAll("OU=", "ou=").replaceAll(", ", ",");
616 		return lowerCased;
617 	}
618 
619 	public static long getProjectOverdueTasksNumber(Node project) {
620 		StringBuilder builder = new StringBuilder();
621 		try {
622 			builder.append(XPathUtils.descendantFrom(project.getPath()));
623 			builder.append("//element(*, ").append(ActivitiesTypes.ACTIVITIES_TASK).append(")");
624 			// Past due date
625 			Calendar now = GregorianCalendar.getInstance();
626 			String overdueCond = XPathUtils.getPropertyDateComparaison(ACTIVITIES_DUE_DATE, now, "<");
627 			// Only opened tasks
628 			String notClosedCond = "not(@" + ConnectNames.CONNECT_CLOSE_DATE + ")";
629 			builder.append("[").append(XPathUtils.localAnd(overdueCond, notClosedCond)).append("]");
630 			Query query = XPathUtils.createQuery(project.getSession(), builder.toString());
631 			return query.execute().getNodes().getSize();
632 		} catch (RepositoryException e) {
633 			throw new ActivitiesException("Unable to get overdue tasks number for " + project);
634 		}
635 	}
636 }