Project

General

Profile

Download (16.9 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.URISyntaxException;
12
import java.util.Map;
13
import java.util.Set;
14
import java.util.UUID;
15

    
16
import org.springframework.stereotype.Component;
17

    
18
import eu.etaxonomy.cdm.api.service.dto.IdentifiedEntityDTO;
19
import eu.etaxonomy.cdm.api.service.pager.Pager;
20
import eu.etaxonomy.cdm.common.CdmUtils;
21
import eu.etaxonomy.cdm.common.URI;
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.TaxonNameDescription;
28
import eu.etaxonomy.cdm.model.media.ExternalLinkType;
29
import eu.etaxonomy.cdm.model.name.INonViralName;
30
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
31
import eu.etaxonomy.cdm.model.name.TaxonName;
32
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
33
import eu.etaxonomy.cdm.model.reference.Reference;
34
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
35
import eu.etaxonomy.cdm.model.taxon.Classification;
36
import eu.etaxonomy.cdm.model.taxon.Taxon;
37
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
38
import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
39
import eu.etaxonomy.cdm.model.term.DefinedTerm;
40
import eu.etaxonomy.cdm.persistence.query.MatchMode;
41
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
42
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
43
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
44

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

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

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

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

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

    
94
        Map<String, String> record = state.getCurrentRecord();
95

    
96
        makeReference(state, name);
97
        makeProtologue(state, name);
98
        makeNomStatus(state, name);
99

    
100
        if (record.get(OUTPUT_HOW_MATCHED) != null){
101
            //ignore
102
        }
103
        if (record.get(OUTPUT_ISSUE) != null){
104
            //ignore
105
        }
106
        if (record.get(OUTPUT_BATCH_ID) != null){
107
            //ignore
108
        }
109
        if (record.get(OUTPUT_COLLATION) != null){
110
            //ignore
111
        }
112
        state.getDeduplicationHelper().replaceAuthorNamesAndNomRef(name);
113

    
114
        getNameService().saveOrUpdate(name);
115
        state.getResult().addNewRecords(TaxonName.class.getSimpleName(), 1);
116

    
117
        makeTaxon(state, name);
118
    }
119

    
120
    private void makeNomStatus(STATE state, TaxonName name) {
121
        String nomStatusStr = state.getCurrentRecord().get(OUTPUT_NOM_STATUS);
122
        if (nomStatusStr == null || nomStatusStr.equalsIgnoreCase("No opinion")){
123
            return;
124
        }else{
125
            NomenclaturalStatusType status = null;
126
            try {
127
                status = NomenclaturalStatusType.getNomenclaturalStatusTypeByLabel(nomStatusStr);
128
            } catch (UnknownCdmTypeException e) {
129
                try {
130
                    status = NomenclaturalStatusType.getNomenclaturalStatusTypeByAbbreviation(nomStatusStr, name);
131
                } catch (UnknownCdmTypeException e1) {
132
                    //handle later
133
                }
134
            }
135
            if (status == null){
136
                String message = "Nomenclatural status '%s' not recognized.";
137
                message = String.format(message, nomStatusStr);
138
                state.getResult().addWarning(message, state.getRow());
139
            }else{
140
                name.addStatus(status, null, null);
141
            }
142
        }
143
    }
144

    
145
    private void makeProtologue(STATE state, TaxonName name) {
146
        String bhlLink = state.getCurrentRecord().get(OUTPUT_BHL_LINK);
147
        if (bhlLink == null){
148
            return;
149
        }
150

    
151
        try {
152
            URI uri = new URI(bhlLink);
153
            name.addProtologue(uri, null, ExternalLinkType.WebSite);
154
        } catch (URISyntaxException e) {
155
            String message = "(BHL) Link could not be recognized as valid URI. Link was not added to %s: %s";
156
            message = String.format(message, name.getTitleCache(), bhlLink);
157
            state.getResult().addWarning(message, state.getRow());
158
        }
159
    }
160

    
161
    //TODO implementation must be improved when matching of taxon names with existing names is implemented
162
    //=> the assumption that the only description is the description added by this import
163
    //is wrong then, but once protologues are handled differently we don't need this anymore
164
    //anyway
165
    private TaxonNameDescription getNameDescription(TaxonName name, STATE state) {
166
        Set<TaxonNameDescription> descriptions = name.getDescriptions();
167
        if (descriptions.size()>1){
168
            throw new IllegalStateException("Implementation does not yet support names with multiple descriptions");
169
        }else if (descriptions.size()==1){
170
            return descriptions.iterator().next();
171
        }else{
172
            TaxonNameDescription desc = TaxonNameDescription.NewInstance(name);
173
            desc.addSource(OriginalSourceType.Import, null, "NameDescription", getTransactionalSourceReference(state), null);
174
            return desc;
175
        }
176
    }
