Project

General

Profile

Download (17.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 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.io.tropicos.in;
10

    
11
import java.net.URI;
12
import java.net.URISyntaxException;
13
import java.util.Map;
14
import java.util.Set;
15
import java.util.UUID;
16

    
17
import org.springframework.stereotype.Component;
18

    
19
import eu.etaxonomy.cdm.api.service.dto.IdentifiedEntityDTO;
20
import eu.etaxonomy.cdm.api.service.pager.Pager;
21
import eu.etaxonomy.cdm.common.CdmUtils;
22
import eu.etaxonomy.cdm.io.csv.in.CsvImportBase;
23
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
24
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
25
import eu.etaxonomy.cdm.model.common.Language;
26
import eu.etaxonomy.cdm.model.common.VerbatimTimePeriod;
27
import eu.etaxonomy.cdm.model.description.Feature;
28
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
29
import eu.etaxonomy.cdm.model.description.TextData;
30
import eu.etaxonomy.cdm.model.media.Media;
31
import eu.etaxonomy.cdm.model.name.INonViralName;
32
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
33
import eu.etaxonomy.cdm.model.name.TaxonName;
34
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
35
import eu.etaxonomy.cdm.model.reference.Reference;
36
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
37
import eu.etaxonomy.cdm.model.taxon.Classification;
38
import eu.etaxonomy.cdm.model.taxon.Taxon;
39
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
40
import eu.etaxonomy.cdm.model.term.DefinedTerm;
41
import eu.etaxonomy.cdm.persistence.query.MatchMode;
42
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
43
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
44
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
45

    
46
/**
47
 * @author a.mueller
48
 * @since 15.11.2017
49
 */
50
@Component
51
public class TropicosNameImport<STATE extends TropicosNameImportState>
52
        extends CsvImportBase<TropicosNameImportConfigurator, STATE, TaxonName>{
53

    
54
    private static final long serialVersionUID = -4111479364751713088L;
55

    
56
    private static final String INPUT_FULLNAME_WITH_AUTHORS = "FullnameWithAuthors";
57
    private static final String INPUT_FULL_NAME_NO_AUTHORS = "FullnameNoAuthors";
58
    private static final String INPUT_SOURCE_ID = "SourceID";
59

    
60
    private static final String OUTPUT_NAME_ID = "OutputNameID";
61
    private static final String OUTPUT_HOW_MATCHED = "OutputHowMatched";
62
    private static final String OUTPUT_FULL_NAME_WITH_AUTHORS = "OutputFullNameWithAuthors";
63
    private static final String OUTPUT_ABBREV_TITLE = "OutputAbbreviatedTitle";
64
    private static final String OUTPUT_COLLATION = "OutputCollation";
65
    private static final String OUTPUT_VOLUME = "OutputVolume";
66
    private static final String OUTPUT_ISSUE = "OutputIssue";
67
    private static final String OUTPUT_PAGE = "OutputPage";
68
    private static final String OUTPUT_TITLE_PAGE_YEAR = "OutputTitlePageYear";
69
    private static final String OUTPUT_YEAR_PUBLISHED = "OutputYearPublished";
70
    private static final String OUTPUT_NOM_STATUS = "OutputNomenclatureStatus";
71
    private static final String OUTPUT_BHL_LINK = "OutputBHLLink";
72
    private static final String OUTPUT_BATCH_ID = "OutputBatchID";
73
    private static final String NOM_PUB_TYPE = "NomPubType";
74
    private static final String IPNI_ID = "IPNI-ID";
75

    
76

    
77
    /**
78
     * {@inheritDoc}
79
     */
80
    @Override
81
    protected void handleSingleLine(STATE state) {
82
        TaxonName name = makeName(state);
83
        if (name == null){
84
            return;
85
        }
86
        if (checkAndAddIdentifier(state, name, INPUT_SOURCE_ID,
87
                state.getConfig().isAllowWfoDuplicates(), DefinedTerm.IDENTIFIER_NAME_WFO())){
88
            return;
89
        }
90
        if (checkAndAddIdentifier(state, name, IPNI_ID,
91
                state.getConfig().isAllowIpniDuplicates(), DefinedTerm.IDENTIFIER_NAME_IPNI())){
92
            return;
93
        }
94
        if (checkAndAddIdentifier(state, name, OUTPUT_NAME_ID,
95
                state.getConfig().isAllowTropicosDuplicates(), DefinedTerm.IDENTIFIER_NAME_TROPICOS())){
96
            return;
97
        }
98

    
99
        Map<String, String> record = state.getCurrentRecord();
100

    
101
        makeReference(state, name);
102
        makeProtologue(state, name);
103
        makeNomStatus(state, name);
104

    
105
        if (record.get(OUTPUT_HOW_MATCHED) != null){
106
            //ignore
107
        }
108
        if (record.get(OUTPUT_ISSUE) != null){
109
            //ignore
110
        }
111
        if (record.get(OUTPUT_BATCH_ID) != null){
112
            //ignore
113
        }
114
        if (record.get(OUTPUT_COLLATION) != null){
115
            //ignore
116
        }
117
        state.getDedupHelper().replaceAuthorNamesAndNomRef(state, name);
118

    
119
        getNameService().saveOrUpdate(name);
120
        state.getResult().addNewRecords(TaxonName.class.getSimpleName(), 1);
121

    
122
        makeTaxon(state, name);
123

    
124
    }
125

    
126

    
127
    /**
128
     * @param state
129
     * @param name
130
     */
131
    private void makeNomStatus(STATE state, TaxonName name) {
132
        String nomStatusStr = state.getCurrentRecord().get(OUTPUT_NOM_STATUS);
133
        if (nomStatusStr == null || nomStatusStr.equalsIgnoreCase("No opinion")){
134
            return;
135
        }else{
136
            NomenclaturalStatusType status = null;
137
            try {
138
                status = NomenclaturalStatusType.getNomenclaturalStatusTypeByLabel(nomStatusStr);
139
            } catch (UnknownCdmTypeException e) {
140
                try {
141
                    status = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(nomStatusStr, name);
142
                } catch (UnknownCdmTypeException e1) {
143
                    //handle later
144
                }
145
            }
146
            if (status == null){
147
                String message = "Nomenclatural status '%s' not recognized.";
148
                message = String.format(message, nomStatusStr);
149
                state.getResult().addWarning(message, state.getRow());
150
            }else{
151
                name.addStatus(status, null, null);
152
            }
153
        }
154
    }
155

    
156
    /**
157
     * @param state
158
     * @param name
159
     */
160
    private void makeProtologue(STATE state, TaxonName name) {
161
        String bhlLink = state.getCurrentRecord().get(OUTPUT_BHL_LINK);
162
        if (bhlLink == null){
163
            return;
164
        }
165

    
166
        TextData textData = TextData.NewInstance(Feature.PROTOLOGUE());
167
        this.getNameDescription(name, state).addElement(textData);
168
        URI uri;
169
        try {
170
            uri = new URI(bhlLink);
171
            textData.addMedia(Media.NewInstance(uri, null, null, null));
172

    
173
        } catch (URISyntaxException e) {
174
            String message = "(BHL) Link could not be recognized as valid URI. Link was not added to %s: %s";
175
            message = String.format(message, name.getTitleCache(), bhlLink);
176
            state.getResult().addWarning(message, state.getRow());
177
        }
178
    }
179

    
180
    //TODO implementation must be improved when matching of taxon names with existing names is implemented
181
    //=> the assumption that the only description is the description added by this import
182
    //is wrong then, but once protologues are handled differently we don't need this anymore
183
    //anyway
184
    private TaxonNameDescription getNameDescription(TaxonName name, STATE state) {
185
        Set<TaxonNameDescription> descriptions = name.getDescriptions();
186
        if (descriptions.size()>1){
187
            throw new IllegalStateException("Implementation does not yet support names with multiple descriptions");
188
        }else if (descriptions.size()==1){
189
            return descriptions.iterator().next();
190
        }else{
191
            TaxonNameDescription desc = TaxonNameDescription.NewInstance(name);
192
            desc.addSource(OriginalSourceType.Import, null, "NameDescription", getTransactionalSourceReference(state), null);
193
            return desc;
194
        }
195
    }
196

    
197
    /**
198
     * @param state
199
     * @param name
200
     */
201
    private void makeReference(STATE state, TaxonName name) {
202

    
203

    
204
        Map<String, String> record = state.getCurrentRecord();
205
        String type = record.get(NOM_PUB_TYPE);
206
        String abbrevTitle = record.get(OUTPUT_ABBREV_TITLE);
207
        String volume = record.get(OUTPUT_VOLUME);
208
        String detail = record.get(OUTPUT_PAGE);
209
        String titlePageYear = record.get(OUTPUT_TITLE_PAGE_YEAR);
210
        String yearPublished = record.get(OUTPUT_YEAR_PUBLISHED);
211
        if (CdmUtils.isBlank(abbrevTitle, volume, detail, titlePageYear, yearPublished)){
212
            //TODO
213
//            state.getResult().addInfo("No nomenclatural reference information given");
214
            return;
215
        }
216

    
217
        //Create and set title + in-Reference
218
        Reference reference;
219
        if (type == null){
220
            //TODO check against DB
221
            reference = ReferenceFactory.newGeneric();
222
            reference.setAbbrevTitle(abbrevTitle);
223
        }else if (type.equals("A")){
224
            reference = ReferenceFactory.newArticle();
225
            Reference journal = ReferenceFactory.newJournal();
226
            journal.setAbbrevTitle(abbrevTitle);
227
            reference.setInJournal(journal);
228
        }else if (type.equals("B")){
229
            reference = ReferenceFactory.newBook();
230
            reference.setAbbrevTitle(abbrevTitle);
231
        }else{
232
            String message = "Value for %s not recognized. Use generic reference instead";
233
            message = String.format(message, NOM_PUB_TYPE);
234
            state.getResult().addWarning(message, state.getRow());
235
            reference = ReferenceFactory.newGeneric();
236
            reference.setAbbrevTitle(abbrevTitle);
237
        }
238
        reference.setVolume(volume);
239

    
240
        //date
241
        if (titlePageYear != null){
242
            if (yearPublished == null){
243
                VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(titlePageYear);
244
                reference.setDatePublished(tp);
245
            }else{
246
                VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(yearPublished);
247
                reference.setDatePublished(tp);
248
            }
249
        }else if (yearPublished != null){
250
            VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(yearPublished);
251
            reference.setDatePublished(tp);
252
        }
253

    
254
        //add to name
255
        name.setNomenclaturalReference(reference);
256
        name.setNomenclaturalMicroReference(detail);
257
        //author
258
        if (state.getConfig().isAddAuthorsToReference()){
259
            TeamOrPersonBase<?> author = name.getCombinationAuthorship();
260
            if (author != null){
261
                reference.setAuthorship(author);
262
            }
263
        }
264

    
265
        //source
266
        addSourceReference(state, reference);
267
    }
268

    
269
    /**
270
     * Checks if the sourceId (WFO ID) already exists in the database.
271
     * @param state
272
     * @param name
273
     * @param idAttr
274
     * @param allowDuplicate
275
     * @param identifierType
276
     * @return <code>true</code> if sourceId already exists.
277
     */
278
    private boolean checkAndAddIdentifier(STATE state, TaxonName name, String idAttr,
279
            boolean allowDuplicate, DefinedTerm identifierType) {
280
        String identifier = state.getCurrentRecord().get(idAttr);
281
        if (identifier == null){
282
            return false;
283
        }
284

    
285
        if (! allowDuplicate || state.getConfig().isReportDuplicateIdentifier()){
286
            //TODO precompute existing per session or, at least, implement count
287
            Pager<IdentifiedEntityDTO<TaxonName>> existing = getNameService().findByIdentifier(TaxonName.class, identifier, identifierType, MatchMode.EXACT, false, null, null, null);
288
            if (existing.getCount() > 0){
289
                //TODO make language configurable
290
                Language language = Language.DEFAULT();
291
                if (! allowDuplicate){
292
                    String message = "The name with the given identifier (%s: %s) exists already in the database. Record is not imported.";
293
                    message = String.format(message, identifierType.getPreferredRepresentation(Language.DEFAULT()).getText(), identifier);
294
                    state.getResult().addWarning(message, state.getRow());
295
                    return true;
296
                }else{
297
                    String message = "The name with the given identifier (%s: %s) exists already in the database. Record is imported but maybe needs to be reviewed.";
298
                    message = String.format(message, identifierType.getPreferredRepresentation(language).getText(), identifier);
299
                    state.getResult().addWarning(message, state.getRow());
300
                }
301
            }
302
        }
303

    
304
        name.addIdentifier(identifier, identifierType);
305
        return false;
306
    }
307

    
308
    private NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
309

    
310
    /**
311
     * @param state
312
     * @return
313
     */
314
    private TaxonName makeName(STATE state) {
315
        Map<String, String> record = state.getCurrentRecord();
316
        String fullNameStr = record.get(OUTPUT_FULL_NAME_WITH_AUTHORS);
317
        String nameStr = record.get(INPUT_FULL_NAME_NO_AUTHORS);
318
        String inputFullNameStr = record.get(INPUT_FULLNAME_WITH_AUTHORS);
319
        if (inputFullNameStr != null && fullNameStr != null){
320
            if (inputFullNameStr.replaceAll("\\s", "").equals(fullNameStr.replaceAll("\\s", ""))){
321
                String message = "Full input (%s) and full output (%s) name are not equal. Record is, however, processed.";
322
                message = String.format(message, inputFullNameStr, fullNameStr);
323
                state.getResult().addWarning(message, state.getRow());
324
            }
325
        }else if (inputFullNameStr != null && fullNameStr == null){
326
            fullNameStr = inputFullNameStr;
327
        }
328

    
329
        INonViralName name;
330
        if (fullNameStr == null && nameStr == null){
331
            String message = "No name given. No record will be imported.";
332
            state.getResult().addWarning(message, state.getRow());
333
            return null;
334
        }else if (fullNameStr != null){
335
            name = parser.parseFullName(fullNameStr, state.getConfig().getNomenclaturalCode(), null);
336
            if (nameStr != null && !nameStr.equals(name.getNameCache())){
337
                String message = "Name with authors (%s) and without authors (%s) is not consistent";
338
                message = String.format(message, fullNameStr, nameStr);
339
                state.getResult().addWarning(message, state.getRow());
340
            }
341
        }else{
342
            name = parser.parseSimpleName(nameStr, state.getConfig().getNomenclaturalCode(), null);
343
        }
344

    
345
        if (name.isProtectedTitleCache() || name.isProtectedNameCache() || name.isProtectedAuthorshipCache()){
346
            String message = "Name (%s) could not be fully parsed, but is processed";
347
            message = String.format(message, name.getTitleCache());
348
            state.getResult().addWarning(message, state.getRow());
349
        }
350
        addSourceReference(state, (TaxonName)name);
351
        return (TaxonName)name;
352
    }
353

    
354
    /**
355
     * @param state
356
     * @param name
357
     */
358
    private void addSourceReference(STATE state, IdentifiableEntity<?> entity) {
359
        entity.addImportSource(null, null, getTransactionalSourceReference(state), "line " + state.getLine());
360
    }
361

    
362

    
363
    @Override
364
    protected void refreshTransactionStatus(STATE state) {
365
        super.refreshTransactionStatus(state);
366
    }
367

    
368

    
369
    /**
370
     * @param state
371
     * @param name
372
     */
373
    private void makeTaxon(STATE state, TaxonName name) {
374
        if (!state.getConfig().isCreateTaxa()){
375
            return;
376
        }else{
377
            //or do we want to allow to define an own sec reference?
378
            Reference sec = getTransactionalSourceReference(state);
379
            Taxon taxon = Taxon.NewInstance(name, sec);
380
            TaxonNode parentNode = getParentNode(state);
381
            if (parentNode != null){
382
                TaxonNode newNode = parentNode.addChildTaxon(taxon, null, null);
383
                if (state.getConfig().isUnplaced()){
384
                    newNode.setUnplaced(true);
385
                }
386
            }
387
            addSourceReference(state, taxon);
388
            this.getTaxonService().saveOrUpdate(taxon);
389
            state.getResult().addNewRecords(Taxon.class.getSimpleName(), 1);
390

    
391
        }
392
    }
393

    
394

    
395
    /**
396
     * Transactional save method to retrieve the parent node
397
     * @param state
398
     * @param sec
399
     * @return
400
     */
401
    protected TaxonNode getParentNode(STATE state) {
402
        TaxonNode parentNode = state.getParentNode();
403
        if (parentNode == null){
404
            if (state.getConfig().getParentNodeUuid() != null){
405
                parentNode = getTaxonNodeService().find(state.getConfig().getParentNodeUuid());
406
                if (parentNode == null){
407
                    //node does not exist => create new classification
408
                    Classification classification = makeClassification(state);
409
                    parentNode = classification.getRootNode();
410
                    parentNode.setUuid(state.getConfig().getParentNodeUuid());
411
                }
412
            }else {
413
                Classification classification = makeClassification(state);
414
                state.getConfig().setParentNodeUuid(classification.getRootNode().getUuid());
415
                parentNode = classification.getRootNode();
416
            }
417
            state.setParentNode(parentNode);
418
        }
419
        return parentNode;
420
    }
421

    
422

    
423
    /**
424
     * @param state
425
     * @param sec
426
     * @return
427
     */
428
    protected Classification makeClassification(STATE state) {
429
        Reference ref = getTransactionalSourceReference(state);
430
        String classificationStr = state.getConfig().getClassificationName();
431
        if (isBlank(classificationStr)){
432
            classificationStr = "Tropicos import " + UUID.randomUUID();
433
        }
434
        Classification classification = Classification.NewInstance(classificationStr, ref, Language.UNDETERMINED());
435
        return classification;
436
    }
437

    
438

    
439

    
440
}
(1-1/3)