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
.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
;
67 @Transactional(readOnly
= true)
68 public class DistributionServiceImpl
implements IDistributionService
{
70 @SuppressWarnings("unused")
71 private static final Logger logger
= LogManager
.getLogger();
74 private IDescriptionDao dao
;
77 private IDefinedTermDao termDao
;
80 private ITermTreeDao termTreeDao
;
83 private ITermVocabularyDao vocabDao
;
86 private IGeoServiceAreaMapping areaMapping
;
89 public DistributionInfoDto
composeDistributionInfoFor(DistributionInfoConfiguration config
, UUID taxonUUID
,
90 boolean neverUseFallbackAreaAsParent
,
91 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
92 List
<Language
> languages
, List
<String
> propertyPaths
){
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());
101 if (propertyPaths
== null){
102 propertyPaths
= Arrays
.asList(new String
[]{});
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!
109 // adding 'markers.markerType' is not improving the performance since it only
110 // moved the load from the filter method to the getDescriptionElementForTaxon()
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");
117 if(!initStrategy
.contains("area")) {
118 initStrategy
.add("area");
120 if(!initStrategy
.contains("markers.markerType")) {
121 initStrategy
.add("markers.markerType");
124 List
<Distribution
> distributions
= dao
.getDescriptionElementForTaxon(
125 taxonUUID
, features
, Distribution
.class, config
.isIncludeUnpublished(), null, null, initStrategy
);
127 return composeDistributionInfoFor(config
, distributions
, neverUseFallbackAreaAsParent
, presenceAbsenceTermColors
, languages
);
131 public DistributionInfoDto
composeDistributionInfoFor(DistributionInfoConfiguration config
, List
<Distribution
> distributions
,
132 boolean neverUseFallbackAreaAsParent
, Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
,
133 List
<Language
> languages
){
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();
144 final boolean PREFER_AGGREGATED
= true;
145 final boolean PREFER_SUBAREA
= true;
147 DistributionInfoDto dto
= new DistributionInfoDto();
149 if(omitLevels
== null) {
150 @SuppressWarnings("unchecked") //2 lines to allow unchecked annotation
151 Set
<NamedAreaLevel
> emptySet
= Collections
.EMPTY_SET
;
152 omitLevels
= emptySet
;
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
);
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();
167 TermTree
<PresenceAbsenceTerm
> statusTree
= getPersistentStatusTree(config
);
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
);
176 if(parts
.contains(InfoPart
.elements
)) {
177 dto
.setElements(filteredDistributions
);
180 if(parts
.contains(InfoPart
.tree
)) {
181 IDistributionTree tree
;
182 if (config
.isUseTreeDto()) {
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
);
193 boolean useSecondMethod
= false;
194 tree
= DistributionServiceUtilities
.buildOrderedTreeDto(omitLevels
,
195 filteredDtoDistributions
, parentAreaMap
, areaTree
, fallbackAreaMarkerTypes
,
196 alternativeRootAreaMarkerTypes
, neverUseFallbackAreaAsParent
,
197 distributionOrder
, termDao
, useSecondMethod
);
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
);
208 if(parts
.contains(InfoPart
.condensedDistribution
)) {
209 CondensedDistribution condensedDistribution
= DistributionServiceUtilities
.getCondensedDistribution(
210 filteredDistributions
, area2TermNodesMap
, condensedDistConfig
, languages
);
211 dto
.setCondensedDistribution(condensedDistribution
);
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
);
223 dto
.setMapUriParams(DistributionServiceUtilities
.getDistributionServiceRequestParameterString(filteredMapDistributions
,
225 presenceAbsenceTermColors
,
232 private TermTree
<NamedArea
> getPersistentAreaTree(List
<Distribution
> distributions
, DistributionInfoConfiguration config
) {
233 UUID areaTreeUuid
= config
.getAreaTree();
234 if (areaTreeUuid
== null) {
238 String
[] propertyPath
= new String
[] {};
239 @SuppressWarnings("unchecked")
240 TermTree
<NamedArea
> areaTree
= termTreeDao
.load(areaTreeUuid
, Arrays
.asList(propertyPath
));
244 private TermTree
<PresenceAbsenceTerm
> getPersistentStatusTree(DistributionInfoConfiguration config
) {
245 UUID statusTreeUuid
= config
.getStatusTree();
246 if (statusTreeUuid
== null) {
250 String
[] propertyPath
= new String
[] {};
251 @SuppressWarnings("unchecked")
252 TermTree
<PresenceAbsenceTerm
> statusTree
= termTreeDao
.load(statusTreeUuid
, Arrays
.asList(propertyPath
));
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
) {
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
,
275 return condensedDistribution
;
279 public void setMapping(NamedArea area
, GeoServiceArea geoServiceArea
) {
280 areaMapping
.set(area
, geoServiceArea
);
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
) {
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
);
299 String uriParams
= DistributionServiceUtilities
.getDistributionServiceRequestParameterString(
300 filteredDistributions
,
302 presenceAbsenceTermColors
,
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
{
313 Set
<NamedArea
> areas
= new HashSet
<>();
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
);
321 areas
.addAll(areaVocabulary
.getTerms());
323 if(namedAreaUuids
!= null && !namedAreaUuids
.isEmpty()){
324 for(DefinedTermBase
<?
> dtb
: termDao
.list(namedAreaUuids
, null, null, null, null)){
325 areas
.add((NamedArea
)CdmBase
.deproxy(dtb
));
329 ShpAttributesToNamedAreaMapper mapper
= new ShpAttributesToNamedAreaMapper(areas
, areaMapping
);
330 Map
<NamedArea
, String
> resultMap
= mapper
.readCsv(csvReader
, idSearchFields
, wmsLayerName
);
331 termDao
.saveOrUpdateAll((Collection
)areas
);