Project

General

Profile

Download (12.3 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.distribution.excelupdate;
10

    
11
import java.util.HashMap;
12
import java.util.HashSet;
13
import java.util.Iterator;
14
import java.util.Map;
15
import java.util.Set;
16
import java.util.UUID;
17

    
18
import org.apache.log4j.Logger;
19
import org.springframework.stereotype.Component;
20

    
21
import eu.etaxonomy.cdm.common.CdmUtils;
22
import eu.etaxonomy.cdm.io.common.ImportResult;
23
import eu.etaxonomy.cdm.io.excel.common.ExcelImportBase;
24
import eu.etaxonomy.cdm.io.excel.common.ExcelRowBase;
25
import eu.etaxonomy.cdm.model.common.CdmBase;
26
import eu.etaxonomy.cdm.model.description.DescriptionBase;
27
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
28
import eu.etaxonomy.cdm.model.description.Distribution;
29
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
30
import eu.etaxonomy.cdm.model.description.TaxonDescription;
31
import eu.etaxonomy.cdm.model.location.NamedArea;
32
import eu.etaxonomy.cdm.model.taxon.Taxon;
33
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
34
import eu.etaxonomy.cdm.model.term.TermVocabulary;
35

    
36
/**
37
 * This Import class updates existing distributions with the new state
38
 * described in the Excel file. It requires that the data was exported
39
 * before in the defined format.
40
 *
41
 * TODO where is the export to be found?
42
 *
43
 * This class is initiated by #6524
44
 *
45
 * @author a.mueller
46
 * @since 04.04.2017
47
 *
48
 */
49
@Component
50
public class ExcelDistributionUpdate
51
            extends ExcelImportBase<ExcelDistributionUpdateState, ExcelDistributionUpdateConfigurator, ExcelRowBase>{
52

    
53
    private static final long serialVersionUID = 621338661492857764L;
54
    private static final Logger logger = Logger.getLogger(ExcelDistributionUpdate.class);
55

    
56
    private static final String AREA_MAP = "AreaMap";
57

    
58
    /**
59
     * {@inheritDoc}
60
     */
61
    @Override
62
    protected void analyzeRecord(Map<String, String> record, ExcelDistributionUpdateState state) {
63
        // nothing to do
64
    }
65

    
66
    /**
67
     * {@inheritDoc}
68
     */
69
    @Override
70
    protected void firstPass(ExcelDistributionUpdateState state) {
71
        Map<String, String> record = state.getOriginalRecord();
72
        String line = state.getCurrentLine() + ": ";
73
        String taxonUuid = getValue(record, "taxon_uuid");
74
        String taxonName = getValue(record, "Taxonname");
75

    
76
        if ("taxon_uuid".equals(taxonUuid)){
77
            return;
78
        }
79
        UUID uuidTaxon = UUID.fromString(taxonUuid);
80
        Taxon taxon = (Taxon)getTaxonService().find(uuidTaxon);
81
        if (taxon == null){
82
            String message = line + "Taxon for uuid not found: " +  uuidTaxon;
83
            state.getResult().addError(message);
84
        }else{
85
            try {
86
                handleAreasForTaxon(state, taxon, record, line);
87
            } catch (Exception e) {
88
                String message = line + "An unexpected error occurred when handling %s (uuid: %s)";
89
                message = String.format(message, taxonName, taxonUuid);
90
                state.getResult().addError(message);
91
                state.getResult().addException(e);
92
            }
93
        }
94
    }
95

    
96
    /**
97
     * @param state
98
     * @param taxon
99
     * @param record
100
     * @param line
101
     */
102
    private void handleAreasForTaxon(ExcelDistributionUpdateState state, Taxon taxon, Map<String, String> record,
103
            String line) {
104
        ImportResult result = state.getResult();
105
        Map<NamedArea, Set<Distribution>> existingDistributions = getExistingDistributions(state, taxon, line);
106
        Map<NamedArea, Distribution> newDistributions = getNewDistributions(state, record, line);
107
        TaxonDescription newDescription = TaxonDescription.NewInstance();
108
        newDescription.addImportSource(null, null, state.getConfig().getSourceReference(), "row " + state.getCurrentLine());
109
        newDescription.setTitleCache("Updated distributions for " + getTaxonLabel(taxon), true);
110
        Set<TaxonDescription> oldReducedDescriptions = new HashSet<>();
111
        for (NamedArea area : newDistributions.keySet()){
112
            Set<Distribution> existingDistrForArea = existingDistributions.get(area);
113
            boolean hasChange = false;
114
            Distribution newDistribution = newDistributions.get(area);
115
            if (existingDistrForArea == null || existingDistrForArea.isEmpty()){
116
                if (newDistribution != null){
117
                    //new distribution exists, old distribution did not exist
118
                    hasChange = true;
119
                }
120
            }else{
121
                for (Distribution existingDistr : existingDistrForArea){
122
                    if (!isEqualDistribution(existingDistr, newDistribution)){
123
                        //distribution changed or deleted
124
                        if (state.getConfig().isCreateNewDistribution() || newDistribution == null ){
125
                            DescriptionBase<?> inDescription = existingDistr.getInDescription();
126
                            inDescription.removeElement(existingDistr);
127
                            result.addDeletedRecord(existingDistr);
128
                            hasChange = true;
129
                            oldReducedDescriptions.add(CdmBase.deproxy(inDescription, TaxonDescription.class));
130
                        }else{
131
                            existingDistr.setStatus(newDistribution.getStatus());
132
                            result.addUpdatedRecord(existingDistr);
133
                            existingDistr.addImportSource(null, null, state.getConfig().getSourceReference(), "row "+state.getCurrentLine());
134
                        }
135
                    }else{
136
    //                    addSource? => not if nothing changed
137
                    }
138
                }
139
            }
140
            if (hasChange && newDistribution != null){
141
                newDescription.addElement(newDistribution);
142
                result.addNewRecord(newDistribution);
143
            }
144
        }
145
        //add new description to taxon if any new element exists
146
        if (!newDescription.getElements().isEmpty()){
147
            taxon.addDescription(newDescription);
148
            result.addNewRecord(newDescription);
149
        }
150
        //remove old empty descriptions (oldReducedDescriptions) if really empty
151
        for (TaxonDescription desc : oldReducedDescriptions){
152
            if (desc.getElements().isEmpty()){
153
                desc.getTaxon().removeDescription(desc);
154
                result.addDeletedRecord(desc);
155
            }
156
        }
157
    }
158

    
159
    /**
160
     * @param taxon
161
     * @return
162
     */
163
    private String getTaxonLabel(Taxon taxon) {
164
        return taxon.getName() == null ? taxon.getTitleCache() : taxon.getName().getTitleCache();
165
    }
166

    
167
    private Map<NamedArea, Distribution> getNewDistributions(ExcelDistributionUpdateState state,
168
            Map<String, String> record, String line) {
169

    
170
        Map<NamedArea, Distribution> result = new HashMap<>();
171

    
172
        Set<String> keys = record.keySet();
173
        keys = removeNonAreaKeys(keys);
174
        for (String key : keys){
175
            NamedArea area = getAreaByIdInVoc(state, key, line);
176
            if (area != null){
177
                String statusStr = record.get(key);
178
                PresenceAbsenceTerm status = getStatusByStatusStr(state, statusStr, line);
179
                if (status != null){
180
                    Distribution distribution = Distribution.NewInstance(area, status);
181
                    distribution.addImportSource(null, null, state.getConfig().getSourceReference(), "row " + state.getCurrentLine());
182
                    Distribution previousDistribution = result.put(area, distribution);
183
                    if (previousDistribution != null){
184
                        String message = line + "Multiple distributions exist for same area (" + area.getTitleCache() +  ") in input source";
185
                        logger.warn(message);
186
                        state.getResult().addWarning(message);
187
                    }
188
                }else{
189
                    result.put(area, null);
190
                }
191
            }else{
192
                //??
193
            }
194
        }
195
        return result;
196
    }
197

    
198
    /**
199
     * @param statusStr
200
     * @return
201
     */
202
    private PresenceAbsenceTerm getStatusByStatusStr(ExcelDistributionUpdateState state, String statusStr, String line) {
203
//        FIXME replace hardcoded;
204
        if ("A".equals(statusStr)) {
205
            return PresenceAbsenceTerm.ABSENT();
206
        }else if ("P".equals(statusStr)) {
207
            return PresenceAbsenceTerm.PRESENT();
208
        }else if ("P?".equals(statusStr)) {
209
            return PresenceAbsenceTerm.PRESENT_DOUBTFULLY();
210
        }else if (isBlank(statusStr)){
211
            return null;
212
        }else{
213
            String message = line + "Status string not recognized: " +  statusStr +". Status not imported.";
214
            logger.warn(message);
215
            state.getResult().addWarning(message);
216
        }
217

    
218
        return null;
219
    }
220

    
221
    /**
222
     * @param state
223
     * @param key
224
     * @param line
225
     * @return
226
     */
227
    private NamedArea getAreaByIdInVoc(ExcelDistributionUpdateState state, String id, String line) {
228
        //TODO remember in state
229
        Map<String, NamedArea> areaMap = (Map<String, NamedArea>)state.getStatusItem(AREA_MAP);
230
        if (areaMap == null){
231
            areaMap = createAreaMap(state);
232
            state.putStatusItem(AREA_MAP, areaMap);
233
        }
234
        NamedArea area = areaMap.get(id);
235
        return area;
236
    }
237

    
238
    /**
239
     * @param state
240
     * @return
241
     */
242
    private Map<String, NamedArea> createAreaMap(ExcelDistributionUpdateState state) {
243
        Map<String, NamedArea> result = new HashMap<>();
244
        TermVocabulary<?> voc = getVocabularyService().find(state.getConfig().getAreaVocabularyUuid());
245
        //TODO handle null
246
        for (DefinedTermBase<?> obj : voc.getTerms()){
247
            //TODO handle exception
248
            NamedArea area = CdmBase.deproxy(obj, NamedArea.class);
249
            String key = area.getIdInVocabulary();
250
            result.put(key, area);
251
        }
252
        return result;
253
    }
254

    
255
    /**
256
     * @param keys
257
     * @return
258
     */
259
    private Set<String> removeNonAreaKeys(Set<String> keys) {
260
        Iterator<String> it = keys.iterator();
261
        while (it.hasNext()){
262
            if (it.next().matches("(Family|Taxonname|taxon_uuid)")){
263
                it.remove();
264
            }
265
        }
266
        return keys;
267
    }
268

    
269
    private boolean isEqualDistribution(Distribution existingDistribution, Distribution newDistribution) {
270
        if (existingDistribution == null || newDistribution == null){
271
            return existingDistribution == newDistribution;
272
        }
273
        if (existingDistribution.getArea().equals(newDistribution.getArea())){
274
            if (CdmUtils.nullSafeEqual(existingDistribution.getStatus(), newDistribution.getStatus())){
275
                return true;
276
            }
277
        }
278
        return false;
279
    }
280

    
281
    /**
282
     * @param state
283
     * @param taxon
284
     * @param line
285
     * @return
286
     */
287
    private Map<NamedArea, Set<Distribution>> getExistingDistributions(
288
            ExcelDistributionUpdateState state, Taxon taxon,
289
            String line) {
290
        Map<NamedArea, Set<Distribution>> result = new HashMap<>();
291
        //TODO better use service layer call to return only distributions, this might be necessary if the list of description elements is large
292
        for (TaxonDescription desc : taxon.getDescriptions()){
293
            for (DescriptionElementBase descElem : desc.getElements()){
294
                if (descElem.isInstanceOf(Distribution.class)){
295
                    Distribution distribution =  CdmBase.deproxy(descElem, Distribution.class);
296
                    NamedArea area = distribution.getArea();
297
                    Set<Distribution> set = result.get(area);
298
                    if (set == null){
299
                        set = new HashSet<>();
300
                        result.put(area, set);
301
                    }
302
                    set.add(distribution);
303
                }
304
            }
305
        }
306
        return result;
307
    }
308

    
309
    /**
310
     * {@inheritDoc}
311
     */
312
    @Override
313
    protected void secondPass(ExcelDistributionUpdateState state) {
314
        // nothing to do
315
    }
316

    
317
    /**
318
     * {@inheritDoc}
319
     */
320
    @Override
321
    protected boolean isIgnore(ExcelDistributionUpdateState state) {
322
        return false;
323
    }
324
    @Override
325
    protected boolean needsNomenclaturalCode() {
326
        return false;
327
    }
328

    
329
}
(1-1/3)