2 * Copyright (C) 2023 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
.api
.service
.geo
;
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
;
23 import java
.util
.UUID
;
24 import java
.util
.stream
.Collectors
;
26 import javax
.persistence
.EntityNotFoundException
;
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
;
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
.DescriptionElementBase
;
49 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
50 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
51 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
52 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
53 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
54 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
55 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTermBase
;
56 import eu
.etaxonomy
.cdm
.model
.term
.TermNode
;
57 import eu
.etaxonomy
.cdm
.model
.term
.TermTree
;
58 import eu
.etaxonomy
.cdm
.model
.term
.TermVocabulary
;
59 import eu
.etaxonomy
.cdm
.persistence
.dao
.description
.IDescriptionDao
;
60 import eu
.etaxonomy
.cdm
.persistence
.dao
.term
.IDefinedTermDao
;
61 import eu
.etaxonomy
.cdm
.persistence
.dao
.term
.ITermTreeDao
;
62 import eu
.etaxonomy
.cdm
.persistence
.dao
.term
.ITermVocabularyDao
;
69 @Transactional(readOnly
= true)
70 public class DistributionServiceImpl
implements IDistributionService
{
72 @SuppressWarnings("unused")
73 private static final Logger logger
= LogManager
.getLogger();
76 private IDescriptionDao dao
;
79 private IDefinedTermDao termDao
;
82 private ITermTreeDao termTreeDao
;
85 private ITermVocabularyDao vocabDao
;
88 private IGeoServiceAreaMapping areaMapping
;
91 public DistributionInfoDto
composeDistributionInfoFor(DistributionInfoConfiguration config
, UUID taxonUUID
,
92 boolean neverUseFallbackAreaAsParent
,
93 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
94 List
<Language
> languages
, List
<String
> propertyPaths
){
96 Set
<UUID
> featureUuids
= config
.getFeatures();
97 Set
<Feature
> features
= null;
98 if (!CdmUtils
.isNullSafeEmpty(featureUuids
)) {
99 features
= termDao
.list(featureUuids
, null, null, null, null)
100 .stream().filter(t
->t
.isInstanceOf(Feature
.class)).map(t
->(Feature
)t
).collect(Collectors
.toSet());
103 if (propertyPaths
== null){
104 propertyPaths
= Arrays
.asList(new String
[]{});
106 // Adding default initStrategies to improve the performance of this method
107 // adding 'status' and 'area' has a good positive effect:
108 // filterDistributions() only takes 21% of the total method time (before it was 46%)
109 // at the same time the cost of the getDescriptionElementForTaxon is not increased at all!
111 // adding 'markers.markerType' is not improving the performance since it only
112 // moved the load from the filter method to the getDescriptionElementForTaxon()
114 // overall improvement by this means is by 42% (from 77,711 ms to 44,868 ms)
115 ArrayList
<String
> initStrategy
= new ArrayList
<>(propertyPaths
);
116 if(!initStrategy
.contains("status")) {
117 initStrategy
.add("status");
119 if(!initStrategy
.contains("area")) {
120 initStrategy
.add("area");
122 if(!initStrategy
.contains("markers.markerType")) {
123 initStrategy
.add("markers.markerType");
126 List
<Distribution
> distributions
= dao
.getDescriptionElementForTaxon(
127 taxonUUID
, features
, Distribution
.class, config
.isIncludeUnpublished(), null, null, initStrategy
);
129 return composeDistributionInfoFor(config
, distributions
, neverUseFallbackAreaAsParent
, presenceAbsenceTermColors
, languages
);
133 public DistributionInfoDto
composeDistributionInfoFor(DistributionInfoConfiguration config
, List
<Distribution
> distributions
,
134 boolean neverUseFallbackAreaAsParent
, Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
135 List
<Language
> languages
){
137 EnumSet
<DistributionInfoDto
.InfoPart
> parts
= config
.getInfoParts();
138 boolean subAreaPreference
= config
.isPreferSubareas();
139 boolean statusOrderPreference
= config
.isStatusOrderPreference();
140 Set
<MarkerType
> fallbackAreaMarkerTypes
= config
.getFallbackAreaMarkerTypeList();
141 Set
<MarkerType
> alternativeRootAreaMarkerTypes
= config
.getAlternativeRootAreaMarkerTypes();
142 Set
<NamedAreaLevel
> omitLevels
= config
.getOmitLevels();
143 CondensedDistributionConfiguration condensedDistConfig
= config
.getCondensedDistributionConfiguration();
144 DistributionOrder distributionOrder
= config
.getDistributionOrder();
146 final boolean PREFER_AGGREGATED
= true;
147 final boolean PREFER_SUBAREA
= true;
149 DistributionInfoDto dto
= new DistributionInfoDto();
151 if(omitLevels
== null) {
152 @SuppressWarnings("unchecked") //2 lines to allow unchecked annotation
153 Set
<NamedAreaLevel
> emptySet
= Collections
.EMPTY_SET
;
154 omitLevels
= emptySet
;
158 TermTree
<NamedArea
> areaTree
= getPersistentAreaTree(distributions
, config
);
159 if (areaTree
== null) {
160 //TODO better use areaTree created within filterDistributions(...) but how to get it easily?
161 areaTree
= DistributionServiceUtilities
.getAreaTree(distributions
, fallbackAreaMarkerTypes
);
164 //TODO unify to use only the node map
165 SetMap
<NamedArea
, NamedArea
> parentAreaMap
= areaTree
.getParentMap();
166 SetMap
<NamedArea
, TermNode
<NamedArea
>> parentAreaNodeMap
= areaTree
.getParentNodeMap();
167 SetMap
<NamedArea
, TermNode
<NamedArea
>> area2TermNodesMap
= areaTree
.getTermNodesMap();
170 TermTree
<PresenceAbsenceTerm
> statusTree
= getPersistentStatusTree(config
);
172 // for all later applications apply the rules statusOrderPreference, hideHiddenArea
173 // and ignoreUndefinedStatus to all distributions, but KEEP fallback area distributions
174 boolean keepFallBackOnlyIfNoSubareaDataExists
= false;
175 Set
<Distribution
> filteredDistributions
= DistributionServiceUtilities
.filterDistributions(distributions
,
176 areaTree
, statusTree
, fallbackAreaMarkerTypes
, !PREFER_AGGREGATED
, statusOrderPreference
, !PREFER_SUBAREA
,
177 keepFallBackOnlyIfNoSubareaDataExists
);
179 if(parts
.contains(InfoPart
.elements
)) {
180 dto
.setElements(filteredDistributions
);
183 if(parts
.contains(InfoPart
.tree
)) {
184 IDistributionTree tree
;
185 if (config
.isUseTreeDto()) {
187 //transform distributions to distribution DTOs
188 Set
<DistributionDto
> filteredDtoDistributions
= new HashSet
<>();
189 for (Distribution distribution
: filteredDistributions
) {
190 DistributionDto distDto
= new DistributionDto(distribution
, parentAreaMap
);
191 PortalDtoLoader
.loadBaseData(distribution
, distDto
);
192 distDto
.setTimeperiod(distribution
.getTimeperiod() == null ?
null : distribution
.getTimeperiod().toString());
193 filteredDtoDistributions
.add(distDto
);
196 boolean useSecondMethod
= false;
197 tree
= DistributionServiceUtilities
.buildOrderedTreeDto(omitLevels
,
198 filteredDtoDistributions
, parentAreaMap
, areaTree
, fallbackAreaMarkerTypes
,
199 alternativeRootAreaMarkerTypes
, neverUseFallbackAreaAsParent
,
200 distributionOrder
, termDao
, useSecondMethod
);
202 //version with model entities as used in direct webservice (not taxon page DTO)
203 tree
= DistributionServiceUtilities
.buildOrderedTree(omitLevels
,
204 filteredDistributions
, parentAreaMap
, fallbackAreaMarkerTypes
,
205 alternativeRootAreaMarkerTypes
, neverUseFallbackAreaAsParent
,
206 distributionOrder
, termDao
);
211 if(parts
.contains(InfoPart
.condensedDistribution
)) {
212 CondensedDistribution condensedDistribution
= DistributionServiceUtilities
.getCondensedDistribution(
213 filteredDistributions
, area2TermNodesMap
, condensedDistConfig
, languages
);
214 dto
.setCondensedDistribution(condensedDistribution
);
217 if (parts
.contains(InfoPart
.mapUriParams
)) {
218 boolean IGNORE_STATUS_ORDER_PREF
= false;
219 Set
<MarkerType
> fallbackAreaMarkerType
= null;
220 // only apply the subAreaPreference rule for the maps
221 keepFallBackOnlyIfNoSubareaDataExists
= true;
222 Set
<Distribution
> filteredMapDistributions
= DistributionServiceUtilities
.filterDistributions(
223 filteredDistributions
, areaTree
, statusTree
, fallbackAreaMarkerType
, !PREFER_AGGREGATED
,
224 IGNORE_STATUS_ORDER_PREF
, subAreaPreference
, keepFallBackOnlyIfNoSubareaDataExists
);
226 String mapUri
= DistributionServiceUtilities
.getDistributionServiceRequestParameterString(
227 filteredMapDistributions
,
229 presenceAbsenceTermColors
,
231 dto
.setMapUriParams(mapUri
);
237 private TermTree
<NamedArea
> getPersistentAreaTree(List
<Distribution
> distributions
, DistributionInfoConfiguration config
) {
238 UUID areaTreeUuid
= config
.getAreaTree();
239 if (areaTreeUuid
== null) {
243 String
[] propertyPath
= new String
[] {};
244 @SuppressWarnings("unchecked")
245 TermTree
<NamedArea
> areaTree
= termTreeDao
.load(areaTreeUuid
, Arrays
.asList(propertyPath
));
249 private TermTree
<PresenceAbsenceTerm
> getPersistentStatusTree(DistributionInfoConfiguration config
) {
250 UUID statusTreeUuid
= config
.getStatusTree();
251 if (statusTreeUuid
== null) {
255 String
[] propertyPath
= new String
[] {};
256 @SuppressWarnings("unchecked")
257 TermTree
<PresenceAbsenceTerm
> statusTree
= termTreeDao
.load(statusTreeUuid
, Arrays
.asList(propertyPath
));
262 public CondensedDistribution
getCondensedDistribution(Set
<Distribution
> distributions
,
263 TermTree
<NamedArea
> areaTree
,
264 TermTree
<PresenceAbsenceTerm
> statusTree
,
265 boolean statusOrderPreference
,
266 Set
<MarkerType
> fallbackAreaMarkerTypes
,
267 CondensedDistributionConfiguration config
,
268 List
<Language
> langs
) {
270 //TODO exclude "undefined" status as long as status tree is not yet
271 areaTree
= areaTree
== null ? DistributionServiceUtilities
.getAreaTree(distributions
, fallbackAreaMarkerTypes
) : areaTree
;
272 SetMap
<NamedArea
,TermNode
<NamedArea
>> parentNodeMap
= areaTree
.getParentNodeMap();
273 Collection
<Distribution
> filteredDistributions
= DistributionServiceUtilities
.filterDistributions(
274 distributions
, null, statusTree
, fallbackAreaMarkerTypes
, false, statusOrderPreference
, false, false);
275 CondensedDistribution condensedDistribution
= DistributionServiceUtilities
.getCondensedDistribution(
276 filteredDistributions
,
280 return condensedDistribution
;
284 public void setMapping(NamedArea area
, GeoServiceArea geoServiceArea
) {
285 areaMapping
.set(area
, geoServiceArea
);
289 public String
getDistributionServiceRequestParameterString(List
<TaxonDescription
> taxonDescriptions
,
290 boolean subAreaPreference
,
291 boolean statusOrderPreference
,
292 Set
<MarkerType
> hideMarkedAreas
,
293 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
294 List
<Language
> langs
,
295 boolean includeUnpublished
) {
297 Set
<Feature
> features
= new HashSet
<>();
298 features
.add(Feature
.DISTRIBUTION()); //for now only this one
299 Set
<Distribution
> distributions
= getDistributionsOf(taxonDescriptions
, features
, includeUnpublished
);
301 String uriParams
= getDistributionServiceRequestParameterString(distributions
,
303 statusOrderPreference
,
305 presenceAbsenceTermColors
,
311 private Set
<Distribution
> getDistributionsOf(List
<TaxonDescription
> taxonDescriptions
, Set
<Feature
> features
, boolean includeUnpublished
) {
312 Set
<Distribution
> result
= new HashSet
<>();
314 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
315 List
<Distribution
> distributions
;
316 if (taxonDescription
.getId() > 0){
317 distributions
= dao
.getDescriptionElements(taxonDescription
,
318 null, features
, Distribution
.class, includeUnpublished
, null, null, null);
320 distributions
= new ArrayList
<>();
321 for (DescriptionElementBase deb
: taxonDescription
.getElements()){
322 if (deb
.isInstanceOf(Distribution
.class)){
323 if (features
== null || features
.isEmpty()
324 || features
.contains(deb
.getFeature())) {
325 distributions
.add(CdmBase
.deproxy(deb
, Distribution
.class));
330 result
.addAll(distributions
);
336 public String
getDistributionServiceRequestParameterString(
337 Set
<Distribution
> distributions
,
338 boolean subAreaPreference
,
339 boolean statusOrderPreference
,
340 Set
<MarkerType
> hideMarkedAreas
,
341 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
342 List
<Language
> langs
) {
344 TermTree
<NamedArea
> areaTree
= null;
345 TermTree
<PresenceAbsenceTerm
> statusTree
= null;
346 boolean keepFallbackOnlyIfNoSubareaDataExists
= true;
347 Collection
<Distribution
> filteredDistributions
= DistributionServiceUtilities
.filterDistributions(
348 distributions
, areaTree
, statusTree
,
349 hideMarkedAreas
, false, statusOrderPreference
,
350 subAreaPreference
, keepFallbackOnlyIfNoSubareaDataExists
);
352 String uriParams
= DistributionServiceUtilities
.getDistributionServiceRequestParameterString(
353 filteredDistributions
,
355 presenceAbsenceTermColors
,
361 @Transactional(readOnly
=false)
362 public Map
<NamedArea
, String
> mapShapeFileToNamedAreas(Reader csvReader
,
363 List
<String
> idSearchFields
, String wmsLayerName
, UUID areaVocabularyUuid
,
364 Set
<UUID
> namedAreaUuids
) throws IOException
{
366 Set
<NamedArea
> areas
= new HashSet
<>();
368 if(areaVocabularyUuid
!= null){
369 @SuppressWarnings("unchecked")
370 TermVocabulary
<NamedArea
> areaVocabulary
= vocabDao
.load(areaVocabularyUuid
);
371 if(areaVocabulary
== null){
372 throw new EntityNotFoundException("No Vocabulary found for uuid " + areaVocabularyUuid
);
374 areas
.addAll(areaVocabulary
.getTerms());
376 if(namedAreaUuids
!= null && !namedAreaUuids
.isEmpty()){
377 for(DefinedTermBase
<?
> dtb
: termDao
.list(namedAreaUuids
, null, null, null, null)){
378 areas
.add((NamedArea
)CdmBase
.deproxy(dtb
));
382 ShpAttributesToNamedAreaMapper mapper
= new ShpAttributesToNamedAreaMapper(areas
, areaMapping
);
383 Map
<NamedArea
, String
> resultMap
= mapper
.readCsv(csvReader
, idSearchFields
, wmsLayerName
);
384 termDao
.saveOrUpdateAll((Collection
)areas
);