Merge branch 'release/5.45.0'
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / geo / DistributionServiceImpl.java
1 /**
2 * Copyright (C) 2023 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.api.service.geo;
10
11 import java.awt.Color;
12 import java.io.IOException;
13 import java.io.Reader;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.EnumSet;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.UUID;
23 import java.util.stream.Collectors;
24
25 import javax.persistence.EntityNotFoundException;
26
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29 import org.springframework.beans.factory.annotation.Autowired;
30 import org.springframework.stereotype.Service;
31 import org.springframework.transaction.annotation.Transactional;
32
33 import eu.etaxonomy.cdm.api.dto.portal.DistributionInfoDto;
34 import eu.etaxonomy.cdm.api.dto.portal.DistributionInfoDto.InfoPart;
35 import eu.etaxonomy.cdm.api.dto.portal.config.CondensedDistribution;
36 import eu.etaxonomy.cdm.api.dto.portal.config.CondensedDistributionConfiguration;
37 import eu.etaxonomy.cdm.api.dto.portal.config.DistributionInfoConfiguration;
38 import eu.etaxonomy.cdm.api.service.ICommonService;
39 import eu.etaxonomy.cdm.common.CdmUtils;
40 import eu.etaxonomy.cdm.model.common.CdmBase;
41 import eu.etaxonomy.cdm.model.common.Language;
42 import eu.etaxonomy.cdm.model.common.MarkerType;
43 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
44 import eu.etaxonomy.cdm.model.description.Distribution;
45 import eu.etaxonomy.cdm.model.description.Feature;
46 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
47 import eu.etaxonomy.cdm.model.description.TaxonDescription;
48 import eu.etaxonomy.cdm.model.location.NamedArea;
49 import eu.etaxonomy.cdm.model.term.DefinedTermBase;
50 import eu.etaxonomy.cdm.model.term.TermTree;
51 import eu.etaxonomy.cdm.model.term.TermVocabulary;
52 import eu.etaxonomy.cdm.persistence.dao.description.IDescriptionDao;
53 import eu.etaxonomy.cdm.persistence.dao.term.IDefinedTermDao;
54 import eu.etaxonomy.cdm.persistence.dao.term.ITermVocabularyDao;
55
56 /**
57 * @author a.mueller
58 * @date 08.02.2023
59 */
60 @Service
61 @Transactional(readOnly = true)
62 public class DistributionServiceImpl implements IDistributionService {
63
64 @SuppressWarnings("unused")
65 private static final Logger logger = LogManager.getLogger();
66
67 @Autowired
68 private IDescriptionDao dao;
69
70 @Autowired
71 private ICommonService commonService;
72
73 @Autowired
74 private IDefinedTermDao termDao;
75
76 @Autowired
77 private ITermVocabularyDao vocabDao;
78
79 @Autowired
80 private IGeoServiceAreaMapping areaMapping;
81
82 @Override
83 public DistributionInfoDto composeDistributionInfoFor(DistributionInfoConfiguration config,
84 UUID taxonUUID,
85 Map<UUID,Color> distributionStatusColorMap,
86 List<Language> languages,
87 List<String> propertyPaths){
88
89 Set<UUID> featureUuids = config.getFeatures();
90 Set<Feature> features = null;
91 if (!CdmUtils.isNullSafeEmpty(featureUuids)) {
92 features = termDao.list(featureUuids, null, null, null, null)
93 .stream().filter(t->t.isInstanceOf(Feature.class)).map(t->(Feature)t).collect(Collectors.toSet());
94 }
95
96 if (propertyPaths == null){
97 propertyPaths = Arrays.asList(new String []{});
98 }
99 // Adding default initStrategies to improve the performance of this method
100 // adding 'status' and 'area' has a good positive effect:
101 // filterDistributions() only takes 21% of the total method time (before it was 46%)
102 // at the same time the cost of the getDescriptionElementForTaxon is not increased at all!
103 //
104 // adding 'markers.markerType' is not improving the performance since it only
105 // moved the load from the filter method to the getDescriptionElementForTaxon()
106 // method.
107 // overall improvement by this means is by 42% (from 77,711 ms to 44,868 ms)
108 ArrayList<String> initStrategy = new ArrayList<>(propertyPaths);
109 if(!initStrategy.contains("status")) {
110 initStrategy.add("status");
111 }
112 if(!initStrategy.contains("area")) {
113 initStrategy.add("area");
114 }
115 if(!initStrategy.contains("markers.markerType")) {
116 initStrategy.add("markers.markerType");
117 }
118
119 List<Distribution> distributions = dao.getDescriptionElementForTaxon(
120 taxonUUID, features, Distribution.class, config.isIncludeUnpublished(), null, null, initStrategy);
121
122 TermTree<NamedArea> areaTree = null;
123 TermTree<PresenceAbsenceTerm> statusTree = null;
124 return new DistributionInfoBuilder(languages, commonService).build(
125 config, distributions, areaTree, statusTree,
126 distributionStatusColorMap, areaMapping);
127 }
128
129 @Override
130 public CondensedDistribution getCondensedDistribution(Set<Distribution> distributions,
131 TermTree<NamedArea> areaTree,
132 TermTree<PresenceAbsenceTerm> statusTree,
133 boolean statusOrderPreference,
134 Set<MarkerType> fallbackAreaMarkerTypes,
135 CondensedDistributionConfiguration config,
136 List<Language> langs) {
137
138 DistributionInfoConfiguration diConfig = new DistributionInfoConfiguration();
139 diConfig.setCondensedDistributionConfiguration(config);
140 diConfig.setFallbackAreaMarkerTypes(fallbackAreaMarkerTypes);
141 diConfig.setStatusOrderPreference(statusOrderPreference);
142 diConfig.setInfoParts(EnumSet.of(InfoPart.condensedDistribution));
143 DistributionInfoDto distInfo = new DistributionInfoBuilder(langs, commonService).build(diConfig, distributions, areaTree, statusTree, null, areaMapping);
144 return distInfo.getCondensedDistribution();
145
146 // Collection<DistributionTmpDto> filteredDistributions = DistributionServiceUtilities.filterDistributions(
147 // distributions, areaTree, statusTree, fallbackAreaMarkerTypes, false, statusOrderPreference,
148 // false, false, -99);
149 //
150 // //TODO exclude "undefined" status as long as status tree is not yet
151 // areaTree = areaTree == null ? DistributionServiceUtilities.getAreaTree(distributions, fallbackAreaMarkerTypes) : areaTree;
152 // SetMap<NamedArea,TermNode<NamedArea>> parentNodeMap = areaTree.getTerm2ParentNodeMap();
153 //
154 // CondensedDistribution condensedDistribution = DistributionServiceUtilities.getCondensedDistribution(
155 // filteredDistributions,
156 // parentNodeMap,
157 // config,
158 // langs);
159 // return condensedDistribution;
160 }
161
162 @Override
163 public void setMapping(NamedArea area, GeoServiceArea geoServiceArea) {
164 areaMapping.set(area, geoServiceArea);
165 }
166
167 @Override
168 public String getDistributionServiceRequestParameterString(List<TaxonDescription> taxonDescriptions,
169 boolean subAreaPreference,
170 boolean statusOrderPreference,
171 Set<MarkerType> hideMarkedAreas,
172 Map<UUID, Color> presenceAbsenceTermColors,
173 List<Language> langs,
174 boolean includeUnpublished) {
175
176 Set<Feature> features = new HashSet<>();
177 features.add(Feature.DISTRIBUTION()); //for now only this one
178 Set<Distribution> distributions = getDistributionsOf(taxonDescriptions, features, includeUnpublished);
179
180 String uriParams = getDistributionServiceRequestParameterString(distributions,
181 subAreaPreference,
182 statusOrderPreference,
183 hideMarkedAreas,
184 presenceAbsenceTermColors,
185 langs);
186
187 return uriParams;
188 }
189
190 private Set<Distribution> getDistributionsOf(List<TaxonDescription> taxonDescriptions, Set<Feature> features, boolean includeUnpublished) {
191 Set<Distribution> result = new HashSet<>();
192
193 for (TaxonDescription taxonDescription : taxonDescriptions) {
194 List<Distribution> distributions;
195 if (taxonDescription.getId() > 0){
196 distributions = dao.getDescriptionElements(taxonDescription,
197 null, features, Distribution.class, includeUnpublished, null, null, null);
198 }else{
199 distributions = new ArrayList<>();
200 for (DescriptionElementBase deb : taxonDescription.getElements()){
201 if (deb.isInstanceOf(Distribution.class)){
202 if (features == null || features.isEmpty()
203 || features.contains(deb.getFeature())) {
204 distributions.add(CdmBase.deproxy(deb, Distribution.class));
205 }
206 }
207 }
208 }
209 result.addAll(distributions);
210 }
211 return result;
212 }
213
214 @Override
215 //TODO needed?
216 public String getDistributionServiceRequestParameterString(
217 Set<Distribution> distributions,
218 boolean preferSubareas,
219 boolean statusOrderPreference,
220 Set<MarkerType> fallbackAreaMarkerTypes,
221 Map<UUID, Color> presenceAbsenceTermColors,
222 List<Language> langs) {
223
224 TermTree<NamedArea> areaTree = null;
225 TermTree<PresenceAbsenceTerm> statusTree = null;
226
227 DistributionInfoConfiguration diConfig = new DistributionInfoConfiguration();
228 diConfig.setFallbackAreaMarkerTypes(fallbackAreaMarkerTypes);
229 diConfig.setStatusOrderPreference(statusOrderPreference);
230 diConfig.setPreferSubAreas(preferSubareas);
231 diConfig.setInfoParts(EnumSet.of(InfoPart.mapUriParams));
232 DistributionInfoDto distInfo = new DistributionInfoBuilder(langs, commonService)
233 .build(null, distributions, areaTree, statusTree,
234 presenceAbsenceTermColors, areaMapping);
235 return distInfo.getMapUriParams();
236
237 //
238 // TermTree<NamedArea> areaTree = null;
239 // TermTree<PresenceAbsenceTerm> statusTree = null;
240 // boolean keepFallbackOnlyIfNoSubareaDataExists = true;
241 // Collection<DistributionDto> filteredDistributions = DistributionServiceUtilities.filterDistributions(
242 // distributions, areaTree, statusTree,
243 // hideMarkedAreas, false, statusOrderPreference,
244 // subAreaPreference, keepFallbackOnlyIfNoSubareaDataExists);
245 //
246 // String uriParams = DistributionServiceUtilities.getDistributionServiceRequestParameterString(
247 // filteredDistributions,
248 // areaMapping,
249 // presenceAbsenceTermColors,
250 // null, langs);
251 // return uriParams;
252 }
253
254 @Override
255 @Transactional(readOnly=false)
256 public Map<NamedArea, String> mapShapeFileToNamedAreas(Reader csvReader,
257 List<String> idSearchFields, String wmsLayerName, UUID areaVocabularyUuid,
258 Set<UUID> namedAreaUuids) throws IOException {
259
260 Set<NamedArea> areas = new HashSet<>();
261
262 if(areaVocabularyUuid != null){
263 @SuppressWarnings("unchecked")
264 TermVocabulary<NamedArea> areaVocabulary = vocabDao.load(areaVocabularyUuid);
265 if(areaVocabulary == null){
266 throw new EntityNotFoundException("No Vocabulary found for uuid " + areaVocabularyUuid);
267 }
268 areas.addAll(areaVocabulary.getTerms());
269 }
270 if(namedAreaUuids != null && !namedAreaUuids.isEmpty()){
271 for(DefinedTermBase<?> dtb : termDao.list(namedAreaUuids, null, null, null, null)){
272 areas.add((NamedArea)CdmBase.deproxy(dtb));
273 }
274 }
275
276 ShpAttributesToNamedAreaMapper mapper = new ShpAttributesToNamedAreaMapper(areas, areaMapping);
277 Map<NamedArea, String> resultMap = mapper.readCsv(csvReader, idSearchFields, wmsLayerName);
278 termDao.saveOrUpdateAll((Collection)areas);
279 return resultMap;
280 }
281 }