2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.io
.distribution
.excelupdate
;
11 import java
.util
.HashMap
;
12 import java
.util
.HashSet
;
13 import java
.util
.Iterator
;
16 import java
.util
.UUID
;
18 import org
.apache
.log4j
.Logger
;
19 import org
.springframework
.stereotype
.Component
;
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
;
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.
41 * TODO where is the export to be found?
43 * This class is initiated by #6524
50 public class ExcelDistributionUpdate
51 extends ExcelImportBase
<ExcelDistributionUpdateState
, ExcelDistributionUpdateConfigurator
, ExcelRowBase
>{
53 private static final long serialVersionUID
= 621338661492857764L;
54 private static final Logger logger
= Logger
.getLogger(ExcelDistributionUpdate
.class);
56 private static final String AREA_MAP
= "AreaMap";
62 protected void analyzeRecord(Map
<String
, String
> record
, ExcelDistributionUpdateState state
) {
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");
76 if ("taxon_uuid".equals(taxonUuid
)){
79 UUID uuidTaxon
= UUID
.fromString(taxonUuid
);
80 Taxon taxon
= (Taxon
)getTaxonService().find(uuidTaxon
);
82 String message
= line
+ "Taxon for uuid not found: " + uuidTaxon
;
83 state
.getResult().addError(message
);
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
);
102 private void handleAreasForTaxon(ExcelDistributionUpdateState state
, Taxon taxon
, Map
<String
, String
> record
,
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
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
);
129 oldReducedDescriptions
.add(CdmBase
.deproxy(inDescription
, TaxonDescription
.class));
131 existingDistr
.setStatus(newDistribution
.getStatus());
132 result
.addUpdatedRecord(existingDistr
);
133 existingDistr
.addImportSource(null, null, state
.getConfig().getSourceReference(), "row "+state
.getCurrentLine());
136 // addSource? => not if nothing changed
140 if (hasChange
&& newDistribution
!= null){
141 newDescription
.addElement(newDistribution
);
142 result
.addNewRecord(newDistribution
);
145 //add new description to taxon if any new element exists
146 if (!newDescription
.getElements().isEmpty()){
147 taxon
.addDescription(newDescription
);
148 result
.addNewRecord(newDescription
);
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
);
163 private String
getTaxonLabel(Taxon taxon
) {
164 return taxon
.getName() == null ? taxon
.getTitleCache() : taxon
.getName().getTitleCache();
167 private Map
<NamedArea
, Distribution
> getNewDistributions(ExcelDistributionUpdateState state
,
168 Map
<String
, String
> record
, String line
) {
170 Map
<NamedArea
, Distribution
> result
= new HashMap
<>();
172 Set
<String
> keys
= record
.keySet();
173 keys
= removeNonAreaKeys(keys
);
174 for (String key
: keys
){
175 NamedArea area
= getAreaByIdInVoc(state
, key
, line
);
177 String statusStr
= record
.get(key
);
178 PresenceAbsenceTerm status
= getStatusByStatusStr(state
, statusStr
, line
);
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
);
189 result
.put(area
, null);
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
)){
213 String message
= line
+ "Status string not recognized: " + statusStr
+". Status not imported.";
214 logger
.warn(message
);
215 state
.getResult().addWarning(message
);
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
);
234 NamedArea area
= areaMap
.get(id
);
242 private Map
<String
, NamedArea
> createAreaMap(ExcelDistributionUpdateState state
) {
243 Map
<String
, NamedArea
> result
= new HashMap
<>();
244 TermVocabulary
<?
> voc
= getVocabularyService().find(state
.getConfig().getAreaVocabularyUuid());
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
);
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)")){
269 private boolean isEqualDistribution(Distribution existingDistribution
, Distribution newDistribution
) {
270 if (existingDistribution
== null || newDistribution
== null){
271 return existingDistribution
== newDistribution
;
273 if (existingDistribution
.getArea().equals(newDistribution
.getArea())){
274 if (CdmUtils
.nullSafeEqual(existingDistribution
.getStatus(), newDistribution
.getStatus())){
287 private Map
<NamedArea
, Set
<Distribution
>> getExistingDistributions(
288 ExcelDistributionUpdateState state
, Taxon taxon
,
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
);
299 set
= new HashSet
<>();
300 result
.put(area
, set
);
302 set
.add(distribution
);
313 protected void secondPass(ExcelDistributionUpdateState state
) {
321 protected boolean isIgnore(ExcelDistributionUpdateState state
) {
325 protected boolean needsNomenclaturalCode() {