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 eu.etaxonomy.cdm.common.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.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.getDedupHelper().replaceAuthorNamesAndNomRef(state, name);
113

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

    
117
        makeTaxon(state, name);
118

    
119
    }
120

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
354
        }
355
    }
356

    
357

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

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