]> git.basschouten.com Git - openhab-addons.git/blob
b782d4bd6d0cb5a13f0317f5f1356ae4ba35bd47
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.jpa.internal;
14
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.Set;
21
22 import javax.persistence.EntityExistsException;
23 import javax.persistence.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.Persistence;
26 import javax.persistence.Query;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.core.config.core.ConfigurableService;
31 import org.openhab.core.items.Item;
32 import org.openhab.core.items.ItemNotFoundException;
33 import org.openhab.core.items.ItemRegistry;
34 import org.openhab.core.persistence.FilterCriteria;
35 import org.openhab.core.persistence.FilterCriteria.Ordering;
36 import org.openhab.core.persistence.HistoricItem;
37 import org.openhab.core.persistence.PersistenceItemInfo;
38 import org.openhab.core.persistence.PersistenceService;
39 import org.openhab.core.persistence.QueryablePersistenceService;
40 import org.openhab.core.persistence.strategy.PersistenceStrategy;
41 import org.openhab.core.types.UnDefType;
42 import org.openhab.persistence.jpa.internal.model.JpaPersistentItem;
43 import org.osgi.framework.BundleContext;
44 import org.osgi.framework.Constants;
45 import org.osgi.service.component.annotations.Activate;
46 import org.osgi.service.component.annotations.Component;
47 import org.osgi.service.component.annotations.Deactivate;
48 import org.osgi.service.component.annotations.Reference;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * JPA based implementation of QueryablePersistenceService.
54  *
55  * @author Manfred Bergmann - Initial contribution
56  */
57 @NonNullByDefault
58 @Component(service = { PersistenceService.class,
59         QueryablePersistenceService.class }, configurationPid = "org.openhab.jpa", //
60         property = Constants.SERVICE_PID + "=org.openhab.jpa")
61 @ConfigurableService(category = "persistence", label = "JPA Persistence Service", description_uri = JpaPersistenceService.CONFIG_URI)
62 public class JpaPersistenceService implements QueryablePersistenceService {
63
64     private static final String SERVICE_ID = "jpa";
65     private static final String SERVICE_LABEL = "JPA";
66     protected static final String CONFIG_URI = "persistence:jpa";
67
68     private final Logger logger = LoggerFactory.getLogger(JpaPersistenceService.class);
69
70     private final ItemRegistry itemRegistry;
71
72     private @Nullable EntityManagerFactory emf;
73
74     private @NonNullByDefault({}) JpaConfiguration config;
75
76     private boolean initialized;
77
78     @Activate
79     public JpaPersistenceService(BundleContext context, Map<String, @Nullable Object> properties,
80             final @Reference ItemRegistry itemRegistry) {
81         this.itemRegistry = itemRegistry;
82         logger.debug("Activating JPA persistence service");
83         try {
84             config = new JpaConfiguration(properties);
85             initialized = true;
86         } catch (IllegalArgumentException e) {
87             logger.warn("{}", e.getMessage());
88         }
89     }
90
91     /**
92      * lazy loading because update() is called after activate()
93      *
94      * @return EntityManagerFactory
95      */
96     protected EntityManagerFactory getEntityManagerFactory() {
97         EntityManagerFactory emf = this.emf;
98         if (emf == null) {
99             emf = newEntityManagerFactory();
100             this.emf = emf;
101         }
102         return emf;
103     }
104
105     /**
106      * Closes the EntityPersistenceFactory
107      */
108     @Deactivate
109     public void deactivate() {
110         logger.debug("Deactivating JPA persistence service");
111         closeEntityManagerFactory();
112     }
113
114     @Override
115     public String getId() {
116         return SERVICE_ID;
117     }
118
119     @Override
120     public String getLabel(@Nullable Locale locale) {
121         return SERVICE_LABEL;
122     }
123
124     @Override
125     public void store(Item item) {
126         store(item, null);
127     }
128
129     @Override
130     public void store(Item item, @Nullable String alias) {
131         logger.debug("Storing item: {}", item.getName());
132
133         if (item.getState() instanceof UnDefType) {
134             logger.debug("This item is of undefined type. Cannot persist it!");
135             return;
136         }
137
138         if (!initialized) {
139             logger.debug("Cannot create EntityManagerFactory without a valid configuration!");
140             return;
141         }
142
143         // determine item name to be stored
144         String name = (alias != null) ? alias : item.getName();
145
146         JpaPersistentItem pItem = new JpaPersistentItem();
147         try {
148             String newValue = StateHelper.toString(item.getState());
149             pItem.setValue(newValue);
150             logger.debug("Stored new value: {}", newValue);
151         } catch (Exception e1) {
152             logger.error("Error while converting state value to string: {}", e1.getMessage());
153             return;
154         }
155         pItem.setName(name);
156         pItem.setRealName(item.getName());
157         pItem.setTimestamp(new Date());
158
159         EntityManager em = getEntityManagerFactory().createEntityManager();
160         try {
161             logger.debug("Persisting item...");
162             // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
163             em.getTransaction().begin();
164             em.persist(pItem);
165             em.getTransaction().commit();
166             logger.debug("Persisting item...done");
167         } catch (Exception e) {
168             if (e.getCause() instanceof EntityExistsException) {
169                 // there's a UNIQUE constraint in the database, and we tried to write
170                 // a duplicate timestamp. Just ignore
171                 logger.debug("Failed to persist item {} because of duplicate timestamp", name);
172             } else {
173                 logger.error("Error while persisting item! Rolling back!", e);
174             }
175             em.getTransaction().rollback();
176         } finally {
177             em.close();
178         }
179
180         logger.debug("Storing item...done");
181     }
182
183     @Override
184     public Set<PersistenceItemInfo> getItemInfo() {
185         return Set.of();
186     }
187
188     @Override
189     public Iterable<HistoricItem> query(FilterCriteria filter) {
190         logger.debug("Querying for historic item: {}", filter.getItemName());
191
192         if (!initialized) {
193             logger.warn("Cannot create EntityManagerFactory without a valid configuration!");
194             return List.of();
195         }
196
197         String itemName = filter.getItemName();
198         if (itemName == null) {
199             logger.warn("Item name is missing in filter {}", filter);
200             return List.of();
201         }
202         Item item = getItemFromRegistry(itemName);
203         if (item == null) {
204             logger.debug("Item '{}' does not exist in the item registry", itemName);
205             return List.of();
206         }
207
208         String sortOrder;
209         if (filter.getOrdering() == Ordering.ASCENDING) {
210             sortOrder = "ASC";
211         } else {
212             sortOrder = "DESC";
213         }
214
215         boolean hasBeginDate = false;
216         boolean hasEndDate = false;
217         String queryString = "SELECT n FROM " + JpaPersistentItem.class.getSimpleName()
218                 + " n WHERE n.realName = :itemName";
219         if (filter.getBeginDate() != null) {
220             queryString += " AND n.timestamp >= :beginDate";
221             hasBeginDate = true;
222         }
223         if (filter.getEndDate() != null) {
224             queryString += " AND n.timestamp <= :endDate";
225             hasEndDate = true;
226         }
227         queryString += " ORDER BY n.timestamp " + sortOrder;
228
229         logger.debug("The query: {}", queryString);
230
231         EntityManager em = getEntityManagerFactory().createEntityManager();
232         try {
233             // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
234             em.getTransaction().begin();
235
236             logger.debug("Creating query...");
237             Query query = em.createQuery(queryString);
238             query.setParameter("itemName", item.getName());
239             if (hasBeginDate) {
240                 query.setParameter("beginDate", Date.from(filter.getBeginDate().toInstant()));
241             }
242             if (hasEndDate) {
243                 query.setParameter("endDate", Date.from(filter.getEndDate().toInstant()));
244             }
245
246             query.setFirstResult(filter.getPageNumber() * filter.getPageSize());
247             query.setMaxResults(filter.getPageSize());
248             logger.debug("Creating query...done");
249
250             logger.debug("Retrieving result list...");
251             @SuppressWarnings("unchecked")
252             List<JpaPersistentItem> result = query.getResultList();
253             logger.debug("Retrieving result list...done");
254
255             List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item);
256             logger.debug("Convert to HistoricItem: {}", historicList.size());
257
258             em.getTransaction().commit();
259
260             return historicList;
261         } catch (Exception e) {
262             logger.error("Error while querying database!", e);
263             em.getTransaction().rollback();
264         } finally {
265             em.close();
266         }
267
268         return List.of();
269     }
270
271     /**
272      * Creates a new EntityManagerFactory with properties read from openhab.cfg via JpaConfiguration.
273      *
274      * @return initialized EntityManagerFactory
275      */
276     protected EntityManagerFactory newEntityManagerFactory() {
277         logger.trace("Creating EntityManagerFactory...");
278
279         Map<String, String> properties = new HashMap<>();
280         properties.put("javax.persistence.jdbc.url", config.dbConnectionUrl);
281         properties.put("javax.persistence.jdbc.driver", config.dbDriverClass);
282         if (!config.dbUserName.isBlank()) {
283             properties.put("javax.persistence.jdbc.user", config.dbUserName);
284         }
285         if (!config.dbPassword.isBlank()) {
286             properties.put("javax.persistence.jdbc.password", config.dbPassword);
287         }
288         if (config.dbUserName.isBlank() && config.dbPassword.isBlank()) {
289             logger.info("It is recommended to use a password to protect the JPA persistence data store");
290         }
291         if (!config.dbSyncMapping.isBlank()) {
292             logger.info("You are setting openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!");
293             properties.put("openjpa.jdbc.SynchronizeMappings", config.dbSyncMapping);
294         }
295
296         EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
297         logger.debug("Creating EntityManagerFactory...done");
298
299         return factory;
300     }
301
302     /**
303      * Closes EntityManagerFactory
304      */
305     protected void closeEntityManagerFactory() {
306         if (emf != null) {
307             emf.close();
308             emf = null;
309         }
310         logger.debug("Closing down entity objects...done");
311     }
312
313     /**
314      * Checks if EntityManagerFactory is open
315      *
316      * @return true when open, false otherwise
317      */
318     protected boolean isEntityManagerFactoryOpen() {
319         return emf != null && emf.isOpen();
320     }
321
322     /**
323      * Return the persistence unit as in persistence.xml file.
324      *
325      * @return the persistence unit name
326      */
327     protected String getPersistenceUnitName() {
328         return "default";
329     }
330
331     /**
332      * Retrieves the item for the given name from the item registry
333      *
334      * @param itemName
335      * @return item
336      */
337     private @Nullable Item getItemFromRegistry(String itemName) {
338         try {
339             return itemRegistry.getItem(itemName);
340         } catch (ItemNotFoundException e1) {
341             logger.error("Unable to get item type for {}", itemName);
342         }
343         return null;
344     }
345
346     @Override
347     public List<PersistenceStrategy> getDefaultStrategies() {
348         return List.of();
349     }
350 }