Project

General

Profile

1
package eu.dnetlib.functionality.modular.ui.workflows.controllers;
2

    
3
import java.awt.image.BufferedImage;
4
import java.io.IOException;
5
import java.io.OutputStream;
6
import java.util.ArrayList;
7
import java.util.Collection;
8
import java.util.Collections;
9
import java.util.HashMap;
10
import java.util.Iterator;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.Set;
14
import java.util.UUID;
15

    
16
import javax.annotation.PostConstruct;
17
import javax.annotation.Resource;
18
import javax.imageio.ImageIO;
19
import javax.servlet.http.HttpServletResponse;
20

    
21
import org.apache.commons.lang.StringUtils;
22
import org.apache.commons.lang.math.NumberUtils;
23
import org.apache.commons.logging.Log;
24
import org.apache.commons.logging.LogFactory;
25
import org.dom4j.Document;
26
import org.dom4j.DocumentException;
27
import org.dom4j.Node;
28
import org.joda.time.DateTime;
29
import org.joda.time.format.DateTimeFormat;
30
import org.joda.time.format.DateTimeFormatter;
31
import org.springframework.beans.factory.annotation.Autowired;
32
import org.springframework.beans.factory.annotation.Value;
33
import org.springframework.stereotype.Controller;
34
import org.springframework.web.bind.annotation.RequestMapping;
35
import org.springframework.web.bind.annotation.RequestParam;
36
import org.springframework.web.bind.annotation.ResponseBody;
37

    
38
import com.google.common.base.Function;
39
import com.google.common.collect.Iterators;
40
import com.google.common.collect.Lists;
41
import com.google.common.collect.Maps;
42
import com.google.gson.Gson;
43
import com.google.gson.reflect.TypeToken;
44

    
45
import eu.dnetlib.common.logging.DnetLogger;
46
import eu.dnetlib.common.logging.LogMessage;
47
import eu.dnetlib.common.services.locators.DnetServiceLocator;
48
import eu.dnetlib.enabling.datastructures.MetaWorkflow;
49
import eu.dnetlib.enabling.datastructures.Workflow;
50
import eu.dnetlib.enabling.datastructures.WorkflowInstance;
51
import eu.dnetlib.enabling.datastructures.WorkflowInstance.ConfigurationStatus;
52
import eu.dnetlib.enabling.datastructures.WorkflowInstance.StartMode;
53
import eu.dnetlib.enabling.is.client.InformationServiceClient;
54
import eu.dnetlib.functionality.modular.ui.AbstractAjaxController;
55
import eu.dnetlib.functionality.modular.ui.workflows.objects.NodeInfo;
56
import eu.dnetlib.functionality.modular.ui.workflows.objects.WorkflowParam;
57
import eu.dnetlib.functionality.modular.ui.workflows.objects.WorkflowParamViewer;
58
import eu.dnetlib.functionality.modular.ui.workflows.sarasvati.viewer.ProcessGraphGenerator;
59
import eu.dnetlib.miscutils.DateUtils;
60
import eu.dnetlib.msro.dispatcher.ProcessInfo;
61
import eu.dnetlib.msro.dispatcher.WorkflowDispatcher;
62
import eu.dnetlib.msro.dispatcher.WorkflowRegistry;
63
import eu.dnetlib.msro.worker.WorkflowConstants;
64
import eu.dnetlib.rmi.object.manager.ProcessImageDesc;
65
import eu.dnetlib.rmi.object.manager.ProcessNodeDesc;
66
import eu.dnetlib.rmi.objects.is.BlackboardActionStatus;
67
import eu.dnetlib.rmi.soap.ManagerWorkerService;
68
import eu.dnetlib.rmi.soap.exceptions.InformationServiceException;
69

    
70
/**
71
 * Web controller for the UI
72
 * 
73
 * @author Michele Artini
74
 */