177

    
178
    private void makeReference(STATE state, TaxonName name) {
179

    
180
        Map<String, String> record = state.getCurrentRecord();
181
        String type = record.get(NOM_PUB_TYPE);
182
        String abbrevTitle = record.get(OUTPUT_ABBREV_TITLE);
183
        String volume = record.get(OUTPUT_VOLUME);
184
        String detail = record.get(OUTPUT_PAGE);
185
        String titlePageYear = record.get(OUTPUT_TITLE_PAGE_YEAR);
186
        String yearPublished = record.get(OUTPUT_YEAR_PUBLISHED);
187
        if (CdmUtils.isBlank(abbrevTitle, volume, detail, titlePageYear, yearPublished)){
188
            //TODO
189
//            state.getResult().addInfo("No nomenclatural reference information given");
190
            return;
191
        }
192

    
193
        //Create and set title + in-Reference
194
        Reference reference;
195
        if (type == null){
196
            //TODO check against DB
197
            reference = ReferenceFactory.newGeneric();
198
            reference.setAbbrevTitle(abbrevTitle);
199
        }else if (type.equals("A")){
200
            reference = ReferenceFactory.newArticle();
201
            Reference journal = ReferenceFactory.newJournal();
202
            journal.setAbbrevTitle(abbrevTitle);
203
            reference.setInJournal(journal);
204
        }else if (type.equals("B")){
205
            reference = ReferenceFactory.newBook();
206
            reference.setAbbrevTitle(abbrevTitle);
207
        }else{
208
            String message = "Value for %s not recognized. Use generic reference instead";
209
            message = String.format(message, NOM_PUB_TYPE);
210
            state.getResult().addWarning(message, state.getRow());
211
            reference = ReferenceFactory.newGeneric();
212
            reference.setAbbrevTitle(abbrevTitle);
213
        }
214
        reference.setVolume(volume);
215

    
216
        //date
217
        if (titlePageYear != null){
218
            if (yearPublished == null){
219
                VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(titlePageYear);
220
                reference.setDatePublished(tp);
221
            }else{
222
                VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(yearPublished);
223
                reference.setDatePublished(tp);
224
            }
225
        }else if (yearPublished != null){
226
            VerbatimTimePeriod tp = TimePeriodParser.parseStringVerbatim(yearPublished);
227
            reference.setDatePublished(tp);
228
        }
229

    
230
        //add to name
231
        name.setNomenclaturalReference(reference);
232
        name.setNomenclaturalMicroReference(detail);
233
        //author
234
        if (state.getConfig().isAddAuthorsToReference()){
235
            TeamOrPersonBase<?> author = name.getCombinationAuthorship();
236
            if (author != null){
237
                reference.setAuthorship(author);
238
            }
239
        }
240

    
241
        //source
242
        addSourceReference(state, reference);
243
    }
244

    
245
    /**
246
     * Checks if the sourceId (WFO ID) already exists in the database.
247
     * @param state
248
     * @param name
249
     * @param idAttr
250
     * @param allowDuplicate
251
     * @param identifierType
252
     * @return <code>true</code> if sourceId already exists.
253
     */
254
    private boolean checkAndAddIdentifier(STATE state, TaxonName name, String idAttr,
255
            boolean allowDuplicate, DefinedTerm identifierType) {
256
        String identifier = state.getCurrentRecord().get(idAttr);
257
        if (identifier == null){
258
            return false;
259
        }
260

    
261
        if (! allowDuplicate || state.getConfig().isReportDuplicateIdentifier()){
262
            //TODO precompute existing per session or, at least, implement count
263
            Pager<IdentifiedEntityDTO<TaxonName>> existing = getNameService().findByIdentifier(TaxonName.class, identifier, identifierType, MatchMode.EXACT, false, null, null, null);
264
            if (existing.getCount() > 0){
265
                //TODO make language configurable
266
                Language language = Language.DEFAULT();
267
                if (! allowDuplicate){
268
                    String message = "The name with the given identifier (%s: %s) exists already in the database. Record is not imported.";
269
                    message = String.format(message, identifierType.getPreferredRepresentation(Language.DEFAULT()).getText(), identifier);
270
                    state.getResult().addWarning(message, state.getRow());
271
                    return true;
272
                }else{
273
                    String message = "The name with the given identifier (%s: %s) exists already in the database. Record is imported but maybe needs to be reviewed.";
274
                    message = String.format(message, identifierType.getPreferredRepresentation(language).getText(), identifier);
275
                    state.getResult().addWarning(message, state.getRow());
276
                }
277
            }
278
        }
279

    
280
        name.addIdentifier(identifier, identifierType);
281
        return false;
282
    }
283

    
284
    private NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
