Project

General

Profile

« Previous | Next » 

Revision 58438

link to api from history

View differences:

modules/dnet-modular-workflows-ui/trunk/src/main/java/eu/dnetlib/functionality/modular/ui/workflows/controllers/WorkflowsController.java
65 65

  
66 66
/**
67 67
 * Web controller for the UI
68
 * 
68
 *
69 69
 * @author Michele Artini
70 70
 */
71 71

  
......
77 77
		@Override
78 78
		public ProcessListEntry apply(final Map<String, String> input) {
79 79
			final String name = input.get(WorkflowsConstants.SYSTEM_WF_PROFILE_NAME);
80
			
81
			String repo = "";
82
			if (input.containsKey(WorkflowsConstants.DATAPROVIDER_NAME)) {
83
				repo += input.get(WorkflowsConstants.DATAPROVIDER_NAME);
84
			}
80

  
81
			final String repo = input.containsKey(WorkflowsConstants.DATAPROVIDER_NAME) ? input.get(WorkflowsConstants.DATAPROVIDER_NAME) : "";
82
			final String repoId = input.containsKey(WorkflowsConstants.DATAPROVIDER_ORIGINALID) ? input.get(WorkflowsConstants.DATAPROVIDER_ORIGINALID) : "";
83
			final String apiId = input.containsKey(WorkflowsConstants.DATAPROVIDER_INTERFACE) ? input.get(WorkflowsConstants.DATAPROVIDER_INTERFACE) : "";
84

  
85 85
			final String procId = input.get(WorkflowsConstants.SYSTEM_WF_PROCESS_ID);
86 86
			final String wfId = input.get(WorkflowsConstants.SYSTEM_WF_PROFILE_ID);
87 87
			final String family = input.get(WorkflowsConstants.SYSTEM_WF_PROFILE_FAMILY);
88 88
			final long date = NumberUtils.toLong(input.get(LogMessage.LOG_DATE_FIELD), 0);
89 89
			final String status = Boolean.valueOf(input.get(WorkflowsConstants.SYSTEM_COMPLETED_SUCCESSFULLY)) ? "SUCCESS" : "FAILURE";
90
		
91
			return new ProcessListEntry(procId, wfId, name, family, status, date, repo);
90

  
91
			return new ProcessListEntry(procId, wfId, name, family, status, date, repo, repoId, apiId);
92 92
		}
93 93
	}
94 94

  
......
119 119
	private static final Log log = LogFactory.getLog(WorkflowsController.class);
120 120

  
121 121
	@RequestMapping("/ui/list_metaworkflows.json")
122
	public @ResponseBody List<MetaWorkflowDescriptor> listMetaWorflowsForSection(@RequestParam(value = "section", required = false) final String sectionName, @RequestParam(value = "dsId", required = false) final String dsId)
122
	public @ResponseBody List<MetaWorkflowDescriptor> listMetaWorflowsForSection(@RequestParam(value = "section", required = false) final String sectionName,
123
			@RequestParam(value = "dsId", required = false) final String dsId)
