1
|
/*
|
2
|
* Copyright EDIT
|
3
|
* European Distributed Institute of Taxonomy
|
4
|
* http://www.e-taxonomy.eu
|
5
|
*
|
6
|
* The contents of this file are subject to the Mozilla Public License Version 1.1
|
7
|
* See LICENSE.TXT at the top of this package for the full license terms.
|
8
|
*/
|
9
|
package eu.etaxonomy.cdm.remote.controller.checklist;
|
10
|
|
11
|
import java.io.File;
|
12
|
import java.io.IOException;
|
13
|
import java.io.InputStream;
|
14
|
import java.util.HashMap;
|
15
|
import java.util.HashSet;
|
16
|
import java.util.List;
|
17
|
import java.util.Map;
|
18
|
import java.util.Set;
|
19
|
import java.util.UUID;
|
20
|
|
21
|
import javax.servlet.http.HttpServletRequest;
|
22
|
import javax.servlet.http.HttpServletResponse;
|
23
|
|
24
|
import org.apache.log4j.Logger;
|
25
|
import org.springframework.beans.factory.annotation.Autowired;
|
26
|
import org.springframework.context.ApplicationContext;
|
27
|
import org.springframework.context.ResourceLoaderAware;
|
28
|
import org.springframework.core.io.Resource;
|
29
|
import org.springframework.core.io.ResourceLoader;
|
30
|
import org.springframework.stereotype.Controller;
|
31
|
import org.springframework.web.bind.WebDataBinder;
|
32
|
import org.springframework.web.bind.annotation.InitBinder;
|
33
|
import org.springframework.web.bind.annotation.RequestMapping;
|
34
|
import org.springframework.web.bind.annotation.RequestMethod;
|
35
|
import org.springframework.web.bind.annotation.RequestParam;
|
36
|
import org.springframework.web.servlet.ModelAndView;
|
37
|
|
38
|
import eu.etaxonomy.cdm.api.service.IClassificationService;
|
39
|
import eu.etaxonomy.cdm.api.service.IService;
|
40
|
import eu.etaxonomy.cdm.api.service.ITaxonNodeService;
|
41
|
import eu.etaxonomy.cdm.common.CdmUtils;
|
42
|
import eu.etaxonomy.cdm.common.DocUtils;
|
43
|
import eu.etaxonomy.cdm.common.monitor.IRestServiceProgressMonitor;
|
44
|
import eu.etaxonomy.cdm.io.common.CdmApplicationAwareDefaultExport;
|
45
|
import eu.etaxonomy.cdm.io.dwca.out.DwcaEmlRecord;
|
46
|
import eu.etaxonomy.cdm.io.dwca.out.DwcaTaxExportConfigurator;
|
47
|
import eu.etaxonomy.cdm.model.description.Feature;
|
48
|
import eu.etaxonomy.cdm.model.taxon.Classification;
|
49
|
import eu.etaxonomy.cdm.model.taxon.Taxon;
|
50
|
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
|
51
|
import eu.etaxonomy.cdm.remote.controller.AbstractController;
|
52
|
import eu.etaxonomy.cdm.remote.controller.ProgressMonitorController;
|
53
|
import eu.etaxonomy.cdm.remote.controller.util.ProgressMonitorUtil;
|
54
|
import eu.etaxonomy.cdm.remote.editor.UUIDListPropertyEditor;
|
55
|
import eu.etaxonomy.cdm.remote.editor.UuidList;
|
56
|
import eu.etaxonomy.cdm.remote.view.FileDownloadView;
|
57
|
import eu.etaxonomy.cdm.remote.view.HtmlView;
|
58
|
|
59
|
/**
|
60
|
* @author a.mueller
|
61
|
* @created 28.06.2017
|
62
|
* <p>
|
63
|
* This controller exports taxonomies via Darwin Core Archive
|
64
|
* (https://en.wikipedia.org/wiki/Darwin_Core_Archive).
|
65
|
*/
|
66
|
@Controller
|
67
|
@RequestMapping(value = { "/dwca" })
|
68
|
public class DwcaExportController extends AbstractController implements ResourceLoaderAware{
|
69
|
|
70
|
|
71
|
private static final String DWCA_TAX_EXPORT_DOC_RESSOURCE = "classpath:eu/etaxonomy/cdm/doc/remote/apt/dwca-tax-export-default.apt";
|
72
|
|
73
|
@Autowired
|
74
|
private ApplicationContext appContext;
|
75
|
|
76
|
@Autowired
|
77
|
private IClassificationService classificationService;
|
78
|
|
79
|
@Autowired
|
80
|
private ITaxonNodeService taxonNodeService;
|
81
|
|
82
|
@Autowired
|
83
|
public ProgressMonitorController progressMonitorController;
|
84
|
|
85
|
private ResourceLoader resourceLoader;
|
86
|
|
87
|
|
88
|
/**
|
89
|
* There should only be one processes operating on the export
|
90
|
* therefore the according progress monitor uuid is stored in this static
|
91
|
* field.
|
92
|
*/
|
93
|
private static UUID indexMonitorUuid = null;
|
94
|
|
95
|
private final static long DAY_IN_MILLIS = 86400000;
|
96
|
|
97
|
|
98
|
|
99
|
private static final Logger logger = Logger.getLogger(DwcaExportController.class);
|
100
|
|
101
|
/**
|
102
|
* Helper method, which allows to convert strings directly into uuids.
|
103
|
*
|
104
|
* @param binder Special DataBinder for data binding from web request parameters to JavaBean objects.
|
105
|
*/
|
106
|
@InitBinder
|
107
|
public void initBinder(WebDataBinder binder) {
|
108
|
binder.registerCustomEditor(UuidList.class, new UUIDListPropertyEditor());
|
109
|
// binder.registerCustomEditor(NamedArea.class, new TermBaseListPropertyEditor<>(termService));
|
110
|
// binder.registerCustomEditor(UUID.class, new UUIDEditor());
|
111
|
}
|
112
|
|
113
|
|
114
|
|
115
|
|
116
|
/**
|
117
|
* Documentation webservice for this controller.
|
118
|
*
|
119
|
* @param response unused
|
120
|
* @param request unused
|
121
|
* @return
|
122
|
* @throws IOException
|
123
|
*/
|
124
|
@RequestMapping(value = {""}, method = { RequestMethod.GET})
|
125
|
public ModelAndView exportGetExplanation(HttpServletResponse response,
|
126
|
HttpServletRequest request) throws IOException{
|
127
|
ModelAndView mv = new ModelAndView();
|
128
|
// Read apt documentation file.
|
129
|
Resource resource = resourceLoader.getResource(DWCA_TAX_EXPORT_DOC_RESSOURCE);
|
130
|
// using input stream as this works for both files in the classes directory
|
131
|
// as well as files inside jars
|
132
|
InputStream aptInputStream = resource.getInputStream();
|
133
|
// Build Html View
|
134
|
Map<String, String> modelMap = new HashMap<>();
|
135
|
// Convert Apt to Html
|
136
|
modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
|
137
|
mv.addAllObjects(modelMap);
|
138
|
|
139
|
HtmlView hv = new HtmlView();
|
140
|
mv.setView(hv);
|
141
|
return mv;
|
142
|
}
|
143
|
|
144
|
/**
|
145
|
* This service endpoint is for generating the documentation site.
|
146
|
* If any request of the other endpoint below is incomplete or false
|
147
|
* then this method will be triggered.
|
148
|
*
|
149
|
* @param response
|
150
|
* @param request
|
151
|
* @return
|
152
|
* @throws IOException
|
153
|
*/
|
154
|
public ModelAndView exportGetExplanation(HttpServletResponse response,
|
155
|
HttpServletRequest request, Resource res) throws IOException{
|
156
|
ModelAndView mv = new ModelAndView();
|
157
|
// Read apt documentation file.
|
158
|
Resource resource = (res!= null) ? res : resourceLoader.getResource(DWCA_TAX_EXPORT_DOC_RESSOURCE);
|
159
|
// using input stream as this works for both files in the classes directory
|
160
|
// as well as files inside jars
|
161
|
InputStream aptInputStream = resource.getInputStream();
|
162
|
// Build Html View
|
163
|
Map<String, String> modelMap = new HashMap<>();
|
164
|
// Convert Apt to Html
|
165
|
modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
|
166
|
mv.addAllObjects(modelMap);
|
167
|
|
168
|
HtmlView hv = new HtmlView();
|
169
|
mv.setView(hv);
|
170
|
return mv;
|
171
|
}
|
172
|
|
173
|
|
174
|
|
175
|
/**
|
176
|
*
|
177
|
* This Service endpoint will offer a csv file. It caches the csv-file in the system temp directory
|
178
|
* and will only generate a new one after 24 hours. Or if explicitly triggerd by noCache parameter.
|
179
|
*
|
180
|
* @param featureUuids List of uuids to download/select {@link Feature feature}features
|
181
|
* @param clearCache will trigger export and avoids cached file
|
182
|
* @param classificationUUID Selected {@link Classification classification} to iterate the {@link Taxon}
|
183
|
* @param response HttpServletResponse which returns the ByteArrayOutputStream
|
184
|
* @throws Exception
|
185
|
*/
|
186
|
@RequestMapping(value = { "dwcaTaxExport" }, method = { RequestMethod.GET })
|
187
|
public synchronized ModelAndView doDwcaTaxExport(
|
188
|
@RequestParam(value = "subtrees", required = false) final UuidList subtreeUuids,
|
189
|
@RequestParam(value = "clearCache", required = false) final boolean clearCache,
|
190
|
// @RequestParam(value = "demoExport", required = false) final boolean demoExport,
|
191
|
// @RequestParam(value = "conceptExport", required = false) final boolean conceptExport,
|
192
|
// @RequestParam(value = "classification", required = false) final String classificationUUID,
|
193
|
// @RequestParam(value = "area", required = false) final UuidList areas,
|
194
|
@RequestParam(value = "downloadTokenValueId", required = false) final String downloadTokenValueId,
|
195
|
@RequestParam(value = "priority", required = false) Integer priority,
|
196
|
final HttpServletResponse response,
|
197
|
final HttpServletRequest request) throws Exception {
|
198
|
/**
|
199
|
* ========================================
|
200
|
* progress monitor & new thread for export
|
201
|
* ========================================
|
202
|
*/
|
203
|
try{
|
204
|
ModelAndView mv = new ModelAndView();
|
205
|
|
206
|
String fileName = makeFileName(response, subtreeUuids);
|
207
|
|
208
|
final File cacheFile = new File(new File(System.getProperty("java.io.tmpdir")), fileName);
|
209
|
final String origin = request.getRequestURL().append('?')
|
210
|
.append(request.getQueryString()).toString();
|
211
|
|
212
|
Long result = null;
|
213
|
if(cacheFile.exists()){
|
214
|
result = System.currentTimeMillis() - cacheFile.lastModified();
|
215
|
}
|
216
|
//if file exists return file instantly
|
217
|
//timestamp older than one day?
|
218
|
if(clearCache == false && result != null){ //&& result < 7*(DAY_IN_MILLIS)
|
219
|
logger.info("result of calculation: " + result);
|
220
|
Map<String, File> modelMap = new HashMap<>();
|
221
|
modelMap.put("file", cacheFile);
|
222
|
mv.addAllObjects(modelMap);
|
223
|
//application/zip
|
224
|
FileDownloadView fdv = new FileDownloadView("application/octet-stream", fileName, "zip", "UTF-8");
|
225
|
mv.setView(fdv);
|
226
|
return mv;
|
227
|
}else{//trigger progress monitor and performExport()
|
228
|
String processLabel = "Exporting ...";
|
229
|
final String frontbaseUrl = null;
|
230
|
ProgressMonitorUtil progressUtil = new ProgressMonitorUtil(progressMonitorController);
|
231
|
if (!progressMonitorController.isMonitorRunning(indexMonitorUuid)) {
|
232
|
indexMonitorUuid = progressUtil.registerNewMonitor();
|
233
|
Thread subThread = new Thread() {
|
234
|
@Override
|
235
|
public void run() {
|
236
|
try {
|
237
|
cacheFile.createNewFile();
|
238
|
} catch (IOException e) {
|
239
|
logger.info("Could not create file "+ e);
|
240
|
}
|
241
|
performExport(cacheFile, progressMonitorController.getMonitor(indexMonitorUuid),
|
242
|
subtreeUuids, downloadTokenValueId, origin, response);
|
243
|
}
|
244
|
};
|
245
|
if (priority == null) {
|
246
|
priority = AbstractController.DEFAULT_BATCH_THREAD_PRIORITY;
|
247
|
}
|
248
|
subThread.setPriority(priority);
|
249
|
subThread.start();
|
250
|
}
|
251
|
mv = progressUtil.respondWithMonitorOrDownload(frontbaseUrl, origin, processLabel, indexMonitorUuid, false, request, response);
|
252
|
}
|
253
|
return mv;
|
254
|
}catch(Exception e){
|
255
|
//TODO: Write an specific documentation for this service endpoint
|
256
|
Resource resource = resourceLoader.getResource(DWCA_TAX_EXPORT_DOC_RESSOURCE);
|
257
|
return exportGetExplanation(response, request, resource);
|
258
|
}
|
259
|
}
|
260
|
|
261
|
|
262
|
|
263
|
//=========== Helper Methods ===============//
|
264
|
|
265
|
/**
|
266
|
*
|
267
|
* This private methods finally triggers the export back in the io-package and will create a cache file
|
268
|
* in system temp directory.
|
269
|
*
|
270
|
* @param downloadTokenValueId
|
271
|
* @param response
|
272
|
* @param byteArrayOutputStream
|
273
|
* @param config
|
274
|
* @param defaultExport
|
275
|
*/
|
276
|
private void performExport(File cacheFile, IRestServiceProgressMonitor progressMonitor,
|
277
|
UuidList featureUuids, String downloadTokenValueId, String origin,
|
278
|
HttpServletResponse response
|
279
|
) {
|
280
|
|
281
|
progressMonitor.subTask("configure export");
|
282
|
DwcaTaxExportConfigurator config = setDwcaTaxExportConfigurator(cacheFile, progressMonitor, featureUuids);
|
283
|
@SuppressWarnings("unchecked")
|
284
|
CdmApplicationAwareDefaultExport<DwcaTaxExportConfigurator> defaultExport =
|
285
|
(CdmApplicationAwareDefaultExport<DwcaTaxExportConfigurator>)appContext.getBean("defaultExport");
|
286
|
progressMonitor.subTask("invoke export");
|
287
|
defaultExport.invoke(config); //triggers export
|
288
|
progressMonitor.subTask("wrote results to cache");
|
289
|
progressMonitor.done();
|
290
|
progressMonitor.setOrigin(origin);
|
291
|
}
|
292
|
|
293
|
/**
|
294
|
* Cofiguration method to set the configuration details for the defaultExport in the application context.
|
295
|
* @param cacheFile
|
296
|
*
|
297
|
* @param classificationUUID pass-through the selected {@link Classification classification}
|
298
|
* @param featureUuids pass-through the selected {@link Feature feature} of a {@link Taxon}, in order to fetch it.
|
299
|
* @param areas
|
300
|
* @param byteArrayOutputStream pass-through the stream to write out the data later.
|
301
|
* @param progressMonitor
|
302
|
* @param conceptExport
|
303
|
* @param demoExport
|
304
|
* @return the CsvTaxExportConfiguratorRedlist config
|
305
|
*/
|
306
|
private DwcaTaxExportConfigurator setDwcaTaxExportConfigurator(File cacheFile, IRestServiceProgressMonitor progressMonitor,
|
307
|
UuidList subtreeUuids) {
|
308
|
|
309
|
if(cacheFile == null){
|
310
|
String destination = System.getProperty("java.io.tmpdir");
|
311
|
cacheFile = new File(destination);
|
312
|
}
|
313
|
|
314
|
DwcaEmlRecord emlRecord = null;
|
315
|
DwcaTaxExportConfigurator config = DwcaTaxExportConfigurator.NewInstance(
|
316
|
null, cacheFile, emlRecord);
|
317
|
|
318
|
Set<UUID> subtreeSet = new HashSet<>(subtreeUuids);
|
319
|
config.setProgressMonitor(progressMonitor);
|
320
|
config.setSubtreeUuids(subtreeSet);
|
321
|
|
322
|
// config.setHasHeaderLines(true);
|
323
|
// config.setFieldsTerminatedBy("\t");
|
324
|
// config.setClassificationUuids(classificationUUIDS);
|
325
|
|
326
|
// if(demoExport == false && conceptExport == false){
|
327
|
// config.createPreSelectedExport(false, true);
|
328
|
// }else{
|
329
|
// config.createPreSelectedExport(demoExport, conceptExport);
|
330
|
// }
|
331
|
|
332
|
return config;
|
333
|
}
|
334
|
|
335
|
|
336
|
/**
|
337
|
* @param response
|
338
|
* @param subtreeUuids
|
339
|
* @throws IOException
|
340
|
*/
|
341
|
private String makeFileName(HttpServletResponse response, UuidList subtreeUuids) throws IOException {
|
342
|
String fileName;
|
343
|
if (subtreeUuids != null && ! subtreeUuids.isEmpty()){
|
344
|
UUID firstUuid = subtreeUuids.get(0);
|
345
|
TaxonNode node = taxonNodeService.find(firstUuid);
|
346
|
if (node != null && node.getTaxon() != null){
|
347
|
if (node.getTaxon().getName() != null){
|
348
|
fileName = node.getTaxon().getName().getTitleCache();
|
349
|
}else{
|
350
|
fileName = node.getTaxon().getTitleCache();
|
351
|
}
|
352
|
}else if (node != null){
|
353
|
fileName = node.getClassification().getTitleCache();
|
354
|
}else{
|
355
|
Classification classification = classificationService.find(firstUuid);
|
356
|
if (classification != null){
|
357
|
fileName = classification.getTitleCache();
|
358
|
}else{
|
359
|
//handle via repso
|
360
|
response.sendError(404, "Subtree uuid does not exist: " + firstUuid);
|
361
|
fileName = "Error";
|
362
|
}
|
363
|
}
|
364
|
}else{
|
365
|
List<Classification> classificationList = classificationService.list(null, 1, null
|
366
|
, null, null);
|
367
|
if (!classificationList.isEmpty()){
|
368
|
fileName = classificationList.get(0).getTitleCache();
|
369
|
}else{
|
370
|
//handle via repso
|
371
|
response.sendError(404, "No classification found");
|
372
|
fileName = "Error";
|
373
|
}
|
374
|
}
|
375
|
|
376
|
|
377
|
fileName = fileName + "_" + uuidListToString(subtreeUuids, 40) + ".zip";
|
378
|
|
379
|
return fileName;
|
380
|
|
381
|
}
|
382
|
|
383
|
private String uuidListToString(UuidList uuidList, Integer truncate) {
|
384
|
String result = null;
|
385
|
for (UUID uuid : uuidList){
|
386
|
result = CdmUtils.concat("_", uuid.toString());
|
387
|
}
|
388
|
if (result != null && result.length() > truncate){
|
389
|
result = result.substring(0, truncate);
|
390
|
}
|
391
|
return result;
|
392
|
}
|
393
|
|
394
|
@Override
|
395
|
public void setService(IService service) {}
|
396
|
|
397
|
@Override
|
398
|
public void setResourceLoader(ResourceLoader resourceLoader) {
|
399
|
this.resourceLoader = resourceLoader;
|
400
|
}
|
401
|
|
402
|
}
|