View Javadoc
1   package net.sumaris.core.dao.data;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Core
6    * %%
7    * Copyright (C) 2018 SUMARiS Consortium
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.base.Preconditions;
26  import com.google.common.collect.Lists;
27  import com.google.common.collect.Multimap;
28  import net.sumaris.core.dao.referential.ReferentialDao;
29  import net.sumaris.core.dao.referential.taxon.TaxonNameDao;
30  import net.sumaris.core.model.administration.programStrategy.PmfmStrategy;
31  import net.sumaris.core.model.administration.user.Department;
32  import net.sumaris.core.model.data.Batch;
33  import net.sumaris.core.model.data.Operation;
34  import net.sumaris.core.model.referential.QualityFlag;
35  import net.sumaris.core.model.referential.taxon.ReferenceTaxon;
36  import net.sumaris.core.model.referential.taxon.TaxonGroup;
37  import net.sumaris.core.model.referential.taxon.TaxonName;
38  import net.sumaris.core.util.Beans;
39  import net.sumaris.core.vo.administration.user.DepartmentVO;
40  import net.sumaris.core.vo.data.BatchVO;
41  import net.sumaris.core.vo.data.OperationVO;
42  import net.sumaris.core.vo.referential.ReferentialVO;
43  import net.sumaris.core.vo.referential.TaxonNameVO;
44  import org.apache.commons.collections4.CollectionUtils;
45  import org.apache.commons.collections4.MapUtils;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  import org.springframework.beans.factory.annotation.Autowired;
49  import org.springframework.dao.DataIntegrityViolationException;
50  import org.springframework.stereotype.Repository;
51  
52  import javax.persistence.EntityManager;
53  import javax.persistence.criteria.CriteriaBuilder;
54  import javax.persistence.criteria.CriteriaQuery;
55  import javax.persistence.criteria.ParameterExpression;
56  import javax.persistence.criteria.Root;
57  import java.sql.Timestamp;
58  import java.util.Collection;
59  import java.util.List;
60  import java.util.Map;
61  import java.util.Objects;
62  import java.util.stream.Collectors;
63  import java.util.stream.Stream;
64  
65  @Repository("batchDao")
66  public class BatchDaoImpl extends BaseDataDaoImpl implements BatchDao {
67  
68      /** Logger. */
69      private static final Logger log =
70              LoggerFactory.getLogger(BatchDaoImpl.class);
71  
72      @Autowired
73      private ReferentialDao referentialDao;
74  
75      @Autowired
76      private TaxonNameDao taxonNameDao;
77  
78      @Override
79      public List<BatchVO> getAllByOperationId(int operationId) {
80          CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
81          CriteriaQuery<Batch> query = cb.createQuery(Batch.class);
82          Root<Batch> root = query.from(Batch.class);
83  
84          query.select(root);
85  
86          ParameterExpression<Integer> tripIdParam = cb.parameter(Integer.class);
87  
88          query.where(cb.equal(root.get(Batch.Fields.OPERATION).get(Batch.Fields.ID), tripIdParam));
89  
90          // Sort by rank order
91          query.orderBy(cb.asc(root.get(PmfmStrategy.Fields.RANK_ORDER)));
92  
93          return toBatchVOs(getEntityManager().createQuery(query)
94                  .setParameter(tripIdParam, operationId).getResultList(), false);
95      }
96  
97      @Override
98      public BatchVO get(int id) {
99          Batch entity = get(Batch.class, id);
100         return toBatchVO(entity, false);
101     }
102 
103     @Override
104     public List<BatchVO> saveByOperationId(int operationId, List<BatchVO> sources) {
105 
106         // Load parent entity
107         Operation parent = get(Operation.class, operationId);
108         Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
109 
110         // Remember existing entities
111 
112         final Multimap<Integer, Batch> sourcesByHashCode = Beans.splitByNotUniqueProperty(Beans.getList(parent.getBatches()), Batch.Fields.HASH);
113         final Multimap<String, Batch> sourcesByLabelMap = Beans.splitByNotUniqueProperty(Beans.getList(parent.getBatches()), Batch.Fields.LABEL);
114         final Map<Integer, Batch> sourcesIdsToRemove = Beans.splitById(Beans.getList(parent.getBatches()));
115 
116         // Save each batches
117         sources.forEach(source -> {
118             source.setOperationId(operationId);
119 
120             Batch existingBatch = null;
121             if (source.getId() != null) {
122                 existingBatch = sourcesIdsToRemove.remove(source.getId());
123             }
124             // No id found
125             else {
126                 // Try to get iit by hash code
127                 Collection<Batch> existingBatchs = sourcesByHashCode.get(source.hashCode());
128                 // Not found by hash code: try by label
129                 if (CollectionUtils.isEmpty(existingBatchs)) {
130                     existingBatchs = sourcesByLabelMap.get(source.getLabel());
131                 }
132                 // If one on match => use it
133                 if (CollectionUtils.size(existingBatchs) == 1) {
134                     existingBatch = existingBatchs.iterator().next();
135                     sourcesIdsToRemove.remove(existingBatch.getId());
136                     source.setId(existingBatch.getId());
137                 }
138             }
139             optimizedSave(source, existingBatch, false, newUpdateDate, false);
140         });
141 
142         // Remove unused entities
143         if (MapUtils.isNotEmpty(sourcesIdsToRemove)) {
144             sourcesIdsToRemove.values().forEach(this::delete);
145         }
146 
147         // Remove parent (use only parentId)
148         sources.forEach(batch -> {
149             if (batch.getParent() != null) {
150                 batch.setParentId(batch.getParent().getId());
151                 batch.setParent(null);
152             }
153         });
154 
155         entityManager.flush();
156         entityManager.clear();
157 
158         return sources;
159     }
160 
161 
162     @Override
163     public BatchVOef="../../../../../net/sumaris/core/vo/data/BatchVO.html#BatchVO">BatchVO save(BatchVO source) {
164         Preconditions.checkNotNull(source);
165 
166         EntityManager entityManager = getEntityManager();
167         Batch entity = null;
168         if (source.getId() != null) {
169             entity = get(Batch.class, source.getId());
170         }
171         boolean isNew = (entity == null);
172         if (isNew) {
173             entity = new Batch();
174         }
175 
176         if (!isNew) {
177             // Check update date
178             // FIXME: Cliant app: update entity from the save() result
179             //checkUpdateDateForUpdate(source, entity);
180 
181             // Lock entityName
182             //lockForUpdate(entity);
183         }
184 
185         // Copy some fields from the trip
186         copySomeFieldsFromOperation(source);
187 
188         // VO -> Entity
189         batchVOToEntity(source, entity, true);
190 
191         // Update update_dt
192         Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
193         entity.setUpdateDate(newUpdateDate);
194 
195         // Save entityName
196         if (isNew) {
197             entityManager.persist(entity);
198             source.setId(entity.getId());
199         } else {
200             entityManager.merge(entity);
201         }
202 
203         // Update date
204         source.setUpdateDate(newUpdateDate);
205 
206         entityManager.flush();
207         entityManager.clear();
208 
209         return source;
210     }
211 
212     @Override
213     public void delete(int id) {
214 
215         log.debug(String.format("Deleting batch {id=%s}...", id));
216         delete(Batch.class, id);
217     }
218 
219     @Override
220     public BatchVO toBatchVO(Batch source) {
221         return toBatchVO(source, true);
222     }
223 
224     @Override
225     public List<BatchVO> toFlatList(final BatchVO source) {
226         List<BatchVO> result = Lists.newArrayList();
227         fillListFromTree(result, source);
228         return result;
229     }
230 
231     /* -- protected methods -- */
232 
233     protected BatchVO/../../../net/sumaris/core/vo/data/BatchVO.html#BatchVO">BatchVO optimizedSave(BatchVO source,
234                              Batch entity,
235                              boolean checkUpdateDate,
236                              Timestamp newUpdateDate,
237                              boolean flush) {
238         Preconditions.checkNotNull(source);
239 
240         EntityManager entityManager = getEntityManager();
241         if (entity == null && source.getId() != null) {
242             entity = get(Batch.class, source.getId());
243         }
244         boolean isNew = (entity == null);
245         if (isNew) {
246             entity = new Batch();
247         }
248 
249         if (!isNew && checkUpdateDate) {
250             // Check update date
251             // FIXME: Client app: update entity from the save() result
252             checkUpdateDateForUpdate(source, entity);
253 
254             // Lock entityName
255             //lockForUpdate(entity);
256         }
257 
258         // Copy some fields from the trip
259         copySomeFieldsFromOperation(source);
260 
261         // VO -> Entity
262         batchVOToEntity(source, entity, true);
263 
264         // Update update_dt
265         entity.setUpdateDate(newUpdateDate);
266 
267         // Save entityName
268         if (isNew) {
269             entityManager.persist(entity);
270             source.setId(entity.getId());
271         } else {
272             entityManager.merge(entity);
273         }
274 
275         // Update date
276         source.setUpdateDate(newUpdateDate);
277 
278         if (flush) {
279             entityManager.flush();
280             entityManager.clear();
281         }
282 
283         return source;
284     }
285 
286     protected BatchVO toBatchVO(Batch source, boolean allFields) {
287 
288         if (source == null) return null;
289 
290         BatchVOatchVO.html#BatchVO">BatchVO target = new BatchVO();
291 
292         Beans.copyProperties(source, target);
293 
294         // Taxon group
295         if (source.getTaxonGroup() != null) {
296             ReferentialVO taxonGroup = referentialDao.toReferentialVO(source.getTaxonGroup());
297             target.setTaxonGroup(taxonGroup);
298         }
299 
300         // Taxon name (from reference)
301         if (source.getReferenceTaxon() != null) {
302             TaxonNameVO taxonName = taxonNameDao.getTaxonNameReferent(source.getReferenceTaxon().getId());
303             target.setTaxonName(taxonName);
304         }
305 
306         // Parent batch
307         if (source.getParent() != null) {
308             target.setParentId(source.getParent().getId());
309         }
310 
311         // Operation
312         if (source.getOperation() != null) {
313             target.setOperationId(source.getOperation().getId());
314         }
315 
316         // If full export
317         if (allFields) {
318             // Recorder department
319             DepartmentVO recorderDepartment = referentialDao.toTypedVO(source.getRecorderDepartment(), DepartmentVO.class).orElse(null);
320             target.setRecorderDepartment(recorderDepartment);
321         }
322 
323         return target;
324     }
325 
326     protected void copySomeFieldsFromOperation(BatchVO target) {
327         OperationVO source = target.getOperation();
328         if (source == null) return;
329 
330         target.setRecorderDepartment(source.getRecorderDepartment());
331     }
332 
333     protected List<BatchVO> toBatchVOs(List<Batch> source, boolean allFields) {
334         return this.toBatchVOs(source.stream(), allFields);
335     }
336 
337     protected List<BatchVO> toBatchVOs(Stream<Batch> source, boolean allFields) {
338         return source.map(s -> this.toBatchVO(s, allFields))
339                 .filter(Objects::nonNull)
340                 .collect(Collectors.toList());
341     }
342 
343     protected void batchVOToEntity(BatchVO source, Batch target, boolean copyIfNull) {
344 
345         Beans.copyProperties(source, target);
346 
347         // Taxon group
348         if (copyIfNull || source.getTaxonGroup() != null) {
349             if (source.getTaxonGroup() == null || source.getTaxonGroup().getId() == null) {
350                 target.setTaxonGroup(null);
351             }
352             else {
353                 target.setTaxonGroup(load(TaxonGroup.class, source.getTaxonGroup().getId()));
354             }
355         }
356 
357         // Reference taxon (from taxon name)
358         if (copyIfNull || source.getTaxonName() != null) {
359             if (source.getTaxonName() == null || source.getTaxonName().getId() == null) {
360                 target.setReferenceTaxon(null);
361             }
362             else {
363                 if (source.getTaxonName().getReferenceTaxonId() != null) {
364                     target.setReferenceTaxon(load(ReferenceTaxon.class, source.getTaxonName().getReferenceTaxonId()));
365                 }
366                 else {
367                     // Get the taxon name, then set reference taxon
368                     TaxonName taxonname = get(TaxonName.class, source.getTaxonName().getId());
369                     if (taxonname != null) {
370                         target.setReferenceTaxon(taxonname.getReferenceTaxon());
371                     }
372                     else {
373                         throw new DataIntegrityViolationException("Invalid batch: unknown taxon name with id "+ source.getTaxonName().getId());
374                     }
375                 }
376             }
377         }
378 
379         Integer parentId = (source.getParent() != null ? source.getParent().getId() : source.getParentId());
380         Integer opeId = source.getOperationId() != null ? source.getOperationId() : (source.getOperation() != null ? source.getOperation().getId() : null);
381 
382         // Parent batch
383         if (copyIfNull || (parentId != null)) {
384             if (parentId == null) {
385                 target.setParent(null);
386             }
387             else {
388                 Batch parent = load(Batch.class, parentId);
389                 target.setParent(parent);
390 
391                 // Force same operation as parent
392                 opeId = parent.getOperation().getId();
393             }
394         }
395 
396         // Operation
397         if (copyIfNull || (opeId != null)) {
398             if (opeId == null) {
399                 target.setOperation(null);
400             }
401             else {
402                 target.setOperation(load(Operation.class, opeId));
403             }
404         }
405 
406         // Recorder department
407         if (copyIfNull || source.getRecorderDepartment() != null) {
408             if (source.getRecorderDepartment() == null || source.getRecorderDepartment().getId() == null) {
409                 target.setRecorderDepartment(null);
410             }
411             else {
412                 target.setRecorderDepartment(load(Department.class, source.getRecorderDepartment().getId()));
413             }
414         }
415 
416         // Quality flag
417         if (copyIfNull || source.getQualityFlagId() != null) {
418             if (source.getQualityFlagId() == null) {
419                 target.setQualityFlag(load(QualityFlag.class, config.getDefaultQualityFlagId()));
420             }
421             else {
422                 target.setQualityFlag(load(QualityFlag.class, source.getQualityFlagId()));
423             }
424         }
425 
426         // Store hash code
427         target.setHash(source.hashCode());
428     }
429 
430     protected void fillListFromTree(final List<BatchVO> result, final BatchVO source) {
431 
432         result.add(source);
433 
434         if (CollectionUtils.isNotEmpty(source.getChildren())) {
435             source.getChildren().forEach(child -> {
436                 child.setParentId(source.getId());
437                 fillListFromTree(result, child);
438             });
439         }
440 
441         // Not need anymore
442         source.setParent(null);
443         source.setChildren(null);
444     }
445 }