Implement validation and hierarchical get
[policy/models.git] / models-dao / src / main / java / org / onap / policy / models / dao / impl / DefaultPfDao.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019-2020 Nordix Foundation.
4  *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.models.dao.impl;
23
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.persistence.EntityManager;
31 import javax.persistence.EntityManagerFactory;
32 import javax.persistence.Persistence;
33 import javax.persistence.TypedQuery;
34 import javax.ws.rs.core.Response;
35
36 import org.onap.policy.models.base.PfConcept;
37 import org.onap.policy.models.base.PfConceptKey;
38 import org.onap.policy.models.base.PfModelException;
39 import org.onap.policy.models.base.PfModelRuntimeException;
40 import org.onap.policy.models.base.PfReferenceKey;
41 import org.onap.policy.models.base.PfTimestampKey;
42 import org.onap.policy.models.base.PfUtils;
43 import org.onap.policy.models.dao.DaoParameters;
44 import org.onap.policy.models.dao.PfDao;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The Class DefaultPfDao is an JPA implementation of the {@link PfDao} class for Policy Framework concepts
50  * ({@link PfConcept}). It uses the default JPA implementation in the javax {@link Persistence} class.
51  */
52 public class DefaultPfDao implements PfDao {
53     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPfDao.class);
54
55     // @formatter:off
56     private static final String NAME           = "name";
57     private static final String VERSION        = "version";
58     private static final String TIMESTAMP      = "timeStamp";
59     private static final String PARENT_NAME    = "parentname";
60     private static final String PARENT_VERSION = "parentversion";
61     private static final String LOCAL_NAME     = "localname";
62
63     private static final String TABLE_TOKEN = "__TABLE__";
64
65     private static final String DELETE_FROM_TABLE = "DELETE FROM __TABLE__ c";
66
67     private static final String SELECT_FROM_TABLE = "SELECT c FROM __TABLE__ c";
68
69     private static final String WHERE      = " WHERE ";
70     private static final String AND        = " AND ";
71     private static final String ORDER      = " ORDER BY ";
72
73     private static final String NAME_FILTER            = "c.key.name = :name";
74     private static final String VERSION_FILTER         = "c.key.version = :version";
75     private static final String TIMESTAMP_FILTER       = "c.key.timeStamp = :timeStamp";
76     private static final String TIMESTAMP_START_FILTER = "c.key.timeStamp >= :startTime";
77     private static final String TIMESTAMP_END_FILTER   = "c.key.timeStamp <= :endTime";
78     private static final String PARENT_NAME_FILTER     = "c.key.parentKeyName = :parentname";
79     private static final String PARENT_VERSION_FILTER  = "c.key.parentKeyVersion = :parentversion";
80     private static final String LOCAL_NAME_FILTER      = "c.key.localName = :localname";
81
82     private static final String CLONE_ERR_MSG = "Could not clone object of class \"{}\"";
83
84     private static final String DELETE_BY_CONCEPT_KEY =
85             DELETE_FROM_TABLE + WHERE + NAME_FILTER + AND + VERSION_FILTER;
86
87     private static final String DELETE_BY_TIMESTAMP_KEY =
88             DELETE_FROM_TABLE + WHERE + NAME_FILTER + AND + VERSION_FILTER  + AND + TIMESTAMP_FILTER;
89
90     private static final String DELETE_BY_REFERENCE_KEY =
91             DELETE_FROM_TABLE + WHERE + PARENT_NAME_FILTER + AND + PARENT_VERSION_FILTER + AND + LOCAL_NAME_FILTER;
92
93     private static final String SELECT_ALL_FOR_PARENT =
94             SELECT_FROM_TABLE + WHERE + PARENT_NAME_FILTER + AND + PARENT_VERSION_FILTER;
95
96     private static final String SELECT_ALL_VERSIONS = SELECT_FROM_TABLE + WHERE + NAME_FILTER;
97
98     private static final String SELECT_BY_CONCEPT_KEY =
99             SELECT_FROM_TABLE + WHERE + NAME_FILTER + AND + VERSION_FILTER;
100
101     private static final String SELECT_BY_REFERENCE_KEY =
102             SELECT_FROM_TABLE + WHERE + PARENT_NAME_FILTER + AND + PARENT_VERSION_FILTER + AND + LOCAL_NAME_FILTER;
103     // @formatter:on
104
105     // Entity manager for JPA
106     private EntityManagerFactory emf = null;
107
108     @Override
109     public void init(final DaoParameters daoParameters) throws PfModelException {
110         if (daoParameters == null || daoParameters.getPersistenceUnit() == null) {
111             LOGGER.error("Policy Framework persistence unit parameter not set");
112             throw new PfModelException(Response.Status.INTERNAL_SERVER_ERROR,
113                     "Policy Framework persistence unit parameter not set");
114         }
115
116         LOGGER.debug("Creating Policy Framework persistence unit \"{}\" . . .", daoParameters.getPersistenceUnit());
117         try {
118             emf = Persistence.createEntityManagerFactory(daoParameters.getPersistenceUnit(),
119                     daoParameters.getJdbcProperties());
120         } catch (final Exception ex) {
121             String errorMessage = "Creation of Policy Framework persistence unit \""
122                     + daoParameters.getPersistenceUnit() + "\" failed";
123             LOGGER.warn(errorMessage, ex);
124             throw new PfModelException(Response.Status.INTERNAL_SERVER_ERROR, errorMessage, ex);
125         }
126         LOGGER.debug("Created Policy Framework persistence unit \"{}\"", daoParameters.getPersistenceUnit());
127     }
128
129     /**
130      * Gets the entity manager for this DAO.
131      *
132      * @return the entity manager
133      */
134     protected final synchronized EntityManager getEntityManager() {
135         if (emf == null) {
136             LOGGER.warn("Policy Framework DAO has not been initialized");
137             throw new PfModelRuntimeException(Response.Status.INTERNAL_SERVER_ERROR,
138                     "Policy Framework DAO has not been initialized");
139         }
140
141         return emf.createEntityManager();
142     }
143
144     @Override
145     public final void close() {
146         if (emf != null) {
147             emf.close();
148         }
149     }
150
151     @Override
152     public <T extends PfConcept> void create(final T obj) {
153         if (obj == null) {
154             return;
155         }
156         final EntityManager mg = getEntityManager();
157         try {
158             mg.getTransaction().begin();
159             mg.merge(obj);
160             mg.getTransaction().commit();
161         } finally {
162             mg.close();
163         }
164     }
165
166     @Override
167     public <T extends PfConcept> void delete(final T obj) {
168         if (obj == null) {
169             return;
170         }
171         final EntityManager mg = getEntityManager();
172         try {
173             mg.getTransaction().begin();
174             mg.remove(mg.contains(obj) ? obj : mg.merge(obj));
175             mg.getTransaction().commit();
176         } finally {
177             mg.close();
178         }
179     }
180
181     @Override
182     public <T extends PfConcept> void delete(final Class<T> someClass, final PfConceptKey key) {
183         if (key == null) {
184             return;
185         }
186         final EntityManager mg = getEntityManager();
187         try {
188             // @formatter:off
189             mg.getTransaction().begin();
190             mg.createQuery(setQueryTable(DELETE_BY_CONCEPT_KEY, someClass), someClass)
191                 .setParameter(NAME,    key.getName())
192                 .setParameter(VERSION, key.getVersion())
193                 .executeUpdate();
194             mg.getTransaction().commit();
195             // @formatter:on
196         } finally {
197             mg.close();
198         }
199     }
200
201     @Override
202     public <T extends PfConcept> void delete(final Class<T> someClass, final PfReferenceKey key) {
203         if (key == null) {
204             return;
205         }
206         final EntityManager mg = getEntityManager();
207         try {
208             // @formatter:off
209             mg.getTransaction().begin();
210             mg.createQuery(setQueryTable(DELETE_BY_REFERENCE_KEY, someClass), someClass)
211                 .setParameter(PARENT_NAME,    key.getParentKeyName())
212                 .setParameter(PARENT_VERSION, key.getParentKeyVersion())
213                 .setParameter(LOCAL_NAME,     key.getLocalName())
214                 .executeUpdate();
215             mg.getTransaction().commit();
216             // @formatter:on
217         } finally {
218             mg.close();
219         }
220     }
221
222     @Override
223     public <T extends PfConcept> void delete(final Class<T> someClass, final PfTimestampKey key) {
224         if (key == null) {
225             return;
226         }
227         final EntityManager mg = getEntityManager();
228         try {
229             // @formatter:off
230             mg.getTransaction().begin();
231             mg.createQuery(setQueryTable(DELETE_BY_TIMESTAMP_KEY, someClass), someClass)
232                     .setParameter(NAME,    key.getName())
233                     .setParameter(VERSION, key.getVersion())
234                     .setParameter(TIMESTAMP, key.getTimeStamp())
235                     .executeUpdate();
236             mg.getTransaction().commit();
237             // @formatter:on
238         } finally {
239             mg.close();
240         }
241     }
242
243     @Override
244     public <T extends PfConcept> void createCollection(final Collection<T> objs) {
245         if (objs == null || objs.isEmpty()) {
246             return;
247         }
248         final EntityManager mg = getEntityManager();
249         try {
250             mg.getTransaction().begin();
251             for (final T t : objs) {
252                 mg.merge(t);
253             }
254             mg.getTransaction().commit();
255         } finally {
256             mg.close();
257         }
258     }
259
260     @Override
261     public <T extends PfConcept> void deleteCollection(final Collection<T> objs) {
262         if (objs == null || objs.isEmpty()) {
263             return;
264         }
265         final EntityManager mg = getEntityManager();
266         try {
267             mg.getTransaction().begin();
268             for (final T t : objs) {
269                 mg.remove(mg.contains(t) ? t : mg.merge(t));
270             }
271             mg.getTransaction().commit();
272         } finally {
273             mg.close();
274         }
275     }
276
277     @Override
278     public <T extends PfConcept> int deleteByConceptKey(final Class<T> someClass, final Collection<PfConceptKey> keys) {
279         if (keys == null || keys.isEmpty()) {
280             return 0;
281         }
282         int deletedCount = 0;
283         final EntityManager mg = getEntityManager();
284         try {
285             // @formatter:off
286             mg.getTransaction().begin();
287             for (final PfConceptKey key : keys) {
288                 deletedCount += mg.createQuery(setQueryTable(DELETE_BY_CONCEPT_KEY, someClass), someClass)
289                     .setParameter(NAME,    key.getName())
290                     .setParameter(VERSION, key.getVersion())
291                     .executeUpdate();
292             }
293             mg.getTransaction().commit();
294             // @formatter:on
295         } finally {
296             mg.close();
297         }
298         return deletedCount;
299     }
300
301     @Override
302     public <T extends PfConcept> int deleteByReferenceKey(final Class<T> someClass,
303             final Collection<PfReferenceKey> keys) {
304         if (keys == null || keys.isEmpty()) {
305             return 0;
306         }
307         int deletedCount = 0;
308         final EntityManager mg = getEntityManager();
309         try {
310             // @formatter:off
311             mg.getTransaction().begin();
312             for (final PfReferenceKey key : keys) {
313                 deletedCount += mg.createQuery(setQueryTable(DELETE_BY_REFERENCE_KEY, someClass), someClass)
314                     .setParameter(PARENT_NAME,    key.getParentKeyName())
315                     .setParameter(PARENT_VERSION, key.getParentKeyVersion())
316                     .setParameter(LOCAL_NAME,     key.getLocalName())
317                     .executeUpdate();
318             }
319             mg.getTransaction().commit();
320             // @formatter:on
321         } finally {
322             mg.close();
323         }
324         return deletedCount;
325     }
326
327     @Override
328     public <T extends PfConcept> void deleteAll(final Class<T> someClass) {
329         final EntityManager mg = getEntityManager();
330         try {
331             mg.getTransaction().begin();
332             mg.createQuery(setQueryTable(DELETE_FROM_TABLE, someClass), someClass).executeUpdate();
333             mg.getTransaction().commit();
334         } finally {
335             mg.close();
336         }
337     }
338
339     @Override
340     public <T extends PfConcept> List<T> getFiltered(final Class<T> someClass, final String name,
341             final String version) {
342         if (name == null) {
343             return getAll(someClass);
344         }
345
346         if (version == null) {
347             return getAllVersions(someClass, name);
348         }
349
350         T foundConcept = get(someClass, new PfConceptKey(name, version));
351
352         return (foundConcept == null ? Collections.emptyList() : Collections.singletonList(foundConcept));
353     }
354
355     @Override
356     public <T extends PfConcept> List<T> getFiltered(final Class<T> someClass, final String name, final String version,
357             final Date startTime, final Date endTime, final Map<String, Object> filterMap, final String sortOrder,
358             final int getRecordNum) {
359         final EntityManager mg = getEntityManager();
360
361         String filterQueryString = SELECT_FROM_TABLE + WHERE;
362
363         try {
364             if (filterMap != null) {
365                 StringBuilder bld = new StringBuilder(filterQueryString);
366                 for (String key : filterMap.keySet()) {
367                     bld.append("c." + key + "= :" + key + AND);
368                 }
369                 filterQueryString = bld.toString();
370             }
371             filterQueryString = addKeyFilterString(filterQueryString, name, startTime, endTime);
372             if (getRecordNum > 0) {
373                 filterQueryString += ORDER + " c.key.timeStamp " + sortOrder;
374             }
375             TypedQuery<T> query = mg.createQuery(setQueryTable(filterQueryString, someClass), someClass);
376
377             if (filterMap != null) {
378                 for (Map.Entry<String, Object> entry : filterMap.entrySet()) {
379                     query.setParameter(entry.getKey(), entry.getValue());
380                 }
381             }
382             if (name != null) {
383                 query.setParameter("name", name);
384             }
385             if (startTime != null) {
386                 if (endTime != null) {
387                     query.setParameter("startTime", startTime);
388                     query.setParameter("endTime", endTime);
389                 } else {
390                     query.setParameter("startTime", startTime);
391                 }
392             } else {
393                 if (endTime != null) {
394                     query.setParameter("endTime", endTime);
395                 }
396             }
397             if (getRecordNum > 0) {
398                 query.setMaxResults(getRecordNum);
399             }
400
401             LOGGER.error("filterQueryString is  \"{}\"", filterQueryString);
402             return query.getResultList();
403         } finally {
404             mg.close();
405         }
406     }
407
408     @Override
409     public <T extends PfConcept> T get(final Class<T> someClass, final PfConceptKey key) {
410         if (someClass == null) {
411             return null;
412         }
413         final EntityManager mg = getEntityManager();
414         try {
415             final T t = mg.find(someClass, key);
416             if (t != null) {
417                 mg.refresh(t);
418             }
419             return checkAndReturn(someClass, t);
420         } finally {
421             mg.close();
422         }
423     }
424
425     @Override
426     public <T extends PfConcept> T get(final Class<T> someClass, final PfReferenceKey key) {
427         if (someClass == null) {
428             return null;
429         }
430         final EntityManager mg = getEntityManager();
431         try {
432             final T t = mg.find(someClass, key);
433             if (t != null) {
434                 mg.refresh(t);
435             }
436             return checkAndReturn(someClass, t);
437         } finally {
438             mg.close();
439         }
440     }
441
442     @Override
443     public <T extends PfConcept> T get(final Class<T> someClass, final PfTimestampKey key) {
444         if (someClass == null) {
445             return null;
446         }
447         final EntityManager mg = getEntityManager();
448         try {
449             final T t = mg.find(someClass, key);
450             if (t != null) {
451                 mg.refresh(t);
452             }
453             return checkAndReturn(someClass, t);
454         } finally {
455             mg.close();
456         }
457     }
458
459     @Override
460     public <T extends PfConcept> List<T> getAll(final Class<T> someClass) {
461         if (someClass == null) {
462             return Collections.emptyList();
463         }
464         final EntityManager mg = getEntityManager();
465         try {
466             return mg.createQuery(setQueryTable(SELECT_FROM_TABLE, someClass), someClass).getResultList();
467         } finally {
468             mg.close();
469         }
470     }
471
472     @Override
473     public <T extends PfConcept> List<T> getAll(final Class<T> someClass, final PfConceptKey parentKey) {
474         if (someClass == null) {
475             return Collections.emptyList();
476         }
477         final EntityManager mg = getEntityManager();
478         try {
479             // @formatter:off
480             return mg.createQuery(setQueryTable(SELECT_ALL_FOR_PARENT, someClass), someClass)
481                     .setParameter(PARENT_NAME,    parentKey.getName())
482                     .setParameter(PARENT_VERSION, parentKey.getVersion())
483                     .getResultList();
484             // @formatter:on
485         } finally {
486             mg.close();
487         }
488     }
489
490     @Override
491     public <T extends PfConcept> List<T> getAllVersions(final Class<T> someClass, final String conceptName) {
492         if (someClass == null || conceptName == null) {
493             return Collections.emptyList();
494         }
495         final EntityManager mg = getEntityManager();
496         try {
497             // @formatter:off
498             return mg.createQuery(setQueryTable(SELECT_ALL_VERSIONS, someClass), someClass)
499                     .setParameter(NAME, conceptName)
500                     .getResultList();
501             // @formatter:on
502         } finally {
503             mg.close();
504         }
505     }
506
507     @Override
508     public <T extends PfConcept> T getConcept(final Class<T> someClass, final PfConceptKey key) {
509         if (someClass == null || key == null) {
510             return null;
511         }
512         final EntityManager mg = getEntityManager();
513         List<T> ret;
514         try {
515             // @formatter:off
516             ret = mg.createQuery(setQueryTable(SELECT_BY_CONCEPT_KEY, someClass), someClass)
517                     .setParameter(NAME,    key.getName())
518                     .setParameter(VERSION, key.getVersion())
519                     .getResultList();
520             // @formatter:on
521         } finally {
522             mg.close();
523         }
524
525         return getSingleResult(someClass, key.getId(), ret);
526     }
527
528     @Override
529     public <T extends PfConcept> T getConcept(final Class<T> someClass, final PfReferenceKey key) {
530         if (someClass == null || key == null) {
531             return null;
532         }
533         final EntityManager mg = getEntityManager();
534         List<T> ret;
535         try {
536             // @formatter:off
537             ret = mg.createQuery(setQueryTable(SELECT_BY_REFERENCE_KEY, someClass), someClass)
538                     .setParameter(PARENT_NAME,    key.getParentKeyName())
539                     .setParameter(PARENT_VERSION, key.getParentKeyVersion())
540                     .setParameter(LOCAL_NAME,     key.getLocalName())
541                     .getResultList();
542             // @formatter:on
543         } finally {
544             mg.close();
545         }
546
547         return getSingleResult(someClass, key.getId(), ret);
548     }
549
550     @Override
551     public <T extends PfConcept> T update(final T obj) {
552         final EntityManager mg = getEntityManager();
553         T ret;
554         try {
555             mg.getTransaction().begin();
556             ret = mg.merge(obj);
557             mg.flush();
558             mg.getTransaction().commit();
559         } finally {
560             mg.close();
561         }
562         return ret;
563     }
564
565     @Override
566     public <T extends PfConcept> long size(final Class<T> someClass) {
567         if (someClass == null) {
568             return 0;
569         }
570         final EntityManager mg = getEntityManager();
571         long size = 0;
572         try {
573             size = mg.createQuery("SELECT COUNT(c) FROM " + someClass.getSimpleName() + " c", Long.class)
574                     .getSingleResult();
575         } finally {
576             mg.close();
577         }
578         return size;
579     }
580
581     /**
582      * Add the table to a query string.
583      *
584      * @param queryString the query string
585      * @param tableClass the class name of the table
586      * @return the updated query string
587      */
588     private <T extends PfConcept> String setQueryTable(final String queryString, final Class<T> tableClass) {
589         return queryString.replaceAll(TABLE_TOKEN, tableClass.getSimpleName());
590     }
591
592     /**
593      * Check that a query returned one and only one entry and return that entry.
594      *
595      * @param someClass the class being searched for
596      * @param conceptName the concept name being searched for
597      * @param resultList the result list returned by the query
598      * @return the single unique result
599      */
600     private <T extends PfConcept> T getSingleResult(final Class<T> someClass, final String searchFilter, List<T> ret) {
601         if (ret == null || ret.isEmpty()) {
602             return null;
603         }
604         if (ret.size() > 1) {
605             throw new IllegalArgumentException("More than one result was returned query on " + someClass
606                     + " with filter " + searchFilter + ": " + ret);
607         }
608         return ret.get(0);
609     }
610
611     /**
612      * generate filter string with the filter value in TimestampKey.
613      *
614      * @param inputFilterString current filterString generated from FilterMap
615      * @param name the pdp name the start timeStamp to filter from database, filter rule: startTime <= filteredRecord
616      *        timeStamp <= endTime. null for ignore start time.
617      * @param endTime the end timeStamp to filter from database, filter rule: startTime <= filteredRecord timeStamp <=
618      *        endTime. null for ignore end time
619      * @return the filter string to query database
620      */
621     private String addKeyFilterString(String inputFilterString, final String name, final Date startTime,
622             final Date endTime) {
623         String filterQueryString;
624         if (name != null) {
625             inputFilterString += NAME_FILTER + AND;
626         }
627         if (startTime != null) {
628             if (endTime != null) {
629                 filterQueryString = inputFilterString + TIMESTAMP_START_FILTER + AND + TIMESTAMP_END_FILTER;
630             } else {
631                 filterQueryString = inputFilterString + TIMESTAMP_START_FILTER;
632             }
633         } else {
634             if (endTime != null) {
635                 filterQueryString = inputFilterString + TIMESTAMP_END_FILTER;
636             } else {
637                 filterQueryString = inputFilterString.substring(0, inputFilterString.length() - AND.length());
638             }
639         }
640
641         return filterQueryString;
642     }
643
644     /**
645      * check the result get from database and return the object.
646      *
647      * @param <T> the type of the object to get, a subclass of {@link PfConcept}
648      * @param someClass the class of the object to get, a subclass of {@link PfConcept}
649      * @param t the object that was retrieved from the database
650      * @return the checked object or null
651      */
652     private <T extends PfConcept> T checkAndReturn(final Class<T> someClass, final T objToCheck) {
653         if (objToCheck != null) {
654             try {
655                 return PfUtils.makeCopy(objToCheck);
656             } catch (final Exception e) {
657                 LOGGER.warn(CLONE_ERR_MSG, someClass.getName(), e);
658             }
659         }
660         return null;
661     }
662 }