123 124
			throws ISLookUpException, IOException {
124 125
		if (sectionName != null) {
125 126
			return workflowSectionGrouper.listMetaWorflowsForSection(sectionName);
......
148 149
	@RequestMapping("/ui/wf_atomic_workflow.img")
149 150
	public void showAtomicWorkflow(final HttpServletResponse response, @RequestParam(value = "id", required = true) final String id) throws Exception {
150 151

  
151
		String xml = profileToSarasvatiConverter.getSarasvatiWorkflow(id).getWorkflowXml();
152
		Set<String> notConfiguredNodes = isLookupClient.getNotConfiguredNodes(id);
153
		BufferedImage image = processGraphGenerator.getWfDescImage(id, xml, notConfiguredNodes);
152
		final String xml = profileToSarasvatiConverter.getSarasvatiWorkflow(id).getWorkflowXml();
153
		final Set<String> notConfiguredNodes = isLookupClient.getNotConfiguredNodes(id);
154
		final BufferedImage image = processGraphGenerator.getWfDescImage(id, xml, notConfiguredNodes);
154 155
		sendImage(response, image);
155 156
	}
156 157

  
157 158
	private void sendImage(final HttpServletResponse response, final BufferedImage image) throws IOException {
158 159
		response.setContentType("image/png");
159
		OutputStream out = response.getOutputStream();
160
		final OutputStream out = response.getOutputStream();
160 161
		ImageIO.write(image, "png", out);
161 162
		out.flush();
162 163
		out.close();
......
181 182

  
182 183
	@RequestMapping("/ui/wf_metaworkflow.edit")
183 184
	public @ResponseBody boolean scheduleMetaWorkflow(@RequestParam(value = "json", required = true) final String json) throws Exception {
184
		
185
		final AdvancedMetaWorkflowDescriptor info = (new Gson()).fromJson(json, AdvancedMetaWorkflowDescriptor.class);
186 185

  
186
		final AdvancedMetaWorkflowDescriptor info = new Gson().fromJson(json, AdvancedMetaWorkflowDescriptor.class);
187

  
187 188
		log.info("Updating workflow " + info.getName());
188 189

  
189 190
		final String xml = isLookupClient.getProfile(info.getWfId());
190
		boolean res = isRegistryClient.updateSarasvatiMetaWorkflow(info.getWfId(), xml, info);
191
		final boolean res = isRegistryClient.updateSarasvatiMetaWorkflow(info.getWfId(), xml, info);
191 192

  
192 193
		return res;
193 194
	}
......
198 199

  
199 200
		if (name.trim().length() > 0) {
200 201
			final String xml = isLookupClient.getProfile(id);
201
			SAXReader reader = new SAXReader();
202
			Document doc = reader.read(new StringReader(xml));
202
			final SAXReader reader = new SAXReader();
203
			final Document doc = reader.read(new StringReader(xml));
203 204
			doc.selectSingleNode("//METAWORKFLOW_NAME").setText(name);
204
			for (Object o : doc.selectNodes("//WORKFLOW")) {
205
				Element n = (Element) o;
206
				String atomWfXml = isLookupClient.getProfile(n.valueOf("@id"));
207
				String newAtomWfId = isRegistryClient.registerProfile(atomWfXml);
205
			for (final Object o : doc.selectNodes("//WORKFLOW")) {
206
				final Element n = (Element) o;
207
				final String atomWfXml = isLookupClient.getProfile(n.valueOf("@id"));
208
				final String newAtomWfId = isRegistryClient.registerProfile(atomWfXml);
208 209
				n.addAttribute("id", newAtomWfId);
209 210
			}
210 211
			return isRegistryClient.registerProfile(doc.asXML());
211
		} else throw new IllegalArgumentException("Name is empty");
212
		} else {
213
			throw new IllegalArgumentException("Name is empty");
214
		}
212 215
	}
213 216

  
214 217
	@RequestMapping("/ui/wf_proc_node.json")
......
217 220

  
218 221
		final NodeToken token = findNodeToken(pid, nid);
219 222

  
220
		final NodeTokenInfo info = (token == null) ? new NodeTokenInfo(findNodeName(pid, nid)) : new NodeTokenInfo(token);
223
		final NodeTokenInfo info = token == null ? new NodeTokenInfo(findNodeName(pid, nid)) : new NodeTokenInfo(token);
221 224

  
222 225
		return info;
223 226
	}
......
225 228
	private NodeToken findNodeToken(final String pid, final long nid) {
226 229
		final GraphProcess process = graphProcessRegistry.findProcess(pid);
227 230
		if (process != null) {
228
			for (NodeToken token : process.getNodeTokens()) {
229
				if (token.getNode().getId() == nid) return token;
231
			for (final NodeToken token : process.getNodeTokens()) {
232
				if (token.getNode().getId() == nid) { return token; }
230 233
			}
231 234
		}
232 235
		return null;
......
235 238
	private String findNodeName(final String pid, final long nid) {
236 239
		final GraphProcess process = graphProcessRegistry.findProcess(pid);
237 240
		if (process != null) {
238
			for (Node node : process.getGraph().getNodes()) {
239
				if (node.getId() == nid) return node.getName();
241
			for (final Node node : process.getGraph().getNodes()) {
242
				if (node.getId() == nid) { return node.getName(); }
240 243
			}
241 244
		}
242 245
		return "-";
243 246
	}
244 247

  
245

  
246 248
	@RequestMapping("/ui/wf_proc.img")
247 249
	public void showProcessWorkflow(final HttpServletResponse response, @RequestParam(value = "id", required = true) final String id) throws Exception {
248
		BufferedImage image = processGraphGenerator.getProcessImage(id);
250
		final BufferedImage image = processGraphGenerator.getProcessImage(id);
249 251
		sendImage(response, image);
250 252
	}
251 253

  
252 254
	@RequestMapping("/ui/wf_proc.kill")
253 255
	public @ResponseBody boolean killProcessWorkflow(@RequestParam(value = "id", required = true) final String id) throws Exception {
254
		GraphProcess proc = graphProcessRegistry.findProcess(id);
256
		final GraphProcess proc = graphProcessRegistry.findProcess(id);
255 257
		proc.setState(ProcessState.Canceled);
256 258
		return true;
257 259
	}
258
	
260

  
259 261
	@RequestMapping("/ui/wf_journal.range")
260 262
	public @ResponseBody Collection<ProcessListEntry> rangeWfJournal(@RequestParam(value = "start", required = true) final String start,
261 263
			@RequestParam(value = "end", required = true) final String end) throws Exception {
262
		
264

  
263 265
		final Map<String, ProcessListEntry> res = Maps.newHashMap();
264
		
266

  
265 267
		final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
266 268
		final DateTime startDate = formatter.parseDateTime(start);
267 269
		final DateTime endDate = formatter.parseDateTime(end).plusHours(23).plusMinutes(59).plusSeconds(59);
268
		
270

  
269 271
		final Iterator<ProcessListEntry> iter = Iterators.transform(dnetLogger.range(startDate.toDate(), endDate.toDate()), new JournalEntryFunction());
270 272
		while (iter.hasNext()) {
271
			ProcessListEntry e = iter.next();
273
			final ProcessListEntry e = iter.next();
272 274
			res.put(e.getProcId(), e);
273 275
		}
274
		
275
		long now = DateUtils.now();
276

  
277
		final long now = DateUtils.now();
276 278
		if (startDate.isBefore(now) && endDate.isAfter(now)) {
277
			for (String pid : graphProcessRegistry.listIdentifiers()) {
279
			for (final String pid : graphProcessRegistry.listIdentifiers()) {
278 280
				final GraphProcess proc = graphProcessRegistry.findProcess(pid);
279 281
				res.put(pid, new ProcessListEntry(pid, proc));
280 282
			}
281 283
		}
282
	
284

  
283 285
		return res.values();
284
		
286

  
285 287
	}
286 288

  
287 289
	@RequestMapping("/ui/wf_journal.find")
288 290
	public @ResponseBody Collection<ProcessListEntry> findWfJournal(@RequestParam(value = "wfs", required = true) final String wfs) {
289 291
		final Map<String, ProcessListEntry> res = Maps.newHashMap();
290
		
291
		final Set<String> wfFilter = (new Gson()).fromJson(wfs, new TypeToken<Set<String>>(){}.getType());
292
				
293
		for (String wfId : wfFilter) {
294
			final Iterator<ProcessListEntry> iter = Iterators.transform(dnetLogger.find(WorkflowsConstants.SYSTEM_WF_PROFILE_ID, wfId), new JournalEntryFunction());
292

  
293
		final Set<String> wfFilter = new Gson().fromJson(wfs, new TypeToken<Set<String>>() {}.getType());
294

  
295
		for (final String wfId : wfFilter) {
296
			final Iterator<ProcessListEntry> iter =
297
					Iterators.transform(dnetLogger.find(WorkflowsConstants.SYSTEM_WF_PROFILE_ID, wfId), new JournalEntryFunction());
295 298
			while (iter.hasNext()) {
296
				ProcessListEntry e = iter.next();
299
				final ProcessListEntry e = iter.next();
297 300
				res.put(e.getProcId(), e);
298 301
			}
299 302
		}
300
		
301
		for (String pid : graphProcessRegistry.listIdentifiers()) {
303

  
304
		for (final String pid : graphProcessRegistry.listIdentifiers()) {
302 305
			final GraphProcess proc = graphProcessRegistry.findProcess(pid);
303 306
			if (wfFilter.contains(ProcessUtils.calculateWfId(proc))) {
304 307
				res.put(pid, new ProcessListEntry(pid, proc));
305 308
			}
306 309
		}
307
		
310

  
308 311
		return res.values();
309 312
	}
310
		
313

  
311 314
	@RequestMapping("/ui/wf_journal_byFamily.find")
312
	public @ResponseBody Collection<ProcessListEntry> findWfJournalByFamily(@RequestParam(value = "family", required = true) final String family) throws IOException {
313
		final Iterator<ProcessListEntry> iter = Iterators.transform(dnetLogger.find(WorkflowsConstants.SYSTEM_WF_PROFILE_FAMILY, family), new JournalEntryFunction());
315
	public @ResponseBody Collection<ProcessListEntry> findWfJournalByFamily(@RequestParam(value = "family", required = true) final String family)
316
			throws IOException {
317
		final Iterator<ProcessListEntry> iter =
318
				Iterators.transform(dnetLogger.find(WorkflowsConstants.SYSTEM_WF_PROFILE_FAMILY, family), new JournalEntryFunction());
314 319
		return Lists.newArrayList(iter);
315 320
	}
316
		
321

  
317 322
	@RequestMapping("/ui/wf_journal.get")
318 323
	public @ResponseBody Map<String, Object> getWfJournalLog(@RequestParam(value = "id", required = true) final String id) throws Exception {
319 324
		final Map<String, Object> res = Maps.newHashMap();
320
		
325

  
321 326
		final Map<String, String> logs = dnetLogger.findOne("system:processId", id);
322 327

  
323 328
		if (logs != null && !logs.isEmpty()) {
324 329
			final List<String> keys = Lists.newArrayList(logs.keySet());
325 330
			Collections.sort(keys);
326
			
331

  
327 332
			final List<Map<String, String>> journalEntry = Lists.newArrayList();
328
			for (String k : keys) {
333
			for (final String k : keys) {
329 334
				final Map<String, String> m = Maps.newHashMap();
330 335
				m.put("name", k);
331 336
				m.put("value", logs.get(k));
......
333 338
			}
334 339
			res.put("journal", journalEntry);
335 340
		}
336
		
341

  
337 342
		final GraphProcess process = graphProcessRegistry.findProcess(id);
338
	
343

  
339 344
		if (process != null) {
340
			final String mapContent = (process.getState() == ProcessState.Created) ? "" : processGraphGenerator.getProcessImageMap(id);
341
	
345
			final String mapContent = process.getState() == ProcessState.Created ? "" : processGraphGenerator.getProcessImageMap(id);
346

  
342 347
			String status = "";
343 348
			if (!process.isComplete()) {
344 349
				status = process.getState().toString().toUpperCase();
......
347 352
			} else {
348 353
				status = "FAILURE";
349 354
			}
350
	
351
			final String img = (process.getState() == ProcessState.Created) ? "../resources/img/notStarted.gif" : "wf_proc.img?id=" + id + "&t=" + DateUtils.now();
352
			
355

  
356
			final String img =
357
					process.getState() == ProcessState.Created ? "../resources/img/notStarted.gif" : "wf_proc.img?id=" + id + "&t=" + DateUtils.now();
358

  
353 359
			final String name = process.getGraph().getName();
354
			
360

  
355 361
			final long startDate = NumberUtils.toLong(process.getEnv().getAttribute(WorkflowsConstants.SYSTEM_START_DATE), 0);
356 362
			final long endDate = NumberUtils.toLong(process.getEnv().getAttribute(WorkflowsConstants.SYSTEM_END_DATE), 0);
357
	
363

  
358 364
			final AtomicWorkflowDescriptor wf = new AtomicWorkflowDescriptor(id, name, status, mapContent, img, true, "auto", "RUNNING", startDate, endDate);
359
		
365

  
360 366
			res.put("graph", wf);
361 367
		}
362 368

  
......
370 376

  
371 377
		return value;
372 378
	}
373
	
379

  
374 380
	@RequestMapping("/ui/workflow_user_params.json")
375 381
	public @ResponseBody List<NodeWithUserParams> listWorkflowUserParams(@RequestParam(value = "wf", required = true) final String wfId) throws Exception {
376 382
		return isLookupClient.listWorkflowUserParams(wfId);
377
	}	
383
	}
378 384

  
379
	@RequestMapping(value="/ui/save_user_params.do")
380
	public @ResponseBody boolean saveWorkflowUserParams(@RequestParam(value = "wf", required = true) final String wfId, @RequestParam(value = "params", required = true) final String jsonParams) throws Exception {
381
		
385
	@RequestMapping(value = "/ui/save_user_params.do")
386
	public @ResponseBody boolean saveWorkflowUserParams(@RequestParam(value = "wf", required = true) final String wfId,
387
			@RequestParam(value = "params", required = true) final String jsonParams) throws Exception {
388

  
382 389
		final String xml = isLookupClient.getProfile(wfId);
383
		
384
		final List<NodeWithUserParams> params = new Gson().fromJson(jsonParams,  new TypeToken<List<NodeWithUserParams>>(){}.getType());
385
		
386
		boolean res = isRegistryClient.updateSarasvatiWorkflow(wfId, xml, params);
387 390

  
388
		for (String metaWfId : isLookupClient.listMetaWorflowsForWfId(wfId)) {
391
		final List<NodeWithUserParams> params = new Gson().fromJson(jsonParams, new TypeToken<List<NodeWithUserParams>>() {}.getType());
392

  
393
		final boolean res = isRegistryClient.updateSarasvatiWorkflow(wfId, xml, params);
394

  
395
		for (final String metaWfId : isLookupClient.listMetaWorflowsForWfId(wfId)) {
389 396
			if (isLookupClient.isExecutable(metaWfId)) {
390 397
				isRegistryClient.updateMetaWorkflowStatus(metaWfId, WorkflowStatus.EXECUTABLE);
391 398
			} else {
392 399
				isRegistryClient.updateMetaWorkflowStatus(metaWfId, WorkflowStatus.WAIT_USER_SETTINGS);
393 400
			}
394 401
		}
395
		
402

  
396 403
		return res;
397 404
	}
398 405

  
modules/dnet-modular-workflows-ui/trunk/src/main/java/eu/dnetlib/functionality/modular/ui/workflows/objects/ProcessListEntry.java
7 7
import eu.dnetlib.msro.workflows.util.ProcessUtils;
8 8

  
9 9
public class ProcessListEntry {
10

  
10 11
	private String procId;
11 12
	private String wfId;
12 13
	private String name;
......
14 15
	private String status;
15 16
	private long date;
16 17
	private String repo;
17
	
18
	private String repoId;
19
	private String apiId;
20

  
18 21
	public ProcessListEntry() {}
19 22

  
20
	public ProcessListEntry(String procId, String wfId, String name, String family, String status, long date, String repo) {
23
	public ProcessListEntry(final String procId, final String wfId, final String name, final String family, final String status, final long date,
24
			final String repo, final String repoId, final String apiId) {
21 25
		this.procId = procId;
22 26
		this.wfId = wfId;
23 27
		this.name = name;
......
25 29
		this.status = status;
26 30
		this.date = date;
27 31
		this.repo = repo;
32
		this.repoId = repoId;
33
		this.apiId = apiId;
28 34
	}
29 35

  
30
	public ProcessListEntry(String procId, String wfId, String name, String family, String status, Date date, String repo) {
31
		this(procId, wfId, name, family, status, dateToLong(date), repo);
36
	public ProcessListEntry(final String procId, final String wfId, final String name, final String family, final String status, final Date date,
37
			final String repo, final String repoId, final String apiId) {
38
		this(procId, wfId, name, family, status, dateToLong(date), repo, repoId, apiId);
32 39
	}
33
	
40

  
34 41
	public ProcessListEntry(final String procId, final GraphProcess process) {
35
		this(procId, 
36
				ProcessUtils.calculateWfId(process), 
42
		this(procId,
43
				ProcessUtils.calculateWfId(process),
37 44
				ProcessUtils.calculateName(process),
38 45
				ProcessUtils.calculateFamily(process),
39 46
				ProcessUtils.calculateStatus(process),
40 47
				ProcessUtils.calculateLastActivityDate(process),
41
				ProcessUtils.calculateRepo(process));
48
				ProcessUtils.calculateRepo(process),
49
				ProcessUtils.calculateRepoId(process),
50
				ProcessUtils.calculateApiId(process));
42 51
	}
43 52

  
44 53
	public String getProcId() {
45 54
		return procId;
46 55
	}
47 56

  
48
	public void setProcId(String procId) {
57
	public void setProcId(final String procId) {
49 58
		this.procId = procId;
50 59
	}
51 60

  
......
53 62
		return name;
54 63
	}
55 64

  
56
	public void setName(String name) {
65
	public void setName(final String name) {
57 66
		this.name = name;
58 67
	}
59 68

  
......
61 70
		return status;
62 71
	}
63 72

  
64
	public void setStatus(String status) {
73
	public void setStatus(final String status) {
65 74
		this.status = status;
66 75
	}
67 76

  
......
73 82
		return wfId;
74 83
	}
75 84

  
76
	public void setWfId(String wfId) {
85
	public void setWfId(final String wfId) {
77 86
		this.wfId = wfId;
78 87
	}
79 88

  
......
81 90
		return date;
82 91
	}
83 92

  
84
	public void setDate(long date) {
93
	public void setDate(final long date) {
85 94
		this.date = date;
86 95
	}
87 96

  
88
	public void setFamily(String family) {
97
	public void setFamily(final String family) {
89 98
		this.family = family;
90 99
	}
91 100

  
......
93 102
		return repo;
94 103
	}
95 104

  
96
	public void setRepo(String repo) {
105
	public void setRepo(final String repo) {
97 106
		this.repo = repo;
98 107
	}
99
	
100
	private static long dateToLong(Date date) {
101
		return (date == null) ? Long.MAX_VALUE : date.getTime();
108

  
109
	private static long dateToLong(final Date date) {
110
		return date == null ? Long.MAX_VALUE : date.getTime();
102 111
	}
103 112

  
113
	public String getRepoId() {
114
		return repoId;
115
	}
116

  
117
	public void setRepoId(final String repoId) {
118
		this.repoId = repoId;
119
	}
120

  
121
	public String getApiId() {
122
		return apiId;
123
	}
124

  
125
	public void setApiId(final String apiId) {
126
		this.apiId = apiId;
127
	}
128

  
104 129
}
modules/dnet-modular-workflows-ui/trunk/src/main/resources/eu/dnetlib/web/resources/js/dnet_wf_journal.js
93 93
			columnDefs: [{field: 'procId', displayName: 'Process ID'      , width: '15%', cellTemplate: '<div class="ngCellText"><a href="javascript:void(0)" ng-click="showProcess(row.getProperty(col.field))">{{row.getProperty(col.field)}}</a></div>'},
94 94
			             {field: 'name'  , displayName: 'Workflow name'   , width: '20%',},
95 95
			             {field: 'family', displayName: 'Workflow family' , width: '15%' },
96
			             {field: 'repo'  , displayName: 'Datasource'  },
96
			             {field: 'repo'  , displayName: 'Datasource', cellTemplate: '<div class="ngCellText"><a href="repoApis.do#/api/{{row.getProperty(\'repoId\')}}/{{row.getProperty(\'apiId\')}}/ALL/ALL" ng-if="row.getProperty(\'repoId\') && row.getProperty(\'apiId\') && row.getProperty(\'repo\')">{{row.getProperty(col.field)}}</a><span ng-if="!(row.getProperty(\'repoId\') && row.getProperty(\'apiId\'))">{{row.getProperty(col.field)}}</span></div>'},
97 97
			             {field: 'status', displayName: 'Status'          , width: '10%', cellTemplate: '<div class="ngCellText"><span class="label label-default" ng-class="{ \'label-success\': row.getProperty(col.field) == \'SUCCESS\', \'label-danger\': row.getProperty(col.field) == \'FAILURE\', \'label-info\': row.getProperty(col.field) == \'EXECUTING\'}">{{row.getProperty(col.field)}}</span></div>'},
98 98
			             {field: 'date'  , displayName: 'Date'            , width: '10%', cellTemplate: '<div class="ngCellText">{{ (row.getProperty("date") > 0 && row.getProperty("date") < 9999999999999) ? (row.getProperty("date") | date:"yyyy-MM-dd HH:mm:ss") : "not yet started" }}</div>'}
99 99
			            ]
modules/dnet-modular-workflows-ui/trunk/pom.xml
22 22
		<dependency>
23 23
			<groupId>eu.dnetlib</groupId>
24 24
			<artifactId>dnet-msro-service</artifactId>
25
			<version>[3.0.0,4.0.0)</version>
25
			<version>[3.2.7,4.0.0)</version>
26 26
		</dependency>
27 27
		<dependency>
28 28
			<groupId>joda-time</groupId>

Also available in: Unified diff