View Javadoc
1   package net.sumaris.core.dao.referential;
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.Maps;
28  import net.sumaris.core.dao.technical.SortDirection;
29  import net.sumaris.core.dao.technical.hibernate.HibernateDaoSupport;
30  import net.sumaris.core.exception.SumarisTechnicalException;
31  import net.sumaris.core.model.administration.programStrategy.*;
32  import net.sumaris.core.model.administration.user.Department;
33  import net.sumaris.core.model.referential.*;
34  import net.sumaris.core.model.referential.gear.Gear;
35  import net.sumaris.core.model.referential.gear.GearClassification;
36  import net.sumaris.core.model.referential.grouping.Grouping;
37  import net.sumaris.core.model.referential.grouping.GroupingClassification;
38  import net.sumaris.core.model.referential.grouping.GroupingLevel;
39  import net.sumaris.core.model.referential.location.Location;
40  import net.sumaris.core.model.referential.location.LocationClassification;
41  import net.sumaris.core.model.referential.location.LocationLevel;
42  import net.sumaris.core.model.referential.metier.Metier;
43  import net.sumaris.core.model.referential.pmfm.*;
44  import net.sumaris.core.model.referential.taxon.TaxonGroup;
45  import net.sumaris.core.model.referential.taxon.TaxonGroupType;
46  import net.sumaris.core.model.referential.taxon.TaxonName;
47  import net.sumaris.core.model.referential.taxon.TaxonomicLevel;
48  import net.sumaris.core.model.referential.transcribing.TranscribingItem;
49  import net.sumaris.core.model.technical.configuration.Software;
50  import net.sumaris.core.model.technical.extraction.ExtractionProduct;
51  import net.sumaris.core.model.technical.extraction.ExtractionProductTable;
52  import net.sumaris.core.util.Beans;
53  import net.sumaris.core.vo.filter.ReferentialFilterVO;
54  import net.sumaris.core.vo.referential.IReferentialVO;
55  import net.sumaris.core.vo.referential.ReferentialTypeVO;
56  import net.sumaris.core.vo.referential.ReferentialVO;
57  import org.apache.commons.lang3.ArrayUtils;
58  import org.apache.commons.lang3.StringUtils;
59  import org.nuiton.i18n.I18n;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  import org.springframework.beans.BeanUtils;
63  import org.springframework.dao.DataRetrievalFailureException;
64  import org.springframework.stereotype.Repository;
65  
66  import javax.persistence.EntityManager;
67  import javax.persistence.TypedQuery;
68  import javax.persistence.criteria.*;
69  import java.beans.PropertyDescriptor;
70  import java.io.Serializable;
71  import java.sql.Timestamp;
72  import java.util.*;
73  import java.util.stream.Collectors;
74  import java.util.stream.Stream;
75  
76  @Repository("referentialDao")
77  public class ReferentialDaoImpl extends HibernateDaoSupport implements ReferentialDao {
78  
79      /** Logger. */
80      private static final Logger log =
81              LoggerFactory.getLogger(ReferentialDaoImpl.class);
82  
83      private static Map<String, Class<? extends IReferentialEntity>> entityClassMap = Maps.uniqueIndex(
84              ImmutableList.of(
85                      Department.class,
86                      Location.class,
87                      LocationLevel.class,
88                      LocationClassification.class,
89                      Gear.class,
90                      GearClassification.class,
91                      UserProfile.class,
92                      SaleType.class,
93                      VesselType.class,
94                      // Taxon group
95                      TaxonGroupType.class,
96                      TaxonGroup.class,
97                      // Taxon
98                      TaxonomicLevel.class,
99                      TaxonName.class,
100                     // Métier
101                     Metier.class,
102                     // Pmfm
103                     Parameter.class,
104                     Pmfm.class,
105                     Matrix.class,
106                     Fraction.class,
107                     QualitativeValue.class,
108                     // Quality
109                     QualityFlag.class,
110                     // Program/strategy
111                     Program.class,
112                     Strategy.class,
113                     AcquisitionLevel.class,
114                     // Transcribing
115                     TranscribingItem.class,
116                     // Grouping
117                     GroupingClassification.class,
118                     GroupingLevel.class,
119                     Grouping.class,
120                     // Product
121                     ExtractionProduct.class,
122                     ExtractionProductTable.class,
123                     // Software
124                     Software.class,
125                     // Program
126                     ProgramPrivilege.class
127             ), Class::getSimpleName);
128 
129     private Map<String, PropertyDescriptor> levelPropertyNameMap = initLevelPropertyNameMap();
130 
131     static {
132         I18n.n("sumaris.persistence.table.location");
133         I18n.n("sumaris.persistence.table.locationLevel");
134         I18n.n("sumaris.persistence.table.gear");
135         I18n.n("sumaris.persistence.table.gearLevel");
136         I18n.n("sumaris.persistence.table.parameter");
137         I18n.n("sumaris.persistence.table.userProfile");
138         I18n.n("sumaris.persistence.table.saleType");
139         I18n.n("sumaris.persistence.table.taxonGroup");
140         I18n.n("sumaris.persistence.table.taxonGroupType");
141         I18n.n("sumaris.persistence.table.taxonomicLevel");
142         I18n.n("sumaris.persistence.table.referenceTaxon");
143         I18n.n("sumaris.persistence.table.taxonName");
144         I18n.n("sumaris.persistence.table.metier");
145         I18n.n("sumaris.persistence.table.parameter");
146         I18n.n("sumaris.persistence.table.pmfm");
147         I18n.n("sumaris.persistence.table.matrix");
148         I18n.n("sumaris.persistence.table.fraction");
149         I18n.n("sumaris.persistence.table.qualitativeValue");
150         I18n.n("sumaris.persistence.table.program");
151         I18n.n("sumaris.persistence.table.acquisitionLevel");
152         I18n.n("sumaris.persistence.table.transcribingItem");
153         I18n.n("sumaris.persistence.table.groupingClassification");
154         I18n.n("sumaris.persistence.table.groupingLevel");
155         I18n.n("sumaris.persistence.table.grouping");
156         I18n.n("sumaris.persistence.table.extractionProduct");
157         I18n.n("sumaris.persistence.table.extractionProductTable");
158     }
159 
160     protected static Map<String, PropertyDescriptor> initLevelPropertyNameMap() {
161         Map<String, PropertyDescriptor> result = new HashMap<>();
162 
163         // Detect level properties, by name
164         entityClassMap.values().stream().forEach((clazz) -> {
165             PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(clazz);
166             for (PropertyDescriptor pd: pds) {
167                 if (pd.getName().matches("^.*[Ll]evel([A−Z].*)?$")) {
168                     result.put(clazz.getSimpleName(), pd);
169                     break;
170                 }
171             }
172         });
173 
174         // Other level (not having "level" in id)
175         result.put(Fraction.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Fraction.class, Fraction.Fields.MATRIX));
176         result.put(QualitativeValue.class.getSimpleName(), BeanUtils.getPropertyDescriptor(QualitativeValue.class, QualitativeValue.Fields.PARAMETER));
177         result.put(TaxonGroup.class.getSimpleName(), BeanUtils.getPropertyDescriptor(TaxonGroup.class, TaxonGroup.Fields.TAXON_GROUP_TYPE));
178         result.put(TaxonName.class.getSimpleName(), BeanUtils.getPropertyDescriptor(TaxonName.class, TaxonName.Fields.TAXONOMIC_LEVEL));
179         result.put(Strategy.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Strategy.class, Strategy.Fields.PROGRAM));
180         result.put(Metier.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Metier.class, Metier.Fields.GEAR));
181         result.put(GroupingLevel.class.getSimpleName(), BeanUtils.getPropertyDescriptor(GroupingLevel.class, GroupingLevel.Fields.GROUPING_CLASSIFICATION));
182         result.put(Grouping.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Grouping.class, Grouping.Fields.GROUPING_LEVEL));
183         result.put(ExtractionProductTable.class.getSimpleName(), BeanUtils.getPropertyDescriptor(ExtractionProductTable.class, ExtractionProductTable.Fields.PRODUCT));
184         result.put(LocationLevel.class.getSimpleName(), BeanUtils.getPropertyDescriptor(LocationLevel.class, LocationLevel.Fields.LOCATION_CLASSIFICATION));
185         result.put(Gear.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Gear.class, Gear.Fields.GEAR_CLASSIFICATION));
186         result.put(Program.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Program.class, Program.Fields.GEAR_CLASSIFICATION));
187         result.put(Program.class.getSimpleName(), BeanUtils.getPropertyDescriptor(Program.class, Program.Fields.TAXON_GROUP_TYPE));
188 
189         return result;
190     }
191 
192 
193     @Override
194     public List<ReferentialVO> findByFilter(final String entityName,
195                                             ReferentialFilterVO filter,
196                                             int offset,
197                                             int size,
198                                             String sortAttribute,
199                                             SortDirection sortDirection) {
200         Preconditions.checkNotNull(entityName, "Missing entityName argument");
201         Preconditions.checkNotNull(filter);
202 
203         // Get entity class from entityName
204         Class<? extends IReferentialEntity> entityClass = getEntityClass(entityName);
205 
206         return createFindQuery(entityClass,
207                     filter.getLevelId(),
208                     filter.getLevelIds(),
209                     StringUtils.trimToNull(filter.getSearchText()),
210                     StringUtils.trimToNull(filter.getSearchAttribute()),
211                     filter.getStatusIds(),
212                     sortAttribute,
213                     sortDirection, null)
214                 .setFirstResult(offset)
215                 .setMaxResults(size)
216                 .getResultStream()
217                 .map(s -> toReferentialVO(entityName, s))
218                 .filter(Objects::nonNull)
219                 .collect(Collectors.toList());
220     }
221 
222     @Override
223     public ReferentialVO findByUniqueLabel(String entityName, String label) {
224         Preconditions.checkNotNull(entityName, "Missing entityName argument");
225         Preconditions.checkNotNull(label);
226 
227         // Get entity class from entityName
228         Class<? extends IReferentialEntity> entityClass = getEntityClass(entityName);
229 
230         return toReferentialVO(entityName, createFindByUniqueLabelQuery(entityClass, label).getSingleResult());
231     }
232 
233     @Override
234     public List<ReferentialTypeVO> getAllTypes() {
235 
236         return entityClassMap.keySet().stream()
237                 .map(this::getTypeByEntityName)
238                 .collect(Collectors.toList());
239     }
240 
241     @Override
242     public List<ReferentialVO> getAllLevels(final String entityName) {
243         Preconditions.checkNotNull(entityName, "Missing entityName argument");
244 
245         PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(entityName);
246         if (levelDescriptor == null) {
247             return ImmutableList.of();
248         }
249 
250         String levelEntityName = levelDescriptor.getPropertyType().getSimpleName();
251         return findByFilter(levelEntityName, new ReferentialFilterVO(), 0, 100,
252                 IItemReferentialEntity.Fields.NAME, SortDirection.ASC);
253     }
254 
255     @Override
256     public ReferentialVO getLevelById(String entityName, int levelId) {
257         Preconditions.checkNotNull(entityName, "Missing entityName argument");
258 
259         PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(entityName);
260         if (levelDescriptor == null) {
261             throw new DataRetrievalFailureException("Unable to find level with id=" + levelId + " for entityName=" + entityName);
262         }
263 
264         Class<?> levelClass = levelDescriptor.getPropertyType();
265         if (!IReferentialEntity.class.isAssignableFrom(levelClass)){
266             throw new DataRetrievalFailureException("Unable to convert class=" + levelClass.getName() + " to a referential bean");
267         }
268         return toReferentialVO(levelClass.getSimpleName(), (IReferentialEntity)entityManager.find(levelClass, levelId));
269     }
270 
271     @Override
272     public <T extends IReferentialEntity> ReferentialVO toReferentialVO(T source) {
273         return toReferentialVO(getEntityName(source), source);
274     }
275 
276     @Override
277     public <T extends IReferentialVO, S extends IReferentialEntity> Optional<T> toTypedVO(S source, Class<T> targetClazz) {
278 
279         if (source == null)
280             return Optional.empty();
281 
282         try {
283             T target = targetClazz.newInstance();
284             Beans.copyProperties(source, target);
285             return Optional.of(target);
286         }catch(IllegalAccessException | InstantiationException e) {
287             throw new SumarisTechnicalException(e.getMessage(), e);
288         }
289     }
290 
291     @Override
292     public void delete(final String entityName, int id) {
293 
294         // Get the entity class
295         Class<? extends IReferentialEntity> entityClass = getEntityClass(entityName);
296 
297         log.debug(String.format("Deleting %s {id=%s}...", entityName, id));
298         delete(entityClass, id);
299     }
300 
301     @Override
302     public Long count(String entityName) {
303         Preconditions.checkNotNull(entityName, "Missing entityName argument");
304 
305         // Get entity class from entityName
306         Class<? extends IReferentialEntity> entityClass = getEntityClass(entityName);
307 
308         CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
309         CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
310         criteriaQuery.select(builder.count(criteriaQuery.from(entityClass)));
311 
312         return getEntityManager().createQuery(criteriaQuery).getSingleResult();
313     }
314 
315     @Override
316     public Long countByLevelId(String entityName, Integer... levelIds) {
317         Preconditions.checkNotNull(entityName, "Missing entityName argument");
318 
319         // Get entity class from entityName
320         Class<? extends IReferentialEntity> entityClass = getEntityClass(entityName);
321 
322         CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
323         CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
324 
325         Root<? extends IReferentialEntity> entityRoot = criteriaQuery.from(entityClass);
326         criteriaQuery.select(builder.count(entityRoot));
327 
328         // Level ids
329         Predicate levelClause = null;
330         ParameterExpression<Collection> levelIdsParam = builder.parameter(Collection.class);
331         PropertyDescriptor pd = levelPropertyNameMap.get(entityClass.getSimpleName());
332         if (pd != null && ArrayUtils.isNotEmpty(levelIds)) {
333             levelClause = builder.in(entityRoot.get(pd.getName()).get(IReferentialEntity.Fields.ID)).value(levelIdsParam);
334             criteriaQuery.where(levelClause);
335         }
336 
337         TypedQuery<Long> query = getEntityManager().createQuery(criteriaQuery);
338         if (levelClause != null) {
339             query.setParameter(levelIdsParam, ImmutableList.copyOf(levelIds));
340         }
341 
342         return query.getSingleResult();
343     }
344 
345     @Override
346     public ReferentialVOl/ReferentialVO.html#ReferentialVO">ReferentialVO save(final ReferentialVO source) {
347         Preconditions.checkNotNull(source);
348 
349         // Get the entity class
350         Class<? extends IReferentialEntity> entityClass = getEntityClass(source.getEntityName());
351 
352         EntityManager entityManager = getEntityManager();
353 
354         IReferentialEntity entity = null;
355         if (source.getId() != null) {
356             entity = get(entityClass, source.getId());
357         }
358         boolean isNew = (entity == null);
359         if (isNew) {
360             try {
361                 entity = entityClass.newInstance();
362             } catch (IllegalAccessException |InstantiationException e) {
363                 throw new IllegalArgumentException(String.format("Entity with name [%s] has no empty constructor", source.getEntityName()));
364             }
365         }
366 
367         if (!isNew) {
368             // Check update date
369             checkUpdateDateForUpdate(source, entity);
370 
371             // Lock entityName
372             // TODO
373         }
374 
375         // VO -> Entity
376         referentialVOToEntity(source, entity, true);
377 
378         // Update update_dt
379         Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
380         entity.setUpdateDate(newUpdateDate);
381 
382         // Save entity
383         if (isNew) {
384             // Force creation date
385             entity.setCreationDate(newUpdateDate);
386             source.setCreationDate(newUpdateDate);
387 
388             entityManager.persist(entity);
389             source.setId(entity.getId());
390         } else {
391             entityManager.merge(entity);
392         }
393 
394         source.setUpdateDate(newUpdateDate);
395 
396         entityManager.flush();
397         entityManager.clear();
398 
399         return source;
400     }
401 
402     /* -- protected methods -- */
403 
404     protected ReferentialTypeVO getTypeByEntityName(final String entityName) {
405 
406         ReferentialTypeVOferentialTypeVO.html#ReferentialTypeVO">ReferentialTypeVO type = new ReferentialTypeVO();
407         type.setId(entityName);
408 
409         PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(entityName);
410         if (levelDescriptor != null) {
411             type.setLevel(levelDescriptor.getPropertyType().getSimpleName());
412         }
413         return type;
414     }
415 
416     protected <T extends IReferentialEntity> ReferentialVO toReferentialVO(final String entityName, T source) {
417         Preconditions.checkNotNull(entityName);
418         Preconditions.checkNotNull(source);
419 
420         ReferentialVOReferentialVO.html#ReferentialVO">ReferentialVO target = new ReferentialVO();
421 
422         Beans.copyProperties(source, target);
423 
424         // Status
425         target.setStatusId(source.getStatus().getId());
426 
427         // Level
428         PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(entityName);
429         if (levelDescriptor != null) {
430             try {
431                 IReferentialEntity/../net/sumaris/core/model/referential/IReferentialEntity.html#IReferentialEntity">IReferentialEntity level = (IReferentialEntity)levelDescriptor.getReadMethod().invoke(source, new Object[0]);
432                 if (level != null) {
433                     target.setLevelId(level.getId());
434                 }
435             } catch(Exception e) {
436                 throw new SumarisTechnicalException(e);
437             }
438         }
439 
440         // EntityName (as metadata)
441         target.setEntityName(entityName);
442 
443         return target;
444     }
445 
446     protected List<ReferentialVO> toReferentialVOs(List<? extends IReferentialEntity> source) {
447         return toReferentialVOs(source.stream());
448     }
449 
450     protected List<ReferentialVO> toReferentialVOs(Stream<? extends IReferentialEntity> source) {
451         return source
452                 .map(this::toReferentialVO)
453                 .filter(Objects::nonNull)
454                 .collect(Collectors.toList());
455     }
456 
457     public <T> TypedQuery<T> createFindQuery(Class<T> entityClass,
458                                              Integer levelId,
459                                              Integer[] levelIds,
460                                              String searchText,
461                                              String searchAttribute,
462                                              Integer[] statusIds,
463                                              String sortAttribute,
464                                              SortDirection sortDirection,
465                                              QueryVisitor<T> queryVisitor) {
466         CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
467         CriteriaQuery<T> query = builder.createQuery(entityClass);
468         Root<T> entityRoot = query.from(entityClass);
469         query.select(entityRoot).distinct(true);
470 
471         // Level Ids
472         Predicate levelClause = null;
473         ParameterExpression<Collection> levelIdsParam = null;
474         if (ArrayUtils.isNotEmpty(levelIds)) {
475             if (levelIds.length == 1) {
476                 levelId = levelIds[0];
477                 levelIds = null;
478             } else {
479                 levelId = null;
480                 levelIdsParam = builder.parameter(Collection.class);
481                 PropertyDescriptor pd = levelPropertyNameMap.get(entityClass.getSimpleName());
482                 if (pd != null) {
483                     levelClause = builder.in(entityRoot.get(pd.getName()).get(IReferentialEntity.Fields.ID)).value(levelIdsParam);
484                 }
485                 else {
486                     log.warn(String.format("Trying to request  on level, but no level found for entity {%s}", entityClass.getSimpleName()));
487                 }
488             }
489         }
490         // Level Id
491         ParameterExpression<Integer> levelIdParam = null;
492         if (levelId != null) {
493             levelIdParam = builder.parameter(Integer.class);
494             PropertyDescriptor pd = levelPropertyNameMap.get(entityClass.getSimpleName());
495             if (pd != null) {
496                 levelClause = builder.equal(entityRoot.get(pd.getName()).get(IReferentialEntity.Fields.ID), levelIdParam);
497             }
498             else {
499                 log.warn(String.format("Trying to request  on level, but no level found for entity {%s}", entityClass.getSimpleName()));
500             }
501         }
502 
503         // Filter on text
504         ParameterExpression<String> searchAsPrefixParam = builder.parameter(String.class);
505         ParameterExpression<String> searchAnyMatchParam = builder.parameter(String.class);
506         Predicate searchTextClause = null;
507         if (searchText != null) {
508             // Search on the given search attribute, if exists
509             if (StringUtils.isNotBlank(searchAttribute) && BeanUtils.getPropertyDescriptor(entityClass, searchAttribute) != null) {
510                 searchTextClause = builder.or(
511                         builder.isNull(searchAnyMatchParam),
512                         builder.like(builder.upper(entityRoot.get(searchAttribute)), builder.upper(searchAsPrefixParam))
513                 );
514             }
515             else if (IItemReferentialEntity.class.isAssignableFrom(entityClass)) {
516                 // Search on label+name
517                 searchTextClause = builder.or(
518                         builder.isNull(searchAnyMatchParam),
519                         builder.like(builder.upper(entityRoot.get(IItemReferentialEntity.Fields.LABEL)), builder.upper(searchAsPrefixParam)),
520                         builder.like(builder.upper(entityRoot.get(IItemReferentialEntity.Fields.NAME)), builder.upper(searchAnyMatchParam))
521                 );
522             } else if (BeanUtils.getPropertyDescriptor(entityClass, IItemReferentialEntity.Fields.LABEL) != null) {
523                 // Search on label
524                 searchTextClause = builder.or(
525                         builder.isNull(searchAnyMatchParam),
526                         builder.like(builder.upper(entityRoot.get(IItemReferentialEntity.Fields.LABEL)), builder.upper(searchAsPrefixParam))
527                 );
528             } else if (BeanUtils.getPropertyDescriptor(entityClass, IItemReferentialEntity.Fields.NAME) != null) {
529                 // Search on name
530                 searchTextClause = builder.or(
531                         builder.isNull(searchAnyMatchParam),
532                         builder.like(builder.upper(entityRoot.get(IItemReferentialEntity.Fields.NAME)), builder.upper(searchAnyMatchParam))
533                 );
534             }
535         }
536 
537         // Filter on status
538         ParameterExpression<Collection> statusIdsParam = builder.parameter(Collection.class);
539         Predicate statusIdsClause = null;
540         if (ArrayUtils.isNotEmpty(statusIds)) {
541             statusIdsClause = builder.in(entityRoot.get(IItemReferentialEntity.Fields.STATUS).get(IItemReferentialEntity.Fields.ID)).value(statusIdsParam);
542         }
543 
544         // Compute where clause
545         Expression<Boolean> whereClause = null;
546         if (levelClause != null) {
547             whereClause = levelClause;
548         }
549         if (searchTextClause != null) {
550             whereClause = (whereClause == null) ? searchTextClause : builder.and(whereClause, searchTextClause);
551         }
552 
553         if (statusIdsClause != null) {
554             whereClause = (whereClause == null) ? statusIdsClause : builder.and(whereClause, statusIdsClause);
555         }
556 
557         // Delegate to visitor
558         if (queryVisitor != null) {
559             Expression<Boolean> additionnalWhere = queryVisitor.apply(query, entityRoot);
560             if (additionnalWhere != null) {
561                 whereClause = (whereClause == null) ? additionnalWhere : builder.and(whereClause, additionnalWhere);
562             }
563         }
564 
565         if (whereClause != null) {
566             query.where(whereClause);
567         }
568 
569         // Add sorting
570         if (StringUtils.isNotBlank(sortAttribute)) {
571 
572             // Convert level into the correct property name
573             if (IReferentialVO.Fields.LEVEL.equals(sortAttribute)) {
574                 PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(entityClass.getSimpleName());
575                 if (levelDescriptor != null) {
576                     sortAttribute = levelDescriptor.getName();
577                 }
578                 else {
579                     sortAttribute = null;
580                 }
581             }
582 
583             if (StringUtils.isNotBlank(sortAttribute)) {
584                 Expression<?> sortExpression = entityRoot.get(sortAttribute);
585                 query.orderBy(SortDirection.DESC.equals(sortDirection) ?
586                         builder.desc(sortExpression) :
587                         builder.asc(sortExpression)
588                 );
589             }
590         }
591 
592         TypedQuery<T> typedQuery = getEntityManager().createQuery(query);
593 
594         // Bind parameters
595         if (searchTextClause != null) {
596             String searchTextAsPrefix = null;
597             if (StringUtils.isNotBlank(searchText)) {
598                 searchTextAsPrefix = (searchText + "*") // add trailing escape char
599                     .replaceAll("[*]+", "*") // group escape chars
600                     .replaceAll("[%]", "\\%") // protected '%' chars
601                     .replaceAll("[*]", "%"); // replace asterix
602             }
603             String searchTextAnyMatch = StringUtils.isNotBlank(searchTextAsPrefix) ? ("%"+searchTextAsPrefix) : null;
604             typedQuery.setParameter(searchAsPrefixParam, searchTextAsPrefix);
605             typedQuery.setParameter(searchAnyMatchParam, searchTextAnyMatch);
606         }
607         if (levelClause != null) {
608             if (levelIds != null) {
609                 typedQuery.setParameter(levelIdsParam, ImmutableList.copyOf(levelIds));
610             }
611             else {
612                 typedQuery.setParameter(levelIdParam, levelId);
613             }
614         }
615         if (statusIdsClause != null) {
616             typedQuery.setParameter(statusIdsParam, ImmutableList.copyOf(statusIds));
617         }
618 
619         return typedQuery;
620     }
621 
622     private <T> TypedQuery<T> createFindByUniqueLabelQuery(Class<T> entityClass, String label) {
623         CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
624         CriteriaQuery<T> query = builder.createQuery(entityClass);
625         Root<T> tripRoot = query.from(entityClass);
626         query.select(tripRoot).distinct(true);
627 
628         // Filter on text
629         ParameterExpression<String> labelParam = builder.parameter(String.class);
630         query.where(builder.equal(tripRoot.get(IItemReferentialEntity.Fields.LABEL), labelParam));
631 
632         return getEntityManager().createQuery(query)
633                 .setParameter(labelParam, label);
634     }
635 
636     protected Class<? extends IReferentialEntity> getEntityClass(String entityName) {
637         Preconditions.checkNotNull(entityName);
638 
639         // Get entity class from entityName
640         Class<? extends IReferentialEntity> entityClass = entityClassMap.get(entityName);
641         if (entityClass == null)
642             throw new IllegalArgumentException(String.format("No entity with name [%s]", entityName));
643         return entityClass;
644     }
645 
646     protected String getTableName(String entityName) {
647 
648         return I18n.t("sumaris.persistence.table."+ entityName.substring(0,1).toLowerCase() + entityName.substring(1));
649     }
650 
651     protected <T extends IReferentialEntity> String getEntityName(T source) {
652         String classname = source.getClass().getSimpleName();
653         int index = classname.indexOf("$HibernateProxy");
654         if (index > 0) {
655             return classname.substring(0, index);
656         }
657         return classname;
658     }
659 
660     protected void referentialVOToEntity(final ReferentialVO source, IReferentialEntity target, boolean copyIfNull) {
661 
662         Beans.copyProperties(source, target);
663 
664         // Status
665         if (copyIfNull || source.getStatusId() != null) {
666             if (source.getStatusId() == null) {
667                 target.setStatus(null);
668             }
669             else {
670                 target.setStatus(load(Status.class, source.getStatusId()));
671             }
672         }
673 
674         // Level
675         Integer levelID = source.getLevelId();
676         if (copyIfNull || levelID != null) {
677             PropertyDescriptor levelDescriptor = levelPropertyNameMap.get(source.getEntityName());
678             if (levelDescriptor != null) {
679                 try {
680                     if (levelID == null) {
681                         levelDescriptor.getWriteMethod().invoke(target, new Object[]{null});
682                     }
683                     else {
684                         Object level = load(levelDescriptor.getPropertyType().asSubclass(Serializable.class), levelID);
685                         levelDescriptor.getWriteMethod().invoke(target, level);
686                     }
687                 } catch(Exception e) {
688                     throw new SumarisTechnicalException(e);
689                 }
690             }
691         }
692 
693     }
694 
695 }