View Javadoc
1   package net.sumaris.core.dao.administration.programStrategy;
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.ImmutableList;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import net.sumaris.core.dao.referential.ReferentialDao;
30  import net.sumaris.core.dao.referential.taxon.TaxonGroupRepository;
31  import net.sumaris.core.dao.technical.SortDirection;
32  import net.sumaris.core.dao.technical.hibernate.HibernateDaoSupport;
33  import net.sumaris.core.model.administration.programStrategy.*;
34  import net.sumaris.core.model.referential.IReferentialEntity;
35  import net.sumaris.core.model.referential.Status;
36  import net.sumaris.core.model.referential.StatusEnum;
37  import net.sumaris.core.model.referential.gear.Gear;
38  import net.sumaris.core.model.referential.taxon.TaxonGroup;
39  import net.sumaris.core.util.Beans;
40  import net.sumaris.core.vo.administration.programStrategy.ProgramFetchOptions;
41  import net.sumaris.core.vo.administration.programStrategy.ProgramVO;
42  import net.sumaris.core.vo.filter.ProgramFilterVO;
43  import net.sumaris.core.vo.referential.ReferentialVO;
44  import net.sumaris.core.vo.referential.TaxonGroupVO;
45  import org.apache.commons.collections4.MapUtils;
46  import org.apache.commons.lang3.StringUtils;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  import org.springframework.beans.factory.annotation.Autowired;
50  import org.springframework.dao.EmptyResultDataAccessException;
51  import org.springframework.stereotype.Repository;
52  
53  import javax.annotation.PostConstruct;
54  import javax.persistence.EntityManager;
55  import javax.persistence.LockModeType;
56  import javax.persistence.NoResultException;
57  import javax.persistence.TypedQuery;
58  import javax.persistence.criteria.*;
59  import java.sql.Timestamp;
60  import java.util.*;
61  import java.util.stream.Collectors;
62  
63  @Repository("programDao")
64  public class ProgramDaoImpl extends HibernateDaoSupport implements ProgramDao {
65  
66      /** Logger. */
67      private static final Logger log =
68              LoggerFactory.getLogger(ProgramDaoImpl.class);
69  
70      @Autowired
71      private TaxonGroupRepository taxonGroupRepository;
72  
73      @Autowired
74      private ReferentialDao referentialDao;
75  
76      @PostConstruct
77      protected void init() {
78          Arrays.stream(ProgramEnum.values()).forEach(programEnum -> {
79              try {
80                  ProgramVO program = getByLabel(programEnum.name());
81                  if (program != null) {
82                      programEnum.setId(program.getId());
83                  } else {
84                      // TODO query by id and show program code/name
85                      log.warn("Missing program with label=" + programEnum.name());
86                  }
87              } catch(Throwable t) {
88                  log.error(String.format("Could not initialized enumeration for program {%s}: %s", programEnum.name(), t.getMessage()), t);
89              }
90          });
91      }
92  
93      @Override
94      public List<ProgramVO> getAll() {
95          CriteriaQuery<Program> query = entityManager.getCriteriaBuilder().createQuery(Program.class);
96          Root<Program> root = query.from(Program.class);
97  
98          query.select(root);
99  
100         return getEntityManager()
101                 .createQuery(query)
102                 .getResultStream()
103                 .map(this::toProgramVO)
104                 .collect(Collectors.toList());
105     }
106 
107     @Override
108     public List<ProgramVO> findByFilter(ProgramFilterVO filter, int offset, int size, String sortAttribute, SortDirection sortDirection) {
109         Preconditions.checkNotNull(filter);
110         Preconditions.checkArgument(offset >= 0);
111         Preconditions.checkArgument(size > 0);
112 
113         EntityManager entityManager = getEntityManager();
114         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
115         CriteriaQuery<Program> query = builder.createQuery(Program.class);
116         Root<Program> root = query.from(Program.class);
117 
118         Join<Program, ProgramProperty> upJ = root.join(Program.Fields.PROPERTIES, JoinType.LEFT);
119 
120         ParameterExpression<String> withPropertyParam = builder.parameter(String.class);
121         ParameterExpression<Collection> statusIdsParam = builder.parameter(Collection.class);
122         ParameterExpression<String> searchTextParam = builder.parameter(String.class);
123 
124         query.select(root).distinct(true)
125                 .where(
126                         builder.and(
127                                 // property
128                                 builder.or(
129                                         builder.isNull(withPropertyParam),
130                                         builder.equal(upJ.get(ProgramProperty.Fields.LABEL), withPropertyParam)
131                                 ),
132                                 // status Ids
133                                 builder.or(
134                                         builder.isNull(statusIdsParam),
135                                         root.get(Program.Fields.STATUS).get(IReferentialEntity.Fields.ID).in(statusIdsParam)
136                                 ),
137                                 // search text
138                                 builder.or(
139                                         builder.isNull(searchTextParam),
140                                         builder.like(builder.upper(root.get(Program.Fields.LABEL)), builder.upper(searchTextParam)),
141                                         builder.like(builder.upper(root.get(Program.Fields.NAME)), builder.upper(searchTextParam))
142                                 )
143                         ));
144 
145         if (StringUtils.isNotBlank(sortAttribute)) {
146             if (sortDirection == SortDirection.ASC) {
147                 query.orderBy(builder.asc(root.get(sortAttribute)));
148             } else {
149                 query.orderBy(builder.desc(root.get(sortAttribute)));
150             }
151         }
152 
153         String searchText = StringUtils.trimToNull(filter.getSearchText());
154         String searchTextAnyMatch = null;
155         if (StringUtils.isNotBlank(searchText)) {
156             searchTextAnyMatch = ("*" + searchText + "*"); // add trailing escape char
157             searchTextAnyMatch = searchTextAnyMatch.replaceAll("[*]+", "*"); // group escape chars
158             searchTextAnyMatch = searchTextAnyMatch.replaceAll("[%]", "\\%"); // protected '%' chars
159             searchTextAnyMatch = searchTextAnyMatch.replaceAll("[*]", "%"); // replace asterix
160         }
161 
162         return entityManager.createQuery(query)
163                 .setParameter(withPropertyParam, filter.getWithProperty())
164                 .setParameter(statusIdsParam, filter.getStatusIds())
165                 .setParameter(searchTextParam, searchTextAnyMatch)
166                 .setFirstResult(offset)
167                 .setMaxResults(size)
168                 .getResultList()
169                 .stream()
170                 .map(this::toProgramVO)
171                 .collect(Collectors.toList());
172     }
173 
174     @Override
175     public ProgramVO get(final int id) {
176         return toProgramVO(get(Program.class, id));
177     }
178 
179     @Override
180     public ProgramVO getByLabel(String label) {
181         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
182         CriteriaQuery<Program> query = builder.createQuery(Program.class);
183         Root<Program> root = query.from(Program.class);
184 
185         ParameterExpression<String> labelParam = builder.parameter(String.class);
186 
187         query.select(root)
188                 .where(builder.equal(root.get(Program.Fields.LABEL), labelParam));
189 
190         TypedQuery<Program> q = getEntityManager().createQuery(query)
191                 .setParameter(labelParam, label);
192         try {
193             return toProgramVO(q.getSingleResult());
194         } catch(EmptyResultDataAccessException | NoResultException e) {
195             return null;
196         }
197     }
198 
199 
200     @Override
201     public ProgramVO="../../../../../../net/sumaris/core/vo/administration/programStrategy/ProgramVO.html#ProgramVO">ProgramVO save(ProgramVO source) {
202         Preconditions.checkNotNull(source);
203         Preconditions.checkNotNull(source.getLabel(), "Missing 'label'");
204         Preconditions.checkNotNull(source.getName(), "Missing 'name'");
205         Preconditions.checkNotNull(source.getStatusId(), "Missing 'statusId'");
206 
207         EntityManager entityManager = getEntityManager();
208         Program entity = null;
209         if (source.getId() != null) {
210             entity = get(Program.class, source.getId());
211         }
212         boolean isNew = (entity == null);
213         if (isNew) {
214             entity = new Program();
215         }
216 
217         // If new
218         if (isNew) {
219             // Set default status to Temporary
220             if (source.getStatusId() == null) {
221                 source.setStatusId(config.getStatusIdTemporary());
222             }
223         }
224         // If update
225         else {
226 
227             // Check update date
228             checkUpdateDateForUpdate(source, entity);
229 
230             // Lock entityName
231             lockForUpdate(entity, LockModeType.PESSIMISTIC_WRITE);
232         }
233 
234         programVOToEntity(source, entity, true);
235 
236         // Update update_dt
237         Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
238         entity.setUpdateDate(newUpdateDate);
239 
240         // Save entity
241         if (isNew) {
242             // Force creation date
243             entity.setCreationDate(newUpdateDate);
244             source.setCreationDate(newUpdateDate);
245 
246             entityManager.persist(entity);
247             source.setId(entity.getId());
248         } else {
249             entityManager.merge(entity);
250         }
251 
252         source.setUpdateDate(newUpdateDate);
253 
254         // Save properties
255         saveProperties(source.getProperties(), entity, newUpdateDate);
256 
257         getEntityManager().flush();
258         getEntityManager().clear();
259 
260         // Emit event to listeners
261         //emitSaveEvent(source);
262 
263         return source;
264     }
265 
266     @Override
267     public List<ReferentialVO> getGears(int programId) {
268         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
269         CriteriaQuery<Gear> query = builder.createQuery(Gear.class);
270         Root<Gear> root = query.from(Gear.class);
271 
272         ParameterExpression<Integer> programIdParam = builder.parameter(Integer.class);
273 
274         Join<Gear, Strategy> gearInnerJoin = root.joinList(Gear.Fields.STRATEGIES, JoinType.INNER);
275 
276         query.select(root)
277                 .where(
278                         builder.and(
279                                 // program
280                                 builder.equal(gearInnerJoin.get(Strategy.Fields.PROGRAM).get(Program.Fields.ID), programIdParam),
281                                 // Status (temporary or valid)
282                                 builder.in(root.get(Gear.Fields.STATUS).get(Status.Fields.ID)).value(ImmutableList.of(StatusEnum.ENABLE.getId(), StatusEnum.TEMPORARY.getId()))
283                         ));
284 
285         // Sort by rank order
286         query.orderBy(builder.asc(root.get(Gear.Fields.LABEL)));
287 
288         return getEntityManager()
289                 .createQuery(query)
290                 .setParameter(programIdParam, programId)
291                 .getResultStream()
292                 .map(referentialDao::toReferentialVO)
293                 .collect(Collectors.toList());
294     }
295 
296     @Override
297     public List<TaxonGroupVO> getTaxonGroups(int programId) {
298         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
299         CriteriaQuery<TaxonGroup> query = builder.createQuery(TaxonGroup.class);
300         Root<TaxonGroup> root = query.from(TaxonGroup.class);
301 
302         ParameterExpression<Integer> programIdParam = builder.parameter(Integer.class);
303 
304         Join<TaxonGroup, TaxonGroupStrategy> innerJoinTGS = root.joinList(TaxonGroup.Fields.STRATEGIES, JoinType.INNER);
305         Join<TaxonGroupStrategy, Strategy> innerJoinS = innerJoinTGS.join(TaxonGroupStrategy.Fields.STRATEGY, JoinType.INNER);
306 
307 
308         query.select(root)
309                 .where(
310                         builder.and(
311                                 // program
312                                 builder.equal(innerJoinS.get(Strategy.Fields.PROGRAM).get(Program.Fields.ID), programIdParam),
313                                 // Status (temporary or valid)
314                                 builder.in(root.get(TaxonGroup.Fields.STATUS).get(Status.Fields.ID)).value(ImmutableList.of(StatusEnum.ENABLE.getId(), StatusEnum.TEMPORARY.getId()))
315                         ));
316 
317         // Sort by rank order
318         query.orderBy(builder.asc(root.get(TaxonGroup.Fields.LABEL)));
319 
320         return getEntityManager()
321                 .createQuery(query)
322                 .setParameter(programIdParam, programId)
323                 .getResultStream()
324                 .map(taxonGroupRepository::toTaxonGroupVO)
325                 .collect(Collectors.toList());
326     }
327 
328     @Override
329     public void delete(int id) {
330         log.debug(String.format("Deleting program {id=%s}...", id));
331         delete(Program.class, id);
332     }
333 
334 
335     @Override
336     public ProgramVO toProgramVO(Program source) {
337         return toProgramVO(source, ProgramFetchOptions.builder()
338                 .withProperties(true)
339                 .build());
340     }
341 
342     @Override
343     public ProgramVO toProgramVO(Program source, ProgramFetchOptions fetchOptions) {
344         if (source == null) return null;
345 
346         ProgramVOistration/programStrategy/ProgramVO.html#ProgramVO">ProgramVO target = new ProgramVO();
347 
348         Beans.copyProperties(source, target);
349 
350         // Status id
351         target.setStatusId(source.getStatus().getId());
352 
353         // properties
354         if (fetchOptions.isWithProperties()) {
355             Map<String, String> properties = Maps.newHashMap();
356             Beans.getStream(source.getProperties())
357                     .filter(prop -> Objects.nonNull(prop)
358                             && Objects.nonNull(prop.getLabel())
359                             && Objects.nonNull(prop.getName())
360                     )
361                     .forEach(prop -> {
362                         if (properties.containsKey(prop.getLabel())) {
363                             logger.warn(String.format("Duplicate program property with label {%s}. Overriding existing value with {%s}", prop.getLabel(), prop.getName()));
364                         }
365                         properties.put(prop.getLabel(), prop.getName());
366                     });
367             target.setProperties(properties);
368         }
369 
370         return target;
371     }
372 
373     /* -- protected methods -- */
374 
375 
376 
377 
378     protected void programVOToEntity(ProgramVO source, Program target, boolean copyIfNull) {
379 
380         Beans.copyProperties(source, target);
381 
382         // Status
383         if (copyIfNull || source.getStatusId() != null) {
384             if (source.getStatusId() == null) {
385                 target.setStatus(null);
386             }
387             else {
388                 target.setStatus(load(Status.class, source.getStatusId()));
389             }
390         }
391 
392     }
393 
394     protected void saveProperties(Map<String, String> source, Program parent, Timestamp updateDate) {
395         final EntityManager em = getEntityManager();
396         if (MapUtils.isEmpty(source)) {
397             if (parent.getProperties() != null) {
398                 List<ProgramProperty> toRemove = ImmutableList.copyOf(parent.getProperties());
399                 parent.getProperties().clear();
400                 toRemove.forEach(em::remove);
401             }
402         }
403         else {
404             Map<String, ProgramProperty> existingProperties = Beans.splitByProperty(
405                     Beans.getList(parent.getProperties()),
406                     ProgramProperty.Fields.LABEL);
407             final Status enableStatus = em.getReference(Status.class, StatusEnum.ENABLE.getId());
408             if (parent.getProperties() == null) {
409                 parent.setProperties(Lists.newArrayList());
410             }
411             final List<ProgramProperty> targetProperties = parent.getProperties();
412 
413             // Transform each entry into ProgramProperty
414             source.entrySet().stream()
415                     .filter(e -> Objects.nonNull(e.getKey())
416                             && Objects.nonNull(e.getValue())
417                     )
418                     .map(e -> {
419                         ProgramProperty prop = existingProperties.remove(e.getKey());
420                         boolean isNew = (prop == null);
421                         if (isNew) {
422                             prop = new ProgramProperty();
423                             prop.setLabel(e.getKey());
424                             prop.setProgram(parent);
425                             prop.setCreationDate(updateDate);
426                         }
427                         prop.setName(e.getValue());
428                         prop.setStatus(enableStatus);
429                         prop.setUpdateDate(updateDate);
430                         if (isNew) {
431                             em.persist(prop);
432                         }
433                         else {
434                             em.merge(prop);
435                         }
436                         return prop;
437                     })
438                     .forEach(targetProperties::add);
439 
440             // Remove old properties
441             if (MapUtils.isNotEmpty(existingProperties)) {
442                 parent.getProperties().removeAll(existingProperties.values());
443                 existingProperties.values().forEach(em::remove);
444             }
445 
446         }
447     }
448 }