ref #7589 Improve description search when adding specimens to the matrix
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / DescriptiveDataSetService.java
1 package eu.etaxonomy.cdm.api.service;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.UUID;
13
14 import org.apache.log4j.Logger;
15 import org.springframework.beans.factory.annotation.Autowired;
16 import org.springframework.stereotype.Service;
17 import org.springframework.transaction.annotation.Transactional;
18
19 import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
20 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
21 import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
22 import eu.etaxonomy.cdm.common.monitor.RemotingProgressMonitorThread;
23 import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
24 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
25 import eu.etaxonomy.cdm.model.common.Language;
26 import eu.etaxonomy.cdm.model.description.CategoricalData;
27 import eu.etaxonomy.cdm.model.description.DescriptionBase;
28 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
29 import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
30 import eu.etaxonomy.cdm.model.description.DescriptiveSystemRole;
31 import eu.etaxonomy.cdm.model.description.Feature;
32 import eu.etaxonomy.cdm.model.description.QuantitativeData;
33 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
34 import eu.etaxonomy.cdm.model.description.TextData;
35 import eu.etaxonomy.cdm.model.location.NamedArea;
36 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
37 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
38 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
39 import eu.etaxonomy.cdm.model.taxon.Taxon;
40 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
41 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
42 import eu.etaxonomy.cdm.persistence.dao.description.IDescriptiveDataSetDao;
43 import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
44 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
45 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
46
47 @Service
48 @Transactional(readOnly = false)
49 public class DescriptiveDataSetService
50 extends IdentifiableServiceBase<DescriptiveDataSet, IDescriptiveDataSetDao>
51 implements IDescriptiveDataSetService {
52
53 private static Logger logger = Logger.getLogger(DescriptiveDataSetService.class);
54
55 @Autowired
56 private IOccurrenceService occurrenceService;
57
58 @Autowired
59 private IDescriptionService descriptionService;
60
61 @Autowired
62 private ITaxonNodeService taxonNodeService;
63
64 @Autowired
65 private IProgressMonitorService progressMonitorService;
66
67 @Override
68 @Autowired
69 protected void setDao(IDescriptiveDataSetDao dao) {
70 this.dao = dao;
71 }
72
73 @Override
74 public Map<DescriptionBase, Set<DescriptionElementBase>> getDescriptionElements(DescriptiveDataSet descriptiveDataSet, Set<Feature> features, Integer pageSize, Integer pageNumber,
75 List<String> propertyPaths) {
76 return dao.getDescriptionElements(descriptiveDataSet, features, pageSize, pageNumber, propertyPaths);
77 }
78
79 @Override
80 public <T extends DescriptionElementBase> Map<UuidAndTitleCache, Map<UUID, Set<T>>> getTaxonFeatureDescriptionElementMap(
81 Class<T> clazz, UUID descriptiveDataSetUuid, DescriptiveSystemRole role) {
82 return dao.getTaxonFeatureDescriptionElementMap(clazz, descriptiveDataSetUuid, role);
83 }
84
85 @Override
86 public List<UuidAndTitleCache<DescriptiveDataSet>> getDescriptiveDataSetUuidAndTitleCache(Integer limitOfInitialElements, String pattern) {
87 return dao.getDescriptiveDataSetUuidAndTitleCache( limitOfInitialElements, pattern);
88 }
89
90
91 @Override
92 @Transactional
93 public UUID monitGetRowWrapper(DescriptiveDataSet descriptiveDataSet) {
94 RemotingProgressMonitorThread monitorThread = new RemotingProgressMonitorThread() {
95 @Override
96 public Serializable doRun(IRemotingProgressMonitor monitor) {
97 return getRowWrapper(descriptiveDataSet, monitor);
98 }
99 };
100 UUID uuid = progressMonitorService.registerNewRemotingMonitor(monitorThread);
101 monitorThread.setPriority(3);
102 monitorThread.start();
103 return uuid;
104 }
105
106 @Override
107 public ArrayList<RowWrapperDTO> getRowWrapper(DescriptiveDataSet descriptiveDataSet, IProgressMonitor monitor) {
108 monitor.beginTask("Load row wrapper", descriptiveDataSet.getDescriptions().size());
109 ArrayList<RowWrapperDTO> wrappers = new ArrayList<>();
110 Set<DescriptionBase> descriptions = descriptiveDataSet.getDescriptions();
111 for (DescriptionBase description : descriptions) {
112 if(monitor.isCanceled()){
113 return new ArrayList<>();
114 }
115 wrappers.add(createRowWrapper(null, description, null, descriptiveDataSet));
116 monitor.worked(1);
117 }
118 return wrappers;
119 }
120
121 @Override
122 public Collection<SpecimenNodeWrapper> loadSpecimens(DescriptiveDataSet descriptiveDataSet){
123 //set filter parameters
124 TaxonNodeFilter filter = TaxonNodeFilter.NewRankInstance(descriptiveDataSet.getMinRank(), descriptiveDataSet.getMaxRank());
125 descriptiveDataSet.getGeoFilter().forEach(area -> filter.orArea(area.getUuid()));
126 descriptiveDataSet.getTaxonSubtreeFilter().forEach(node -> filter.orSubtree(node));
127 filter.setIncludeUnpublished(true);
128
129 List<UUID> filteredNodes = taxonNodeService.uuidList(filter);
130 return occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
131 }
132
133 @Override
134 public RowWrapperDTO createRowWrapper(DescriptionBase description, DescriptiveDataSet descriptiveDataSet){
135 return createRowWrapper(null, description, null, descriptiveDataSet);
136 }
137
138 @Override
139 public RowWrapperDTO createRowWrapper(SpecimenOrObservationBase specimen, DescriptiveDataSet descriptiveDataSet){
140 return createRowWrapper(specimen, null, null, descriptiveDataSet);
141 }
142
143 private RowWrapperDTO createRowWrapper(SpecimenOrObservationBase specimen, DescriptionBase description, TaxonNode taxonNode, DescriptiveDataSet descriptiveDataSet){
144 if(description!=null){
145 specimen = description.getDescribedSpecimenOrObservation();
146 }
147 FieldUnit fieldUnit = null;
148 String identifier = null;
149 NamedArea country = null;
150 //supplemental information
151 if(specimen!=null){
152 if(taxonNode==null){
153 Collection<TaxonBase<?>> associatedTaxa = occurrenceService.listAssociatedTaxa(specimen, null, null, null,
154 Arrays.asList(new String[]{
155 "taxonNodes",
156 "taxonNodes.classification",
157 }));
158 if(associatedTaxa!=null && !associatedTaxa.isEmpty()){
159 //FIXME: what about multiple associated taxa
160 Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
161 if(taxonSubtreeFilter!=null && !taxonSubtreeFilter.isEmpty()){
162 Taxon taxon = HibernateProxyHelper.deproxy(associatedTaxa.iterator().next(), Taxon.class);
163 taxonNode = taxon.getTaxonNode(taxonSubtreeFilter.iterator().next().getClassification());
164 }
165 }
166 }
167 Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
168 Arrays.asList(new String[]{
169 "gatheringEvent",
170 "gatheringEvent.country"
171 }));
172 if(fieldUnits.size()!=1){
173 logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
174 }
175 else{
176 fieldUnit = fieldUnits.iterator().next();
177 }
178 if(specimen instanceof DerivedUnit){
179 identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
180 }
181 if(fieldUnit!=null && fieldUnit.getGatheringEvent()!=null){
182 country = fieldUnit.getGatheringEvent().getCountry();
183 }
184 }
185 return new RowWrapperDTO(description, specimen, taxonNode, fieldUnit, identifier, country);
186 }
187
188 @Override
189 @Transactional(readOnly = false)
190 public void updateTitleCache(Class<? extends DescriptiveDataSet> clazz, Integer stepSize,
191 IIdentifiableEntityCacheStrategy<DescriptiveDataSet> cacheStrategy, IProgressMonitor monitor) {
192 if (clazz == null) {
193 clazz = DescriptiveDataSet.class;
194 }
195 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
196 }
197
198 @Override
199 public SpecimenDescription findDescriptionForDescriptiveDataSet(UUID descriptiveDataSetUuid, UUID specimenUuid){
200 DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
201 SpecimenOrObservationBase specimen = occurrenceService.load(specimenUuid);
202
203 Set<Feature> datasetFeatures = dataSet.getDescriptiveSystem().getDistinctFeatures();
204 List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
205
206 for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
207 specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescription.getUuid());
208
209 //check if description is already added to data set
210 if(dataSet.getDescriptions().contains(specimenDescription)){
211 return specimenDescription;
212 }
213
214 //gather specimen description features and check for match with dataset features
215 Set<Feature> specimenDescriptionFeatures = new HashSet<>();
216 for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
217 Feature feature = specimenDescriptionElement.getFeature();
218 specimenDescriptionFeatures.add(feature);
219 if(datasetFeatures.contains(feature)){
220 matchingDescriptionElements.add(specimenDescriptionElement);
221 }
222 }
223 }
224 //Create new specimen description if description has not already been added to the dataset
225 SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
226 newDesription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
227
228 //check for equals description element (same feature and same values)
229 Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
230 for(DescriptionElementBase element:matchingDescriptionElements){
231 List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
232 if(list==null){
233 list = new ArrayList<>();
234 }
235 list.add(element);
236 featureToElementMap.put(element.getFeature(), list);
237 }
238 Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
239 for(Feature feature:featureToElementMap.keySet()){
240 List<DescriptionElementBase> elements = featureToElementMap.get(feature);
241 //no duplicate description elements found for this feature
242 if(elements.size()==1){
243 descriptionElementsToClone.add(elements.get(0));
244 }
245 //duplicates found -> check if all are equal
246 else{
247 DescriptionElementBase match = null;
248 for (DescriptionElementBase descriptionElementBase : elements) {
249 if(match==null){
250 match = descriptionElementBase;
251 }
252 else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
253 match = null;
254 //TODO: propagate message
255 // MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
256 // String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
257 break;
258 }
259 }
260 if(match!=null){
261 descriptionElementsToClone.add(match);
262 }
263 }
264 }
265 //clone matching descriptionElements
266 for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
267 DescriptionElementBase clone;
268 try {
269 clone = descriptionElementBase.clone(newDesription);
270 clone.getSources().forEach(source -> {
271 if(descriptionElementBase instanceof CategoricalData){
272 TextData label = new DefaultCategoricalDescriptionBuilder().build((CategoricalData) descriptionElementBase, null);
273 source.setOriginalNameString(label.getText(Language.DEFAULT()));
274 }
275 else if(descriptionElementBase instanceof QuantitativeData){
276 TextData label = new DefaultQuantitativeDescriptionBuilder().build((QuantitativeData) descriptionElementBase, null);
277 source.setOriginalNameString(label.getText(Language.DEFAULT()));
278 }
279 });
280 } catch (CloneNotSupportedException e) {
281 // MessagingUtils.error(CharacterMatrix.class, e);
282 }
283 }
284
285 //add all remaining description elements to the new description
286 for(Feature wsFeature:datasetFeatures){
287 boolean featureFound = false;
288 for(DescriptionElementBase element:newDesription.getElements()){
289 if(element.getFeature().equals(wsFeature)){
290 featureFound = true;
291 break;
292 }
293 }
294 if(!featureFound){
295 if(wsFeature.isSupportsCategoricalData()){
296 newDesription.addElement(CategoricalData.NewInstance(wsFeature));
297 }
298 else if(wsFeature.isSupportsQuantitativeData()){
299 newDesription.addElement(QuantitativeData.NewInstance(wsFeature));
300 }
301 }
302 }
303 return newDesription;
304
305 }
306
307 private class DescriptionElementCompareWrapper {
308
309 private DescriptionElementBase element;
310 private Set<UUID> stateUuids = new HashSet<>();
311 private Float min = null;
312 private Float max = null;
313
314 public DescriptionElementCompareWrapper(DescriptionElementBase element) {
315 this.element = element;
316 if(element.isInstanceOf(CategoricalData.class)){
317 CategoricalData elementData = (CategoricalData)element;
318 elementData.getStatesOnly().forEach(state->stateUuids.add(state.getUuid()));
319 }
320 else if(element.isInstanceOf(QuantitativeData.class)){
321 QuantitativeData elementData = (QuantitativeData)element;
322 min = elementData.getMin();
323 max = elementData.getMax();
324 }
325 }
326
327 public DescriptionElementBase unwrap() {
328 return element;
329 }
330
331 @Override
332 public int hashCode() {
333 final int prime = 31;
334 int result = 1;
335 result = prime * result + ((max == null) ? 0 : max.hashCode());
336 result = prime * result + ((min == null) ? 0 : min.hashCode());
337 result = prime * result + ((stateUuids == null) ? 0 : stateUuids.hashCode());
338 return result;
339 }
340
341 @Override
342 public boolean equals(Object obj) {
343 if (this == obj) {
344 return true;
345 }
346 if (obj == null) {
347 return false;
348 }
349 if (getClass() != obj.getClass()) {
350 return false;
351 }
352 DescriptionElementCompareWrapper other = (DescriptionElementCompareWrapper) obj;
353 if (max == null) {
354 if (other.max != null) {
355 return false;
356 }
357 } else if (!max.equals(other.max)) {
358 return false;
359 }
360 if (min == null) {
361 if (other.min != null) {
362 return false;
363 }
364 } else if (!min.equals(other.min)) {
365 return false;
366 }
367 if (stateUuids == null) {
368 if (other.stateUuids != null) {
369 return false;
370 }
371 } else if (!stateUuids.equals(other.stateUuids)) {
372 return false;
373 }
374 return true;
375 }
376 }
377
378 }