75

    
76
@Controller
77
public class WorkflowsController extends AbstractAjaxController {
78

    
79
	private final class JournalEntryFunction implements Function<Map<String, String>, ProcessInfo> {
80

    
81
		@Override
82
		public ProcessInfo apply(final Map<String, String> input) {
83
			final String name = input.get(WorkflowConstants.SYSTEM_WF_PROFILE_NAME);
84

    
85
			String repo = "";
86
			if (input.containsKey(WorkflowConstants.DATAPROVIDER_NAME)) {
87
				repo += input.get(WorkflowConstants.DATAPROVIDER_NAME);
88
			}
89
			final String procId = input.get(WorkflowConstants.SYSTEM_WF_PROCESS_ID);
90
			final String metaWfId = input.get(WorkflowConstants.SYSTEM_METAWF_ID);
91
			final String family = input.get(WorkflowConstants.SYSTEM_WF_PROFILE_FAMILY);
92
			final long date = NumberUtils.toLong(input.get(LogMessage.LOG_DATE_FIELD), 0);
93
			final long startDate = NumberUtils.toLong(input.get(WorkflowConstants.SYSTEM_START_DATE), 0);
94
			final BlackboardActionStatus status = Boolean.valueOf(input.get(WorkflowConstants.SYSTEM_COMPLETED_SUCCESSFULLY)) ? BlackboardActionStatus.DONE
95
					: BlackboardActionStatus.FAILED;
96
			final int priority = NumberUtils.toInt(input.get(WorkflowConstants.SYSTEM_WF_PRIORITY), 0);
97

    
98
			final ProcessInfo info = new ProcessInfo(metaWfId, name, repo, family, procId, startDate, priority);
99
			info.setLastActivityDate(date);
100
			info.setStatus(status);
101

    
102
			return info;
103
		}
104
	}
105

    
106
	@Autowired
107
	private WorkflowDispatcher dispatcher;
108

    
109
	@Autowired
110
	private WorkflowRegistry wfRegistry;
111

    
112
	@Autowired
113
	private InformationServiceClient isClient;
114

    
115
	@Resource
116
	private ProcessGraphGenerator processGraphGenerator;
117

    
118
	@Resource
119
	private DnetServiceLocator serviceLocator;
120

    
121
	@Resource(name = "msroWorkflowLogger")
122
	private DnetLogger dnetLogger;
123

    
124
	@Value("${workflows.categories.viewers.json}")
125
	private String paramViewersForCategoryJson;
126

    
127
	private Map<String, List<WorkflowParamViewer>> paramViewers = Maps.newHashMap();
128

    
129
	private static final Log log = LogFactory.getLog(WorkflowsController.class);
130

    
131
	@PostConstruct
132
	private void init() {
133
		log.info("Initialing categoryUis map using JSON: " + paramViewersForCategoryJson);
134
		this.paramViewers = new Gson().fromJson(paramViewersForCategoryJson, new TypeToken<Map<String, List<WorkflowParamViewer>>>() {}.getType());
135
	}
136

    
137
	@RequestMapping("/ui/list_metaworkflows.json")
138
	public @ResponseBody
139
	List<MetaWorkflow> listMetaWorflowsForSection(@RequestParam(value = "section", required = false) final String sectionName,
140
			@RequestParam(value = "dsId", required = false) final String dsId) {
141

    
142
		if (sectionName != null) {
143
			return isClient.listResourceWithProperty("section", sectionName, MetaWorkflow.class);
144
		} else if (dsId != null) {
145
			return isClient.listResourceWithProperty("datasource", dsId, MetaWorkflow.class);
146
		} else {
147
			return Lists.newArrayList();
148
		}
149
	}
150

    
151
	@RequestMapping("/ui/wf_metaworkflow.json")
152
	public @ResponseBody
153
	MetaWorkflow getMetaWorkflow(@RequestParam(value = "id", required = true) final String id) throws Exception {
154
		return isClient.getResourceByCode(id, MetaWorkflow.class);
155
	}
156

    
157
	@RequestMapping("/ui/workflow.img")
158
	public void getWorkflowImage(final HttpServletResponse response, @RequestParam(value = "code", required = true) final String code) throws Exception {
159
		final Workflow wf = isClient.getResourceByCode(code, Workflow.class);
160
		final BufferedImage image = processGraphGenerator.getWfDescImage(code, wf);
161
		sendImage(response, image);
162
	}
163

    
164
	@RequestMapping("/ui/workflow.map")
165
	public @ResponseBody
166
	String getWorkflowImageMap(@RequestParam(value = "code", required = true) final String code) throws Exception {
167
		final Workflow wf = isClient.getResourceByCode(code, Workflow.class);
168
		return processGraphGenerator.getWfDescImageMap(code, wf);
169
	}
170

    
171
	private void sendImage(final HttpServletResponse response, final BufferedImage image) throws IOException {
172
		response.setContentType("image/png");
173
		OutputStream out = response.getOutputStream();
174
		ImageIO.write(image, "png", out);
175
		out.flush();
176
		out.close();
177
	}
178

    
179
	@RequestMapping("/ui/wf.start")
180
	public @ResponseBody
181
	boolean startWorkflow(@RequestParam(value = "metaWf", required = true) final String metaWfId,
182
			@RequestParam(value = "wf", required = true) final String wfName) throws Exception {
183
		dispatcher.dispatchWorkFlow(metaWfId, wfName);
184
		return true;
185
	}
186

    
187
	@RequestMapping("/ui/metawf.start")
188
	public @ResponseBody
189
	boolean startMetaWorkflow(@RequestParam(value = "metaWf", required = true) final String metaWfId) throws Exception {
190
		dispatcher.dispatchMetaWorkFlow(metaWfId);
191
		return true;
192
	}
193

    
194
	@RequestMapping("/ui/wf_workflow_node.json")
195
	public @ResponseBody
196
	NodeInfo workflowNode_info(@RequestParam(value = "wf", required = true) final String wfCode,
197
			@RequestParam(value = "node", required = true) final String nodeName) throws InformationServiceException, DocumentException {
198

    
199
		final Document doc = isClient.getResourceByCode(wfCode, Workflow.class).asDocument();
200
		final Node n = doc.selectSingleNode("//NODE[@name='" + nodeName + "']");
201
		final String name = nodeName;
202
		final String description = n.valueOf("./DESCRIPTION");
203
		final boolean start = "true".equalsIgnoreCase(n.valueOf("@isStart"));
204
		final boolean join = "true".equalsIgnoreCase(n.valueOf("@isJoin"));
205
		final Map<String, String> params = Maps.newLinkedHashMap();
206
		for (Object o : n.selectNodes("./PARAMETERS/PARAM")) {
207
			final Node p = (Node) o;
208
			params.put(p.valueOf("@name"), p.getText());
209
		}
210
		return new NodeInfo(name, description, start, join, params);
211

    
212
	}
213

    
214
	@RequestMapping("/ui/wf_metaworkflow.edit")
215
	public @ResponseBody
216
	boolean editMetaWd(@RequestParam(value = "json", required = true) final String json) throws Exception {
217
		final MetaWorkflow metaWf = new Gson().fromJson(json, MetaWorkflow.class);
218
		log.info("Updating metaWorkflow " + metaWf.getName());
219
		isClient.updateResource(metaWf);
220
		return true;
221
	}
222

    
223
	@RequestMapping("/ui/clone_metaworkflow.do")
224
	public @ResponseBody
225
	String cloneMetaWf(@RequestParam(value = "code", required = true) final String code,
226
			@RequestParam(value = "name", required = true) final String newName) throws Exception {
227

    
228
		if (newName.trim().length() > 0) {
229
			final String newCode = "metawf-" + UUID.randomUUID();
230
			final MetaWorkflow metaWf = isClient.getResourceByCode(code, MetaWorkflow.class);
231
			metaWf.setCode(newCode);
232
			metaWf.setName(newName);
233
			isClient.registerResource(metaWf);
234
			return newCode;
235
		} else {
236
			throw new IllegalArgumentException("Name is empty");
237
		}
238
	}
239

    
240
	@RequestMapping(value = "/ui/wf_proc_node.json", produces = "application/json")
241
	public @ResponseBody
242
	ProcessNodeDesc getProcessWorkflowNode(@RequestParam(value = "proc", required = true) final String procId,
243
			@RequestParam(value = "node", required = true) final long nodeId) throws Exception {
244
		final ProcessInfo proc = wfRegistry.findProcess(procId);
245
		final ManagerWorkerService mws = serviceLocator.getService(ManagerWorkerService.class, proc.getWorkerId());
246
		return mws.getProcessNodeDesc(procId, nodeId);
247
	}
248

    
249
	/*
250
	 * private NodeToken findNodeToken(final String pid, final long nid) { final GraphProcess process =
251
	 * graphProcessRegistry.findProcess(pid); if (process != null) { for (NodeToken token : process.getNodeTokens()) { if
252
	 * (token.getNode().getId() == nid) { return token; } } } return null; }
253
	 * 
254
	 * private String findNodeName(final String pid, final long nid) { final GraphProcess process = graphProcessRegistry.findProcess(pid);
255
	 * if (process != null) { for (Node node : process.getGraph().getNodes()) { if (node.getId() == nid) { return node.getName(); } } }
256
	 * return "-"; }
257
	 */
258

    
259
	@RequestMapping("/ui/wf_proc.kill")
260
	public @ResponseBody
261
	boolean killProcessWorkflow(@RequestParam(value = "id", required = true) final String id) throws Exception {
262
		/*
263
		 * GraphProcess proc = graphProcessRegistry.findProcess(id); proc.setState(ProcessState.Canceled); return true;
264
		 */
265
		// TODO
266
		return false;
267
	}
268

    
269
	@RequestMapping("/ui/wf_journal.range")
270
	public @ResponseBody
271
	Collection<ProcessInfo> rangeWfJournal(@RequestParam(value = "start", required = true) final String start,
272
			@RequestParam(value = "end", required = true) final String end) throws Exception {
273

    
274
		final Map<String, ProcessInfo> res = Maps.newHashMap();
275

    
276
		final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
277
		final DateTime startDate = formatter.parseDateTime(start);
278
		final DateTime endDate = formatter.parseDateTime(end).plusHours(23).plusMinutes(59).plusSeconds(59);
279

    
280
		final Iterator<ProcessInfo> iter = Iterators.transform(dnetLogger.range(startDate.toDate(), endDate.toDate()), new JournalEntryFunction());
281
		while (iter.hasNext()) {
282
			ProcessInfo e = iter.next();
283
			res.put(e.getProcId(), e);
284
		}
285

    
286
		long now = DateUtils.now();
287
		if (startDate.isBefore(now) && endDate.isAfter(now)) {
288
			for (String pid : wfRegistry.listProcIds()) {
289
				res.put(pid, wfRegistry.findProcess(pid));
290
			}
291
		}
292

    
293
		return res.values();
294

    
295
	}
296

    
297
	@RequestMapping("/ui/wf_journal.find")
298
	public @ResponseBody
299
	Collection<ProcessInfo> findWfJournal(@RequestParam(value = "wfs", required = true) final String wfs) {
300
		final Map<String, ProcessInfo> res = Maps.newHashMap();
301

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

    
304
		for (String wfId : wfFilter) {
305
			final Iterator<ProcessInfo> iter = Iterators.transform(dnetLogger.find(WorkflowConstants.SYSTEM_METAWF_ID, wfId),
306
					new JournalEntryFunction());
307
			while (iter.hasNext()) {
308
				ProcessInfo e = iter.next();
309
				res.put(e.getProcId(), e);
310
			}
311
		}
312

    
313
		for (String pid : wfRegistry.listProcIds()) {
314
			final ProcessInfo proc = wfRegistry.findProcess(pid);
315
			if (wfFilter.contains(proc.getMetaWfId())) {
316
				res.put(pid, proc);
317
			}
318
		}
319

    
320
		return res.values();
321
	}
322

    
323
	@RequestMapping("/ui/wf_journal_byFamily.find")
324
	public @ResponseBody
325
	Collection<ProcessInfo> findWfJournalByFamily(@RequestParam(value = "family", required = true) final String family) throws IOException {
326
		final Iterator<ProcessInfo> iter = Iterators.transform(dnetLogger.find(WorkflowConstants.SYSTEM_WF_PROFILE_FAMILY, family),
327
				new JournalEntryFunction());
328
		return Lists.newArrayList(iter);
329
	}
330

    
331
	@RequestMapping("/ui/wf_journal.get")
332
	public @ResponseBody
333
	Map<String, Object> getWfJournalLog(@RequestParam(value = "proc", required = true) final String procId) throws Exception {
334
		final Map<String, Object> res = Maps.newHashMap();
335

    
336
		res.put("procId", procId);
337

    
338
		final Map<String, String> logs = dnetLogger.findOne("system:processId", procId);
339

    
340
		if (logs != null && !logs.isEmpty()) {
341
			final List<String> keys = Lists.newArrayList(logs.keySet());
342
			Collections.sort(keys);
343

    
344
			final List<Map<String, String>> journalEntry = Lists.newArrayList();
345
			for (String k : keys) {
346
				final Map<String, String> m = Maps.newHashMap();
347
				m.put("name", k);
348
				m.put("value", logs.get(k));
349
				journalEntry.add(m);
350
			}
351
			res.put("journal", journalEntry);
352
		}
353

    
354
		final ProcessInfo process = wfRegistry.findProcess(procId);
355

    
356
		if (process != null) {
357
			res.put("name", process.getWfName());
358
			res.put("worker", process.getWorkerId());
359
			res.put("startDate", process.getStartDate());
360
			if (process.getStatus() == BlackboardActionStatus.DONE || process.getStatus() == BlackboardActionStatus.FAILED) {
361
				res.put("endDate", process.getLastActivityDate());
362
			} else {
363
				res.put("endDate", 0);
364
			}
365

    
366
			if (process.getStatus() != BlackboardActionStatus.ASSIGNED) {
367
				final ProcessImageDesc desc = processGraphGenerator.getProcessImageDesc(procId);
368
				if (desc != null) {
369
					res.put("WF_IMAGE_BASE64", desc.getBase64());
370
					res.put("WF_IMAGE_MAP", desc.getImageMap());
371
					res.put("WF_IMAGE_FORMAT", desc.getFormat());
372
				}
373
			}
374
		}
375

    
376
		return res;
377
	}
378

    
379
	@RequestMapping("/ui/wf_atomic_workflow.enable")
380
	public @ResponseBody
381
	String enableAtomicWf(@RequestParam(value = "metaWf", required = true) final String metaWfId,
382
			@RequestParam(value = "wf", required = true) final String wfName,
383
			@RequestParam(value = "start", required = true) final String value) throws Exception {
384

    
385
		final MetaWorkflow metaWf = isClient.getResourceByCode(metaWfId, MetaWorkflow.class);
386
		final WorkflowInstance wfi = metaWf.findWorkflowInstanceByName(wfName);
387
		if (wfi != null) {
388
			wfi.setStartMode(StartMode.valueOf(value.toUpperCase()));
389
			isClient.updateResource(metaWf);
390
		}
391

    
392
		return value;
393
	}
394

    
395
	@SuppressWarnings("unchecked")
396
	@RequestMapping("/ui/workflow_user_params.json")
397
	public @ResponseBody
398
	List<WorkflowParam>
399
			listWorkflowUserParams(@RequestParam(value = "metaWf", required = true) final String metaWfId,
400
					@RequestParam(value = "wf", required = true) final String wfName) throws Exception {
401

    
402
		final MetaWorkflow metaWf = isClient.getResourceByCode(metaWfId, MetaWorkflow.class);
403
		final WorkflowInstance wfi = metaWf.findWorkflowInstanceByName(wfName);
404
		final Map<String, String> actualParams = wfi.getParams();
405
		final Document doc = isClient.getResourceByCode(wfi.getWfCode(), Workflow.class).asDocument();
406

    
407
		return Lists.transform(doc.selectNodes("//CONFIGURATION/PARAM"), new Function<Object, WorkflowParam>() {
408

    
409
			@Override
410
			public WorkflowParam apply(final Object o) {
411
				final Node n = (Node) o;
412

    
413
				final String key = n.valueOf("@name");
414
				final boolean required = "true".equalsIgnoreCase(n.valueOf("@required"));
415
				final String type = n.valueOf("@type");
416
				final String function = n.valueOf("@function");
417
				final String value = actualParams.containsKey(key) ? actualParams.get(key) : n.getText();
418
				final boolean userParam = "user".equals(n.valueOf("@managedBy"));
419
				final String category = n.valueOf("@category");
420
				final List<WorkflowParamViewer> uis =
421
						!StringUtils.isEmpty(category) && paramViewers.containsKey(category) ?
422
								paramViewers.get(category) : new ArrayList<WorkflowParamViewer>();
423

    
424
				return new WorkflowParam(key, value, required, userParam, type, function, uis);
425
			}
426
		});
427

    
428
	}
429

    
430
	@RequestMapping(value = "/ui/save_user_params.do")
431
	public @ResponseBody
432
	boolean saveWorkflowUserParams(@RequestParam(value = "metaWf", required = true) final String metaWfId,
433
			@RequestParam(value = "wf", required = true) final String wfName,
434
			@RequestParam(value = "params", required = true) final String jsonParams) throws Exception {
435

    
436
		final List<WorkflowParam> params = new Gson().fromJson(jsonParams, new TypeToken<List<WorkflowParam>>() {}.getType());
437
		final MetaWorkflow metaWf = isClient.getResourceByCode(metaWfId, MetaWorkflow.class);
438
		final WorkflowInstance wfi = metaWf.findWorkflowInstanceByName(wfName);
439
		wfi.setConfigurationStatus(ConfigurationStatus.CONFIGURED);
440
		wfi.setParams(new HashMap<String, String>());
441
		for (WorkflowParam p : params) {
442
			wfi.getParams().put(p.getName(), p.getValue());
443
			if (p.isRequired() && StringUtils.isBlank(p.getValue())) {
444
				wfi.setConfigurationStatus(ConfigurationStatus.NOT_CONFIGURED);
445
			}
446
		}
447
		isClient.updateResource(metaWf);
448

    
449
		return true;
450
	}
451
}
(5-5/5)