285

    
286
    private TaxonName makeName(STATE state) {
287
        Map<String, String> record = state.getCurrentRecord();
288
        String fullNameStr = record.get(OUTPUT_FULL_NAME_WITH_AUTHORS);
289
        String nameStr = record.get(INPUT_FULL_NAME_NO_AUTHORS);
290
        String inputFullNameStr = record.get(INPUT_FULLNAME_WITH_AUTHORS);
291
        if (inputFullNameStr != null && fullNameStr != null){
292
            if (inputFullNameStr.replaceAll("\\s", "").equals(fullNameStr.replaceAll("\\s", ""))){
293
                String message = "Full input (%s) and full output (%s) name are not equal. Record is, however, processed.";
294
                message = String.format(message, inputFullNameStr, fullNameStr);
295
                state.getResult().addWarning(message, state.getRow());
296
            }
297
        }else if (inputFullNameStr != null && fullNameStr == null){
298
            fullNameStr = inputFullNameStr;
299
        }
300

    
301
        INonViralName name;
302
        if (fullNameStr == null && nameStr == null){
303
            String message = "No name given. No record will be imported.";
304
            state.getResult().addWarning(message, state.getRow());
305
            return null;
306
        }else if (fullNameStr != null){
307
            name = parser.parseFullName(fullNameStr, state.getConfig().getNomenclaturalCode(), null);
308
            if (nameStr != null && !nameStr.equals(name.getNameCache())){
309
                String message = "Name with authors (%s) and without authors (%s) is not consistent";
310
                message = String.format(message, fullNameStr, nameStr);
311
                state.getResult().addWarning(message, state.getRow());
312
            }
313
        }else{
314
            name = parser.parseSimpleName(nameStr, state.getConfig().getNomenclaturalCode(), null);
315
        }
316

    
317
        if (name.isProtectedTitleCache() || name.isProtectedNameCache() || name.isProtectedAuthorshipCache()){
318
            String message = "Name (%s) could not be fully parsed, but is processed";
319
            message = String.format(message, name.getTitleCache());
320
            state.getResult().addWarning(message, state.getRow());
321
        }
322
        addSourceReference(state, (TaxonName)name);
323
        return (TaxonName)name;
324
    }
325

    
326
    private void addSourceReference(STATE state, IdentifiableEntity<?> entity) {
327
        entity.addImportSource(null, null, getTransactionalSourceReference(state), "line " + state.getLine());
328
    }
329

    
330
    @Override
331
    protected void refreshTransactionStatus(STATE state) {
332
        super.refreshTransactionStatus(state);
333
    }
334

    
335
    private void makeTaxon(STATE state, TaxonName name) {
336
        if (!state.getConfig().isCreateTaxa()){
337
            return;
338
        }else{
339
            //or do we want to allow to define an own sec reference?
340
            Reference sec = getTransactionalSourceReference(state);
341
            Taxon taxon = Taxon.NewInstance(name, sec);
342
            TaxonNode parentNode = getParentNode(state);
343
            if (parentNode != null){
344
                TaxonNode newNode = parentNode.addChildTaxon(taxon, null, null);
345
                if (state.getConfig().isUnplaced()){
346
                    newNode.setStatus(TaxonNodeStatus.UNPLACED);
347
                }
348
            }
349
            addSourceReference(state, taxon);
350
            this.getTaxonService().saveOrUpdate(taxon);
351
            state.getResult().addNewRecords(Taxon.class.getSimpleName(), 1);
352

    
353
        }
354
    }
355

    
356

    
357
    /**
358
     * Transactional save method to retrieve the parent node
359
     */
360
    protected TaxonNode getParentNode(STATE state) {
361
        TaxonNode parentNode = state.getParentNode();
362
        if (parentNode == null){
363
            if (state.getConfig().getParentNodeUuid() != null){
364
                parentNode = getTaxonNodeService().find(state.getConfig().getParentNodeUuid());
365
                if (parentNode == null){
366
                    //node does not exist => create new classification
367
                    Classification classification = makeClassification(state);
368
                    parentNode = classification.getRootNode();
369
                    parentNode.setUuid(state.getConfig().getParentNodeUuid());
370
                }
371
            }else {
372
                Classification classification = makeClassification(state);
373
                state.getConfig().setParentNodeUuid(classification.getRootNode().getUuid());
374
                parentNode = classification.getRootNode();
375
            }
376
            state.setParentNode(parentNode);
377
        }
378
        return parentNode;
379
    }
380

    
381
    protected Classification makeClassification(STATE state) {
382
        Reference ref = getTransactionalSourceReference(state);
383
        String classificationStr = state.getConfig().getClassificationName();
384
        if (isBlank(classificationStr)){
385
            classificationStr = "Tropicos import " + UUID.randomUUID();
386
        }
387
        Classification classification = Classification.NewInstance(classificationStr, ref, Language.UNDETERMINED());
388
        return classification;
389
    }
390
}
(1-1/3)