View Javadoc
1   package net.sumaris.core.dao.data;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Core
6    * %%
7    * Copyright (C) 2018 - 2019 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 lombok.AllArgsConstructor;
27  import lombok.Data;
28  import net.sumaris.core.dao.referential.ReferentialDao;
29  import net.sumaris.core.dao.referential.location.LocationDao;
30  import net.sumaris.core.dao.technical.SortDirection;
31  import net.sumaris.core.model.data.Vessel;
32  import net.sumaris.core.model.data.VesselFeatures;
33  import net.sumaris.core.model.data.VesselRegistrationPeriod;
34  import net.sumaris.core.model.referential.Status;
35  import net.sumaris.core.util.Beans;
36  import net.sumaris.core.vo.administration.user.DepartmentVO;
37  import net.sumaris.core.vo.data.VesselSnapshotVO;
38  import net.sumaris.core.vo.filter.VesselFilterVO;
39  import net.sumaris.core.vo.referential.LocationVO;
40  import net.sumaris.core.vo.referential.ReferentialVO;
41  import org.apache.commons.collections4.CollectionUtils;
42  import org.apache.commons.lang3.StringUtils;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  import org.springframework.beans.factory.annotation.Autowired;
46  import org.springframework.stereotype.Repository;
47  
48  import javax.persistence.TypedQuery;
49  import javax.persistence.criteria.*;
50  import java.util.Collection;
51  import java.util.Date;
52  import java.util.List;
53  import java.util.Objects;
54  import java.util.stream.Collectors;
55  
56  /**
57   * @author peck7 on 19/11/2019.
58   */
59  @Repository("vesselSnapshotDao")
60  public class VesselSnapshotDaoImpl extends BaseDataDaoImpl implements VesselSnapshotDao {
61  
62      /** Logger. */
63      private static final Logger log = LoggerFactory.getLogger(VesselSnapshotDaoImpl.class);
64  
65      @Autowired
66      private LocationDao locationDao;
67  
68      @Autowired
69      private ReferentialDao referentialDao;
70  
71      @Override
72      public VesselSnapshotVO getByIdAndDate(int vesselId, Date date) {
73          VesselFilterVOFilterVO.html#VesselFilterVO">VesselFilterVO filter = new VesselFilterVO();
74          filter.setVesselId(vesselId);
75          filter.setDate(date);
76          List<VesselSnapshotVO> res = findByFilter(filter, 0, 1, VesselFeatures.Fields.START_DATE, SortDirection.DESC);
77  
78          // No result for this date
79          if (res.size() == 0) {
80              // Retry using only vessel id (and limit to most recent features)
81              filter.setDate(null);
82              res = findByFilter(filter, 0, 1, VesselFeatures.Fields.START_DATE, SortDirection.DESC);
83              if (res.size() == 0) {
84                  VesselSnapshotVOhtml#VesselSnapshotVO">VesselSnapshotVO unknownVessel = new VesselSnapshotVO();
85                  unknownVessel.setId(vesselId);
86                  unknownVessel.setName("Unknown vessel " + vesselId); // TODO remove string
87                  return unknownVessel;
88              }
89          }
90          return res.get(0);
91      }
92  
93      @Override
94      public List<VesselSnapshotVO> findByFilter(VesselFilterVO filter, int offset, int size, String sortAttribute, SortDirection sortDirection) {
95          Preconditions.checkArgument(offset >= 0);
96          Preconditions.checkArgument(size > 0);
97  
98          CriteriaBuilder cb = entityManager.getCriteriaBuilder();
99          CriteriaQuery<VesselSnapshotResult> query = cb.createQuery(VesselSnapshotResult.class);
100         Root<VesselFeatures> root = query.from(VesselFeatures.class);
101 
102         Join<VesselFeatures, Vessel> vesselJoin = root.join(VesselFeatures.Fields.VESSEL, JoinType.INNER);
103         Join<Vessel, VesselRegistrationPeriod> vrpJoin = vesselJoin.join(Vessel.Fields.VESSEL_REGISTRATION_PERIODS, JoinType.LEFT);
104 
105         query.multiselect(root, vrpJoin);
106 
107         // Apply sorting
108         addSorting(query, cb, root, sortAttribute, sortDirection);
109 
110         // No tripFilter: execute request
111         if (filter == null) {
112             TypedQuery<VesselSnapshotResult> q = getEntityManager().createQuery(query)
113                 .setFirstResult(offset)
114                 .setMaxResults(size);
115             return toVesselSnapshotVOs(q.getResultList());
116         }
117 
118         List<Integer> statusIds = CollectionUtils.isEmpty(filter.getStatusIds())
119             ? null
120             : filter.getStatusIds();
121 
122         // Apply vessel Filter
123         ParameterExpression<Date> dateParam = cb.parameter(Date.class);
124         ParameterExpression<Integer> vesselIdParam = cb.parameter(Integer.class);
125         ParameterExpression<Integer> vesselFeaturesIdParam = cb.parameter(Integer.class);
126         ParameterExpression<String> searchNameParam = cb.parameter(String.class);
127         ParameterExpression<String> searchExteriorMarkingParam = cb.parameter(String.class);
128         ParameterExpression<String> searchRegistrationCodeParam = cb.parameter(String.class);
129         ParameterExpression<Boolean> hasStatusIdsParam = cb.parameter(Boolean.class);
130         ParameterExpression<Collection> statusIdsParam = cb.parameter(Collection.class);
131 
132         query.where(cb.and(
133             // Filter: date
134             cb.or(
135                 cb.and(
136                     // if no date in filter, will return only active period
137                     cb.isNull(dateParam),
138                     cb.isNull(root.get(VesselFeatures.Fields.END_DATE)),
139                     cb.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE))
140                 ),
141                 cb.and(
142                     cb.isNotNull(dateParam),
143                     cb.and(
144                         cb.or(
145                             cb.isNull(root.get(VesselFeatures.Fields.END_DATE)),
146                             cb.greaterThan(root.get(VesselFeatures.Fields.END_DATE), dateParam)
147                         ),
148                         cb.lessThan(root.get(VesselFeatures.Fields.START_DATE), dateParam)
149                     ),
150                     cb.and(
151                         cb.or(
152                             cb.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE)),
153                             cb.greaterThan(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE), dateParam)
154                         ),
155                         cb.lessThan(vrpJoin.get(VesselRegistrationPeriod.Fields.START_DATE), dateParam)
156                     )
157                 )
158             ),
159 
160             // Filter: vessel features id
161             cb.or(
162                 cb.isNull(vesselFeaturesIdParam),
163                 cb.equal(root.get(VesselFeatures.Fields.ID), vesselFeaturesIdParam)
164             ),
165 
166             // Filter: vessel id
167             cb.or(
168                 cb.isNull(vesselIdParam),
169                 cb.equal(vesselJoin.get(Vessel.Fields.ID), vesselIdParam))
170             ),
171 
172             // Filter: search text (on exterior marking OR id)
173             cb.or(
174                 cb.isNull(searchNameParam),
175                 cb.like(cb.lower(root.get(VesselFeatures.Fields.NAME)), cb.lower(searchNameParam)),
176                 cb.like(cb.lower(root.get(VesselFeatures.Fields.EXTERIOR_MARKING)), cb.lower(searchExteriorMarkingParam)),
177                 cb.like(cb.lower(vrpJoin.get(VesselRegistrationPeriod.Fields.REGISTRATION_CODE)), cb.lower(searchRegistrationCodeParam))
178             ),
179 
180             // Status
181             cb.or(
182                 cb.isFalse(hasStatusIdsParam),
183                 cb.in(vesselJoin.get(Vessel.Fields.STATUS).get(Status.Fields.ID)).value(statusIdsParam)
184             )
185         );
186 
187 
188         String searchText = StringUtils.trimToNull(filter.getSearchText());
189         String searchTextAsPrefix = null;
190         if (StringUtils.isNotBlank(searchText)) {
191             searchTextAsPrefix = (searchText + "*"); // add trailing escape char
192             searchTextAsPrefix = searchTextAsPrefix.replaceAll("[*]+", "*"); // group escape chars
193             searchTextAsPrefix = searchTextAsPrefix.replaceAll("[%]", "\\%"); // protected '%' chars
194             searchTextAsPrefix = searchTextAsPrefix.replaceAll("[*]", "%"); // replace asterix
195         }
196         String searchTextAnyMatch = StringUtils.isNotBlank(searchTextAsPrefix) ? ("%"+searchTextAsPrefix) : null;
197 
198         TypedQuery<VesselSnapshotResult> q = entityManager.createQuery(query)
199             .setParameter(dateParam, filter.getDate())
200             .setParameter(vesselFeaturesIdParam, filter.getVesselFeaturesId())
201             .setParameter(vesselIdParam, filter.getVesselId())
202             .setParameter(searchExteriorMarkingParam, searchTextAsPrefix)
203             .setParameter(searchRegistrationCodeParam, searchTextAsPrefix)
204             .setParameter(searchNameParam, searchTextAnyMatch)
205             .setParameter(hasStatusIdsParam, CollectionUtils.isNotEmpty(statusIds))
206             .setParameter(statusIdsParam, statusIds)
207             .setFirstResult(offset)
208             .setMaxResults(size);
209         List<VesselSnapshotResult> result = q.getResultList();
210         return toVesselSnapshotVOs(result);
211     }
212 
213     private List<VesselSnapshotVO> toVesselSnapshotVOs(List<VesselSnapshotResult> source) {
214         return source.stream()
215             .map(this::toVesselSnapshotVO)
216             .filter(Objects::nonNull)
217             .collect(Collectors.toList());
218     }
219 
220     private VesselSnapshotVO toVesselSnapshotVO(VesselSnapshotResult source) {
221         if (source == null)
222             return null;
223 
224         VesselSnapshotVOshotVO.html#VesselSnapshotVO">VesselSnapshotVO target = new VesselSnapshotVO();
225 
226         // Vessel features
227         VesselFeatures features = source.getVesselFeatures();
228 
229         Beans.copyProperties(features, target);
230 
231         // Convert from cm to m
232         if (features.getLengthOverAll() != null) {
233             target.setLengthOverAll(features.getLengthOverAll().doubleValue() /100);
234         }
235         // Convert tonnage (divide by 100)
236         if (features.getGrossTonnageGrt() != null) {
237             target.setGrossTonnageGrt(features.getGrossTonnageGrt().doubleValue() / 100);
238         }
239         if (features.getGrossTonnageGt() != null) {
240             target.setGrossTonnageGt(features.getGrossTonnageGt().doubleValue() / 100);
241         }
242 
243         target.setId(features.getVessel().getId());
244         target.setVesselStatusId(features.getVessel().getStatus().getId());
245         target.setQualityFlagId(features.getQualityFlag().getId());
246 
247         // Vessel type
248         ReferentialVO vesselType = referentialDao.toReferentialVO(features.getVessel().getVesselType());
249         target.setVesselType(vesselType);
250 
251         // base port location
252         LocationVO basePortLocation = locationDao.toLocationVO(features.getBasePortLocation());
253         target.setBasePortLocation(basePortLocation);
254 
255         // Recorder department
256         DepartmentVO recorderDepartment = referentialDao.toTypedVO(features.getRecorderDepartment(), DepartmentVO.class).orElse(null);
257         target.setRecorderDepartment(recorderDepartment);
258 
259         // Registration period
260         VesselRegistrationPeriod period = source.getVesselRegistrationPeriod();
261         if (period != null) {
262 
263             // Registration code
264             target.setRegistrationCode(period.getRegistrationCode());
265             // Registration location
266             LocationVO registrationLocation = locationDao.toLocationVO(period.getRegistrationLocation());
267             target.setRegistrationLocation(registrationLocation);
268         }
269 
270         return target;
271 
272     }
273 
274     @Data
275     @AllArgsConstructor
276     public static class VesselSnapshotResult {
277         VesselFeatures vesselFeatures;
278         VesselRegistrationPeriod vesselRegistrationPeriod;
279     }
280 
281 }