c6e5c1a0f956c6b520a02cc8eb93ffd05a33aa9c
[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.Collections;
18 import java.util.EnumSet;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.UUID;
24 import java.util.stream.Collectors;
25
26 import javax.persistence.EntityNotFoundException;
27
28 import org.apache.logging.log4j.LogManager;
29 import org.apache.logging.log4j.Logger;
30 import org.springframework.beans.factory.annotation.Autowired;
31 import org.springframework.stereotype.Service;
32 import org.springframework.transaction.annotation.Transactional;
33
34 import eu.etaxonomy.cdm.api.dto.portal.DistributionDto;
35 import eu.etaxonomy.cdm.api.dto.portal.DistributionInfoDto;
36 import eu.etaxonomy.cdm.api.dto.portal.DistributionInfoDto.InfoPart;
37 import eu.etaxonomy.cdm.api.dto.portal.IDistributionTree;
38 import eu.etaxonomy.cdm.api.dto.portal.config.DistributionInfoConfiguration;
39 import eu.etaxonomy.cdm.api.dto.portal.config.DistributionOrder;
40 import eu.etaxonomy.cdm.api.service.portal.PortalDtoLoader;
41 import eu.etaxonomy.cdm.common.CdmUtils;
42 import eu.etaxonomy.cdm.common.SetMap;
43 import eu.etaxonomy.cdm.format.description.distribution.CondensedDistribution;
44 import eu.etaxonomy.cdm.format.description.distribution.CondensedDistributionConfiguration;
45 import eu.etaxonomy.cdm.model.common.CdmBase;
46 import eu.etaxonomy.cdm.model.common.Language;
47 import eu.etaxonomy.cdm.model.common.MarkerType;
48 import eu.etaxonomy.cdm.model.description.Distribution;
49 import eu.etaxonomy.cdm.model.description.Feature;
50 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
51 import eu.etaxonomy.cdm.model.location.NamedArea;
52 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
53 import eu.etaxonomy.cdm.model.term.DefinedTermBase;
54 import eu.etaxonomy.cdm.model.term.TermNode;
55 import eu.etaxonomy.cdm.model.term.TermTree;
56 import eu.etaxonomy.cdm.model.term.TermVocabulary;
57 import eu.etaxonomy.cdm.persistence.dao.description.IDescriptionDao;
58 import eu.etaxonomy.cdm.persistence.dao.term.IDefinedTermDao;
59 import eu.etaxonomy.cdm.persistence.dao.term.ITermTreeDao;
60 import eu.etaxonomy.cdm.persistence.dao.term.ITermVocabularyDao;
61
62 /**
63 * @author a.mueller
64 * @date 08.02.2023
65 */
66 @Service
67 @Transactional(readOnly = true)
68 public class DistributionServiceImpl implements IDistributionService {
69
70 @SuppressWarnings("unused")
71 private static final Logger logger = LogManager.getLogger();
72
73 @Autowired
74 private IDescriptionDao dao;
75
76 @Autowired
77 private IDefinedTermDao termDao;
78
79 @Autowired
80 private ITermTreeDao termTreeDao;
81
82 @Autowired
83 private ITermVocabularyDao vocabDao;
84
85 @Autowired
86 private IGeoServiceAreaMapping areaMapping;
87
88 @Override
89 public DistributionInfoDto composeDistributionInfoFor(DistributionInfoConfiguration config, UUID taxonUUID,
90 boolean neverUseFallbackAreaAsParent,
91 Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors,
92 List<Language> languages, List<String> propertyPaths){
93
94 Set<UUID> featureUuids = config.getFeatures();
95 Set<Feature> features = null;
96 if (!CdmUtils.isNullSafeEmpty(featureUuids)) {
97 features = termDao.list(featureUuids, null, null, null, null)
98 .stream().filter(t->t.isInstanceOf(Feature.class)).map(t->(Feature)t).collect(Collectors.toSet());
99 }
100
101 if (propertyPaths == null){
102 propertyPaths = Arrays.asList(new String []{});
103 }
104 // Adding default initStrategies to improve the performance of this method
105 // adding 'status' and 'area' has a good positive effect:
106 // filterDistributions() only takes 21% of the total method time (before it was 46%)
107 // at the same time the cost of the getDescriptionElementForTaxon is not increased at all!
108 //
109 // adding 'markers.markerType' is not improving the performance since it only
110 // moved the load from the filter method to the getDescriptionElementForTaxon()
111 // method.
112 // overall improvement by this means is by 42% (from 77,711 ms to 44,868 ms)
113 ArrayList<String> initStrategy = new ArrayList<>(propertyPaths);
114 if(!initStrategy.contains("status")) {
115 initStrategy.add("status");
116 }
117 if(!initStrategy.contains("area")) {
118 initStrategy.add("area");
119 }
120 if(!initStrategy.contains("markers.markerType")) {
121 initStrategy.add("markers.markerType");
122 }
123
124 List<Distribution> distributions = dao.getDescriptionElementForTaxon(
125 taxonUUID, features, Distribution.class, config.isIncludeUnpublished(), null, null, initStrategy);
126
127 return composeDistributionInfoFor(config, distributions, neverUseFallbackAreaAsParent, presenceAbsenceTermColors, languages);
128 }
129
130 @Override
131 public DistributionInfoDto composeDistributionInfoFor(DistributionInfoConfiguration config, List<Distribution> distributions,
132 boolean neverUseFallbackAreaAsParent, Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors,
133 List<Language> languages){
134
135 EnumSet<DistributionInfoDto.InfoPart> parts = config.getInfoParts();
136 boolean subAreaPreference = config.isPreferSubareas();
137 boolean statusOrderPreference = config.isStatusOrderPreference();
138 Set<MarkerType> fallbackAreaMarkerTypes = config.getFallbackAreaMarkerTypeList();
139 Set<MarkerType> alternativeRootAreaMarkerTypes = config.getAlternativeRootAreaMarkerTypes();
140 Set<NamedAreaLevel> omitLevels = config.getOmitLevels();
141 CondensedDistributionConfiguration condensedDistConfig = config.getCondensedDistributionConfiguration();
142 DistributionOrder distributionOrder = config.getDistributionOrder();
143
144 final boolean PREFER_AGGREGATED = true;
145 final boolean PREFER_SUBAREA = true;
146
147 DistributionInfoDto dto = new DistributionInfoDto();
148
149 if(omitLevels == null) {
150 @SuppressWarnings("unchecked") //2 lines to allow unchecked annotation
151 Set<NamedAreaLevel> emptySet = Collections.EMPTY_SET;
152 omitLevels = emptySet;
153 }
154
155 //area tree
156 TermTree<NamedArea> areaTree = getPersistentAreaTree(distributions, config);
157 if (areaTree == null) {
158 //TODO better use areaTree created within filterDistributions(...) but how to get it easily?
159 areaTree = DistributionServiceUtilities.getAreaTree(distributions, fallbackAreaMarkerTypes);
160 }
161 //TODO unify to use only the node map
162 SetMap<NamedArea, NamedArea> parentAreaMap = areaTree.getParentMap();
163 SetMap<NamedArea, TermNode<NamedArea>> parentAreaNodeMap = areaTree.getParentNodeMap();
164 SetMap<NamedArea, TermNode<NamedArea>> area2TermNodesMap = areaTree.getTermNodesMap();
165
166 //status tree
167 TermTree<PresenceAbsenceTerm> statusTree = getPersistentStatusTree(config);
168
169 // for all later applications apply the rules statusOrderPreference, hideHiddenArea
170 // and ignoreUndefinedStatus to all distributions, but KEEP fallback area distributions
171 boolean keepFallBackOnlyIfNoSubareaDataExists = false;
172 Set<Distribution> filteredDistributions = DistributionServiceUtilities.filterDistributions(distributions,
173 areaTree, statusTree, fallbackAreaMarkerTypes, !PREFER_AGGREGATED, statusOrderPreference, !PREFER_SUBAREA,
174 keepFallBackOnlyIfNoSubareaDataExists);
175
176 if(parts.contains(InfoPart.elements)) {
177 dto.setElements(filteredDistributions);
178 }
179
180 if(parts.contains(InfoPart.tree)) {
181 IDistributionTree tree;
182 if (config.isUseTreeDto()) {
183
184 //transform distributions to distribution DTOs
185 Set<DistributionDto> filteredDtoDistributions = new HashSet<>();
186 for (Distribution distribution : filteredDistributions) {
187 DistributionDto distDto = new DistributionDto(distribution, parentAreaMap);
188 PortalDtoLoader.loadBaseData(distribution, distDto);
189 distDto.setTimeperiod(distribution.getTimeperiod() == null ? null : distribution.getTimeperiod().toString());
190 filteredDtoDistributions.add(distDto);
191 }
192
193 boolean useSecondMethod = false;
194 tree = DistributionServiceUtilities.buildOrderedTreeDto(omitLevels,
195 filteredDtoDistributions, parentAreaMap, areaTree, fallbackAreaMarkerTypes,
196 alternativeRootAreaMarkerTypes, neverUseFallbackAreaAsParent,
197 distributionOrder, termDao, useSecondMethod);
198 }else {
199 //version with model entities as used in direct webservice (not taxon page DTO)
200 tree = DistributionServiceUtilities.buildOrderedTree(omitLevels,
201 filteredDistributions, parentAreaMap, fallbackAreaMarkerTypes,
202 alternativeRootAreaMarkerTypes, neverUseFallbackAreaAsParent,
203 distributionOrder, termDao);
204 }
205 dto.setTree(tree);
206 }
207
208 if(parts.contains(InfoPart.condensedDistribution)) {
209 CondensedDistribution condensedDistribution = DistributionServiceUtilities.getCondensedDistribution(
210 filteredDistributions, area2TermNodesMap, condensedDistConfig, languages);
211 dto.setCondensedDistribution(condensedDistribution);
212 }
213
214 if (parts.contains(InfoPart.mapUriParams)) {
215 boolean IGNORE_STATUS_ORDER_PREF = false;
216 Set<MarkerType> fallbackAreaMarkerType = null;
217 // only apply the subAreaPreference rule for the maps
218 keepFallBackOnlyIfNoSubareaDataExists = true;
219 Set<Distribution> filteredMapDistributions = DistributionServiceUtilities.filterDistributions(
220 filteredDistributions, areaTree, statusTree, fallbackAreaMarkerType, !PREFER_AGGREGATED,
221 IGNORE_STATUS_ORDER_PREF, subAreaPreference, keepFallBackOnlyIfNoSubareaDataExists);
222
223 dto.setMapUriParams(DistributionServiceUtilities.getDistributionServiceRequestParameterString(filteredMapDistributions,
224 areaMapping,
225 presenceAbsenceTermColors,
226 null, languages));
227 }
228
229 return dto;
230 }
231
232 private TermTree<NamedArea> getPersistentAreaTree(List<Distribution> distributions, DistributionInfoConfiguration config) {
233 UUID areaTreeUuid = config.getAreaTree();
234 if (areaTreeUuid == null) {
235 return null;
236 }
237 //TODO property path
238 String[] propertyPath = new String[] {};
239 @SuppressWarnings("unchecked")
240 TermTree<NamedArea> areaTree = termTreeDao.load(areaTreeUuid, Arrays.asList(propertyPath));
241 return areaTree;
242 }
243
244 private TermTree<PresenceAbsenceTerm> getPersistentStatusTree(DistributionInfoConfiguration config) {
245 UUID statusTreeUuid = config.getStatusTree();
246 if (statusTreeUuid == null) {
247 return null;
248 }
249 //TODO property path
250 String[] propertyPath = new String[] {};
251 @SuppressWarnings("unchecked")
252 TermTree<PresenceAbsenceTerm> statusTree = termTreeDao.load(statusTreeUuid, Arrays.asList(propertyPath));
253 return statusTree;
254 }
255
256 @Override
257 public CondensedDistribution getCondensedDistribution(Set<Distribution> distributions,
258 TermTree<NamedArea> areaTree,
259 TermTree<PresenceAbsenceTerm> statusTree,
260 boolean statusOrderPreference,
261 Set<MarkerType> fallbackAreaMarkerTypes,
262 CondensedDistributionConfiguration config,
263 List<Language> langs) {
264
265 //TODO exclude "undefined" status as long as status tree is not yet
266 areaTree = areaTree == null ? DistributionServiceUtilities.getAreaTree(distributions, fallbackAreaMarkerTypes) : areaTree;
267 SetMap<NamedArea,TermNode<NamedArea>> parentNodeMap = areaTree.getParentNodeMap();
268 Collection<Distribution> filteredDistributions = DistributionServiceUtilities.filterDistributions(
269 distributions, null, statusTree, fallbackAreaMarkerTypes, false, statusOrderPreference, false, false);
270 CondensedDistribution condensedDistribution = DistributionServiceUtilities.getCondensedDistribution(
271 filteredDistributions,
272 parentNodeMap,
273 config,
274 langs);
275 return condensedDistribution;
276 }
277
278 @Override
279 public void setMapping(NamedArea area, GeoServiceArea geoServiceArea) {
280 areaMapping.set(area, geoServiceArea);
281 }
282
283 @Override
284 public String getDistributionServiceRequestParameterString(
285 Set<Distribution> distributions,
286 boolean subAreaPreference,
287 boolean statusOrderPreference,
288 Set<MarkerType> hideMarkedAreas,
289 Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors,
290 List<Language> langs) {
291
292 TermTree<NamedArea> areaTree = null;
293 TermTree<PresenceAbsenceTerm> statusTree = null;
294 boolean keepFallbackOnlyIfNoSubareaDataExists = true;
295 Collection<Distribution> filteredDistributions = DistributionServiceUtilities.filterDistributions(
296 distributions, areaTree, statusTree,
297 hideMarkedAreas, false, statusOrderPreference, subAreaPreference, keepFallbackOnlyIfNoSubareaDataExists);
298
299 String uriParams = DistributionServiceUtilities.getDistributionServiceRequestParameterString(
300 filteredDistributions,
301 areaMapping,
302 presenceAbsenceTermColors,
303 null, langs);
304 return uriParams;
305 }
306
307 @Override
308 @Transactional(readOnly=false)
309 public Map<NamedArea, String> mapShapeFileToNamedAreas(Reader csvReader,
310 List<String> idSearchFields, String wmsLayerName, UUID areaVocabularyUuid,
311 Set<UUID> namedAreaUuids) throws IOException {
312
313 Set<NamedArea> areas = new HashSet<>();
314
315 if(areaVocabularyUuid != null){
316 @SuppressWarnings("unchecked")
317 TermVocabulary<NamedArea> areaVocabulary = vocabDao.load(areaVocabularyUuid);
318 if(areaVocabulary == null){
319 throw new EntityNotFoundException("No Vocabulary found for uuid " + areaVocabularyUuid);
320 }
321 areas.addAll(areaVocabulary.getTerms());
322 }
323 if(namedAreaUuids != null && !namedAreaUuids.isEmpty()){
324 for(DefinedTermBase<?> dtb : termDao.list(namedAreaUuids, null, null, null, null)){
325 areas.add((NamedArea)CdmBase.deproxy(dtb));
326 }
327 }
328
329 ShpAttributesToNamedAreaMapper mapper = new ShpAttributesToNamedAreaMapper(areas, areaMapping);
330 Map<NamedArea, String> resultMap = mapper.readCsv(csvReader, idSearchFields, wmsLayerName);
331 termDao.saveOrUpdateAll((Collection)areas);
332 return resultMap;
333 }
334 }