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