1 package net.sumaris.core.dao.data;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import com.google.common.base.Preconditions;
26 import lombok.AllArgsConstructor;
27 import lombok.Data;
28 import net.sumaris.core.config.SumarisConfiguration;
29 import net.sumaris.core.dao.referential.ReferentialDao;
30 import net.sumaris.core.dao.referential.location.LocationDao;
31 import net.sumaris.core.dao.technical.SortDirection;
32 import net.sumaris.core.model.QualityFlagEnum;
33 import net.sumaris.core.model.administration.programStrategy.Program;
34 import net.sumaris.core.model.administration.programStrategy.ProgramEnum;
35 import net.sumaris.core.model.data.Vessel;
36 import net.sumaris.core.model.data.VesselFeatures;
37 import net.sumaris.core.model.data.VesselRegistrationPeriod;
38 import net.sumaris.core.model.referential.QualityFlag;
39 import net.sumaris.core.model.referential.Status;
40 import net.sumaris.core.model.referential.VesselType;
41 import net.sumaris.core.model.referential.location.Location;
42 import net.sumaris.core.util.Beans;
43 import net.sumaris.core.vo.administration.user.DepartmentVO;
44 import net.sumaris.core.vo.data.VesselFeaturesVO;
45 import net.sumaris.core.vo.data.VesselRegistrationVO;
46 import net.sumaris.core.vo.data.VesselVO;
47 import net.sumaris.core.vo.filter.VesselFilterVO;
48 import net.sumaris.core.vo.referential.LocationVO;
49 import net.sumaris.core.vo.referential.ReferentialVO;
50 import org.apache.commons.collections4.CollectionUtils;
51 import org.apache.commons.lang3.StringUtils;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.springframework.beans.factory.annotation.Autowired;
55 import org.springframework.stereotype.Repository;
56
57 import javax.persistence.TypedQuery;
58 import javax.persistence.criteria.*;
59 import java.sql.Timestamp;
60 import java.util.Collection;
61 import java.util.Date;
62 import java.util.List;
63 import java.util.Objects;
64 import java.util.stream.Collectors;
65
66 @Repository("vesselDao")
67 public class VesselDaoImpl extends BaseDataDaoImpl implements VesselDao {
68
69
70
71
72 private static final Logger log = LoggerFactory.getLogger(VesselDaoImpl.class);
73
74 @Autowired
75 private LocationDao locationDao;
76
77 @Autowired
78 private ReferentialDao referentialDao;
79
80 @Override
81 public VesselVO get(int id) {
82
83 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
84 CriteriaQuery<VesselResult> query = builder.createQuery(VesselResult.class);
85
86 Root<Vessel> vesselRoot = query.from(Vessel.class);
87 Join<Vessel, VesselFeatures> featuresJoin = vesselRoot.join(Vessel.Fields.VESSEL_FEATURES, JoinType.LEFT);
88 Join<Vessel, VesselRegistrationPeriod> vrpJoin = vesselRoot.join(Vessel.Fields.VESSEL_REGISTRATION_PERIODS, JoinType.LEFT);
89
90 query.multiselect(vesselRoot, featuresJoin, vrpJoin);
91
92
93 query.where(builder.and(
94 builder.equal(vesselRoot.get(Vessel.Fields.ID), id),
95 builder.isNull(featuresJoin.get(VesselFeatures.Fields.END_DATE)),
96 builder.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE))
97 ));
98
99 TypedQuery<VesselResult> q = getEntityManager().createQuery(query);
100 VesselResult result = q.getSingleResult();
101
102
103 return toVesselVO(result);
104 }
105
106 @Override
107 public List<VesselVO> findByFilter(VesselFilterVO filter, int offset, int size, String sortAttribute, SortDirection sortDirection) {
108 Preconditions.checkArgument(offset >= 0);
109 Preconditions.checkArgument(size > 0);
110
111 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
112 CriteriaQuery<VesselResult> query = builder.createQuery(VesselResult.class);
113 Root<Vessel> vesselRoot = query.from(Vessel.class);
114 Join<Vessel, VesselFeatures> featuresJoin = vesselRoot.join(Vessel.Fields.VESSEL_FEATURES, JoinType.LEFT);
115 Join<Vessel, VesselRegistrationPeriod> vrpJoin = vesselRoot.join(Vessel.Fields.VESSEL_REGISTRATION_PERIODS, JoinType.LEFT);
116
117
118 query.multiselect(vesselRoot, featuresJoin, vrpJoin);
119
120
121 if (StringUtils.isNotBlank(sortAttribute)) {
122
123 sortAttribute = sortAttribute.replaceFirst(VesselVO.Fields.FEATURES, Vessel.Fields.VESSEL_FEATURES);
124 sortAttribute = sortAttribute.replaceFirst(VesselVO.Fields.REGISTRATION, Vessel.Fields.VESSEL_REGISTRATION_PERIODS);
125 sortAttribute = sortAttribute.replaceFirst(VesselVO.Fields.STATUS_ID, Vessel.Fields.STATUS + "." + Status.Fields.ID);
126 }
127 addSorting(query, builder, vesselRoot, sortAttribute, sortDirection);
128
129
130 TypedQuery<VesselResult> typedQuery = createVesselQuery(builder, query, vesselRoot, featuresJoin, vrpJoin, filter)
131 .setFirstResult(offset)
132 .setMaxResults(size);
133 return toVesselVOs(typedQuery.getResultList());
134
135 }
136
137 @Override
138 public Long countByFilter(VesselFilterVO filter) {
139
140 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
141 CriteriaQuery<Long> query = builder.createQuery(Long.class);
142 Root<Vessel> vesselRoot = query.from(Vessel.class);
143 Join<Vessel, VesselFeatures> featuresJoin = vesselRoot.join(Vessel.Fields.VESSEL_FEATURES, JoinType.LEFT);
144 Join<Vessel, VesselRegistrationPeriod> vrpJoin = vesselRoot.join(Vessel.Fields.VESSEL_REGISTRATION_PERIODS, JoinType.LEFT);
145
146 query.select(builder.count(vesselRoot));
147
148 return createVesselQuery(builder, query, vesselRoot, featuresJoin, vrpJoin, filter).getSingleResult();
149 }
150
151 @Override
152 public List<VesselFeaturesVO> getFeaturesByVesselId(int vesselId, int offset, int size, String sortAttribute, SortDirection sortDirection) {
153 Preconditions.checkArgument(offset >= 0);
154 Preconditions.checkArgument(size > 0);
155
156 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
157 CriteriaQuery<VesselFeatures> query = builder.createQuery(VesselFeatures.class);
158 Root<VesselFeatures> root = query.from(VesselFeatures.class);
159 query.select(root);
160
161
162 ParameterExpression<Integer> vesselIdParam = builder.parameter(Integer.class);
163 query.where(builder.equal(root.get(VesselFeatures.Fields.VESSEL).get(Vessel.Fields.ID), vesselIdParam));
164
165
166 addSorting(query, builder, root, sortAttribute, sortDirection);
167
168 TypedQuery<VesselFeatures> q = getEntityManager().createQuery(query)
169 .setParameter(vesselIdParam, vesselId)
170 .setFirstResult(offset)
171 .setMaxResults(size);
172 List<VesselFeatures> result = q.getResultList();
173 return result.stream().map(this::toVesselFeaturesVO).collect(Collectors.toList());
174 }
175
176 @Override
177 public List<VesselRegistrationVO> getRegistrationsByVesselId(int vesselId, int offset, int size, String sortAttribute, SortDirection sortDirection) {
178 Preconditions.checkArgument(offset >= 0);
179 Preconditions.checkArgument(size > 0);
180
181 CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
182 CriteriaQuery<VesselRegistrationPeriod> query = builder.createQuery(VesselRegistrationPeriod.class);
183 Root<VesselRegistrationPeriod> root = query.from(VesselRegistrationPeriod.class);
184 query.select(root);
185
186
187 ParameterExpression<Integer> vesselIdParam = builder.parameter(Integer.class);
188 query.where(builder.equal(root.get(VesselRegistrationPeriod.Fields.VESSEL).get(Vessel.Fields.ID), vesselIdParam));
189
190
191 addSorting(query, builder, root, sortAttribute, sortDirection);
192
193 TypedQuery<VesselRegistrationPeriod> q = getEntityManager().createQuery(query)
194 .setParameter(vesselIdParam, vesselId)
195 .setFirstResult(offset)
196 .setMaxResults(size);
197 List<VesselRegistrationPeriod> result = q.getResultList();
198 return result.stream().map(this::toVesselRegistrationVO).collect(Collectors.toList());
199 }
200
201 @Override
202 public VesselVOf="../../../../../net/sumaris/core/vo/data/VesselVO.html#VesselVO">VesselVO save(VesselVO vessel, boolean checkUpdateDate) {
203 Preconditions.checkNotNull(vessel);
204
205 Vessel vesselEntity = null;
206 if (vessel.getId() != null) {
207 vesselEntity = get(Vessel.class, vessel.getId());
208 }
209 boolean isNew = vesselEntity == null;
210
211 if (isNew) {
212 vesselEntity = new Vessel();
213 }
214
215 if (!isNew) {
216
217 if (checkUpdateDate) {
218
219 checkUpdateDateForUpdate(vessel, vesselEntity);
220 }
221
222
223 lockForUpdate(vesselEntity);
224 }
225
226
227 vesselVOToEntity(vessel, vesselEntity, true);
228
229 Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
230 if (isNew) {
231
232 vesselEntity.setCreationDate(newUpdateDate);
233 vessel.setCreationDate(newUpdateDate);
234 }
235
236 vesselEntity.setUpdateDate(newUpdateDate);
237 vessel.setUpdateDate(newUpdateDate);
238
239
240 VesselFeaturesVO features = vessel.getFeatures();
241 if (features != null) {
242 if (features.getId() == null) {
243
244 VesselFeaturess.html#VesselFeatures">VesselFeatures featuresEntity = new VesselFeatures();
245 vesselFeaturesVOToEntity(features, featuresEntity, false);
246 featuresEntity.setCreationDate(newUpdateDate);
247 featuresEntity.setUpdateDate(newUpdateDate);
248
249 featuresEntity.setVessel(vesselEntity);
250
251 getEntityManager().persist(featuresEntity);
252
253 features.setId(featuresEntity.getId());
254 } else {
255
256 VesselFeatures featuresEntity = get(VesselFeatures.class, features.getId());
257 lockForUpdate(featuresEntity);
258 vesselFeaturesVOToEntity(features, featuresEntity, true);
259 featuresEntity.setUpdateDate(newUpdateDate);
260
261 getEntityManager().merge(featuresEntity);
262 }
263
264 features.setUpdateDate(newUpdateDate);
265 }
266
267
268 VesselRegistrationVO registration = vessel.getRegistration();
269 if (registration != null) {
270 if (registration.getId() == null) {
271
272 VesselRegistrationPeriodiod.html#VesselRegistrationPeriod">VesselRegistrationPeriod periodEntity = new VesselRegistrationPeriod();
273 vesselRegistrationPeriodVOToEntity(registration, periodEntity, false);
274
275 periodEntity.setVessel(vesselEntity);
276
277 getEntityManager().persist(periodEntity);
278
279 vessel.getRegistration().setId(periodEntity.getId());
280 } else {
281
282 VesselRegistrationPeriod registrationEntity = get(VesselRegistrationPeriod.class, registration.getId());
283 lockForUpdate(registrationEntity);
284 vesselRegistrationPeriodVOToEntity(registration, registrationEntity, true);
285
286 getEntityManager().merge(registrationEntity);
287 }
288 }
289
290
291 if (isNew) {
292 getEntityManager().persist(vesselEntity);
293 vessel.setId(vesselEntity.getId());
294 } else {
295 getEntityManager().merge(vesselEntity);
296 }
297
298 getEntityManager().flush();
299 getEntityManager().clear();
300
301 return vessel;
302 }
303
304 @Override
305 public void delete(int id) {
306
307
308 VesselFeatures entity = get(VesselFeatures.class, id);
309
310 boolean deleteParentVessel = CollectionUtils.size(entity.getVessel().getVesselFeatures()) == 1;
311
312
313 if (deleteParentVessel) {
314
315 log.debug(String.format("Deleting vessel {id=%s}...", entity.getVessel().getId()));
316 delete(Vessel.class, entity.getVessel().getId());
317 } else {
318
319 log.debug(String.format("Deleting vessel features {id=%s}...", id));
320 delete(VesselFeatures.class, id);
321 }
322 }
323
324
325
326 private <R> TypedQuery<R> createVesselQuery(CriteriaBuilder builder, CriteriaQuery<R> query,
327 Root<Vessel> vesselRoot,
328 Join<Vessel, VesselFeatures> featuresJoin,
329 Join<Vessel, VesselRegistrationPeriod> vrpJoin,
330 VesselFilterVO filter) {
331
332 if (filter != null) {
333
334 ParameterExpression<Date> dateParam = builder.parameter(Date.class);
335 ParameterExpression<Integer> vesselIdParam = builder.parameter(Integer.class);
336 ParameterExpression<Integer> vesselFeaturesIdParam = builder.parameter(Integer.class);
337 ParameterExpression<String> searchNameParam = builder.parameter(String.class);
338 ParameterExpression<String> searchExteriorMarkingParam = builder.parameter(String.class);
339 ParameterExpression<String> searchRegistrationCodeParam = builder.parameter(String.class);
340 ParameterExpression<Boolean> hasStatusIdsParam = builder.parameter(Boolean.class);
341 ParameterExpression<Collection> statusIdsParam = builder.parameter(Collection.class);
342
343 query.where(builder.and(
344
345 builder.or(
346 builder.and(
347
348 builder.isNull(dateParam),
349 builder.isNull(featuresJoin.get(VesselFeatures.Fields.END_DATE)),
350 builder.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE))
351 ),
352 builder.and(
353 builder.isNotNull(dateParam),
354 builder.and(
355 builder.or(
356 builder.isNull(featuresJoin.get(VesselFeatures.Fields.END_DATE)),
357 builder.greaterThan(featuresJoin.get(VesselFeatures.Fields.END_DATE), dateParam)
358 ),
359 builder.lessThan(featuresJoin.get(VesselFeatures.Fields.START_DATE), dateParam)
360 ),
361 builder.and(
362 builder.or(
363 builder.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE)),
364 builder.greaterThan(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE), dateParam)
365 ),
366 builder.lessThan(vrpJoin.get(VesselRegistrationPeriod.Fields.START_DATE), dateParam)
367 )
368 )
369 ),
370
371
372 builder.or(
373 builder.isNull(vesselFeaturesIdParam),
374 builder.equal(featuresJoin.get(VesselFeatures.Fields.ID), vesselFeaturesIdParam)
375 ),
376
377
378 builder.or(
379 builder.isNull(vesselIdParam),
380 builder.equal(vesselRoot.get(Vessel.Fields.ID), vesselIdParam))
381 ),
382
383
384 builder.or(
385 builder.isNull(searchNameParam),
386 builder.like(builder.lower(featuresJoin.get(VesselFeatures.Fields.NAME)), builder.lower(searchNameParam)),
387 builder.like(builder.lower(featuresJoin.get(VesselFeatures.Fields.EXTERIOR_MARKING)), builder.lower(searchExteriorMarkingParam)),
388 builder.like(builder.lower(vrpJoin.get(VesselRegistrationPeriod.Fields.REGISTRATION_CODE)), builder.lower(searchRegistrationCodeParam))
389 ),
390
391
392 builder.or(
393 builder.isFalse(hasStatusIdsParam),
394 builder.in(vesselRoot.get(Vessel.Fields.STATUS).get(Status.Fields.ID)).value(statusIdsParam)
395 )
396 );
397
398 String searchText = StringUtils.trimToNull(filter.getSearchText());
399 String searchTextAsPrefix = null;
400 if (StringUtils.isNotBlank(searchText)) {
401 searchTextAsPrefix = (searchText + "*");
402 searchTextAsPrefix = searchTextAsPrefix.replaceAll("[*]+", "*");
403 searchTextAsPrefix = searchTextAsPrefix.replaceAll("[%]", "\\%");
404 searchTextAsPrefix = searchTextAsPrefix.replaceAll("[*]", "%");
405 }
406 String searchTextAnyMatch = StringUtils.isNotBlank(searchTextAsPrefix) ? ("%" + searchTextAsPrefix) : null;
407
408 List<Integer> statusIds = CollectionUtils.isEmpty(filter.getStatusIds())
409 ? null
410 : filter.getStatusIds();
411
412 return getEntityManager().createQuery(query)
413 .setParameter(dateParam, filter.getDate())
414 .setParameter(vesselFeaturesIdParam, filter.getVesselFeaturesId())
415 .setParameter(vesselIdParam, filter.getVesselId())
416 .setParameter(searchExteriorMarkingParam, searchTextAsPrefix)
417 .setParameter(searchRegistrationCodeParam, searchTextAsPrefix)
418 .setParameter(searchNameParam, searchTextAnyMatch)
419 .setParameter(hasStatusIdsParam, CollectionUtils.isNotEmpty(statusIds))
420 .setParameter(statusIdsParam, statusIds);
421
422 } else {
423
424
425 query.where(
426 builder.and(
427 builder.isNull(featuresJoin.get(VesselFeatures.Fields.END_DATE)),
428 builder.isNull(vrpJoin.get(VesselRegistrationPeriod.Fields.END_DATE))
429 )
430 );
431
432 return getEntityManager().createQuery(query);
433
434 }
435 }
436
437 private List<VesselVO> toVesselVOs(List<VesselResult> source) {
438 return source.stream()
439 .map(this::toVesselVO)
440 .filter(Objects::nonNull)
441 .collect(Collectors.toList());
442 }
443
444 private VesselVO toVesselVO(VesselResult source) {
445 if (source == null)
446 return null;
447
448 VesselVOsselVO.html#VesselVO">VesselVO target = new VesselVO();
449 Beans.copyProperties(source.getVessel(), target);
450
451
452 target.setStatusId(source.getVessel().getStatus().getId());
453
454
455 ReferentialVO vesselType = referentialDao.toReferentialVO(source.getVessel().getVesselType());
456 target.setVesselType(vesselType);
457
458
459 DepartmentVO recorderDepartment = referentialDao.toTypedVO(source.getVessel().getRecorderDepartment(), DepartmentVO.class).orElse(null);
460 target.setRecorderDepartment(recorderDepartment);
461
462
463 target.setFeatures(toVesselFeaturesVO(source.getVesselFeatures()));
464
465
466 target.setRegistration(toVesselRegistrationVO(source.getVesselRegistrationPeriod()));
467
468 return target;
469 }
470
471 private VesselFeaturesVO toVesselFeaturesVO(VesselFeatures source) {
472 if (source == null) return null;
473
474 VesselFeaturesVOuresVO.html#VesselFeaturesVO">VesselFeaturesVO target = new VesselFeaturesVO();
475
476 Beans.copyProperties(source, target);
477
478
479 if (source.getLengthOverAll() != null) {
480 target.setLengthOverAll(source.getLengthOverAll().doubleValue() / 100);
481 }
482
483 if (source.getGrossTonnageGrt() != null) {
484 target.setGrossTonnageGrt(source.getGrossTonnageGrt().doubleValue() / 100);
485 }
486 if (source.getGrossTonnageGt() != null) {
487 target.setGrossTonnageGt(source.getGrossTonnageGt().doubleValue() / 100);
488 }
489
490 target.setQualityFlagId(source.getQualityFlag().getId());
491
492
493 LocationVO basePortLocation = locationDao.toLocationVO(source.getBasePortLocation());
494 target.setBasePortLocation(basePortLocation);
495
496
497 DepartmentVO recorderDepartment = referentialDao.toTypedVO(source.getRecorderDepartment(), DepartmentVO.class).orElse(null);
498 target.setRecorderDepartment(recorderDepartment);
499
500 return target;
501 }
502
503 private VesselRegistrationVO toVesselRegistrationVO(VesselRegistrationPeriod source) {
504 if (source == null)
505 return null;
506
507 VesselRegistrationVOtionVO.html#VesselRegistrationVO">VesselRegistrationVO target = new VesselRegistrationVO();
508
509 Beans.copyProperties(source, target);
510
511
512 LocationVO registrationLocation = locationDao.toLocationVO(source.getRegistrationLocation());
513 target.setRegistrationLocation(registrationLocation);
514
515 return target;
516 }
517
518 private void vesselVOToEntity(VesselVO source, Vessel target, boolean copyIfNull) {
519
520 copyDataProperties(source, target, copyIfNull);
521
522
523 copyRecorderPerson(source, target, copyIfNull);
524
525
526 if (copyIfNull || source.getVesselType() != null) {
527 if (source.getVesselType() == null) {
528 target.setVesselType(null);
529 } else {
530 target.setVesselType(load(VesselType.class, source.getVesselType().getId()));
531 }
532 }
533
534
535 if (copyIfNull || source.getStatusId() != null) {
536 if (source.getStatusId() == null) {
537 target.setStatus(null);
538 } else {
539 target.setStatus(load(Status.class, source.getStatusId()));
540 }
541 }
542
543
544 if (copyIfNull && target.getProgram() == null) {
545 target.setProgram(load(Program.class, ProgramEnum.SIH.getId()));
546 }
547 }
548
549 private void vesselFeaturesVOToEntity(VesselFeaturesVO source, VesselFeatures target, boolean copyIfNull) {
550
551 copyDataProperties(source, target, copyIfNull);
552
553
554 copyRecorderPerson(source, target, copyIfNull);
555
556
557 if (source.getLengthOverAll() != null) {
558 target.setLengthOverAll((int) (source.getLengthOverAll() * 100));
559 }
560
561 if (source.getGrossTonnageGrt() != null) {
562 target.setGrossTonnageGrt((int) (source.getGrossTonnageGrt() * 100));
563 }
564 if (source.getGrossTonnageGt() != null) {
565 target.setGrossTonnageGt((int) (source.getGrossTonnageGt() * 100));
566 }
567
568
569 if (copyIfNull || source.getBasePortLocation() != null) {
570 if (source.getBasePortLocation() == null || source.getBasePortLocation().getId() == null) {
571 target.setBasePortLocation(null);
572 } else {
573 target.setBasePortLocation(load(Location.class, source.getBasePortLocation().getId()));
574 }
575 }
576
577
578 if (copyIfNull || source.getQualityFlagId() != null) {
579 if (source.getQualityFlagId() == null) {
580 target.setQualityFlag(null);
581 } else {
582 target.setQualityFlag(load(QualityFlag.class, source.getQualityFlagId()));
583 }
584 }
585 else if (copyIfNull) {
586
587 target.setQualityFlag(load(QualityFlag.class, QualityFlagEnum.NOT_QUALIFED.getId()));
588 }
589 }
590
591 private void vesselRegistrationPeriodVOToEntity(VesselRegistrationVO source, VesselRegistrationPeriod target, boolean copyIfNull) {
592
593
594 if (copyIfNull || source.getStartDate() != null) {
595 target.setStartDate(source.getStartDate());
596 }
597
598
599 if (copyIfNull || source.getEndDate() != null) {
600 target.setEndDate(source.getEndDate());
601 }
602
603
604 if (copyIfNull || source.getRegistrationCode() != null) {
605 target.setRegistrationCode(source.getRegistrationCode());
606 }
607
608
609 if (copyIfNull || source.getRegistrationLocation() != null) {
610 if (source.getRegistrationLocation() == null || source.getRegistrationLocation().getId() == null) {
611 target.setRegistrationLocation(null);
612 } else {
613 target.setRegistrationLocation(get(Location.class, source.getRegistrationLocation().getId()));
614 }
615 }
616
617
618 if (target.getQualityFlag() == null) {
619 target.setQualityFlag(load(QualityFlag.class, SumarisConfiguration.getInstance().getDefaultQualityFlagId()));
620 }
621
622
623 if (target.getRankOrder() == null) {
624 target.setRankOrder(1);
625 }
626 }
627
628 @Data
629 @AllArgsConstructor
630 private static class VesselResult {
631 private Vessel vessel;
632 private VesselFeatures vesselFeatures;
633 private VesselRegistrationPeriod vesselRegistrationPeriod;
634 }
635 }