]> git.basschouten.com Git - openhab-addons.git/blob
ebaca5b560520e2e50c2b05380bb3b57eb1fc8b5
[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         Item item = getItemFromRegistry(itemName);
192         if (item == null) {
193             logger.debug("Item '{}' does not exist in the item registry", itemName);
194             return List.of();
195         }
196
197         String sortOrder;
198         if (filter.getOrdering() == Ordering.ASCENDING) {
199             sortOrder = "ASC";
200         } else {
201             sortOrder = "DESC";
202         }
203
204         boolean hasBeginDate = false;
205         boolean hasEndDate = false;
206         String queryString = "SELECT n FROM " + JpaPersistentItem.class.getSimpleName()
207                 + " n WHERE n.realName = :itemName";
208         if (filter.getBeginDate() != null) {
209             queryString += " AND n.timestamp >= :beginDate";
210             hasBeginDate = true;
211         }
212         if (filter.getEndDate() != null) {
213             queryString += " AND n.timestamp <= :endDate";
214             hasEndDate = true;
215         }
216         queryString += " ORDER BY n.timestamp " + sortOrder;
217
218         logger.debug("The query: {}", queryString);
219
220         EntityManager em = getEntityManagerFactory().createEntityManager();
221         try {
222             // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
223             em.getTransaction().begin();
224
225             logger.debug("Creating query...");
226             Query query = em.createQuery(queryString);
227             query.setParameter("itemName", item.getName());
228             if (hasBeginDate) {
229                 query.setParameter("beginDate", Date.from(filter.getBeginDate().toInstant()));
230             }
231             if (hasEndDate) {
232                 query.setParameter("endDate", Date.from(filter.getEndDate().toInstant()));
233             }
234
235             query.setFirstResult(filter.getPageNumber() * filter.getPageSize());
236             query.setMaxResults(filter.getPageSize());
237             logger.debug("Creating query...done");
238
239             logger.debug("Retrieving result list...");
240             @SuppressWarnings("unchecked")
241             List<JpaPersistentItem> result = query.getResultList();
242             logger.debug("Retrieving result list...done");
243
244             List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item);
245             logger.debug("Convert to HistoricItem: {}", historicList.size());
246
247             em.getTransaction().commit();
248
249             return historicList;
250         } catch (Exception e) {
251             logger.error("Error while querying database!", e);
252             em.getTransaction().rollback();
253         } finally {
254             em.close();
255         }
256
257         return List.of();
258     }
259
260     /**
261      * Creates a new EntityManagerFactory with properties read from openhab.cfg via JpaConfiguration.
262      *
263      * @return initialized EntityManagerFactory
264      */
265     protected EntityManagerFactory newEntityManagerFactory() {
266         logger.trace("Creating EntityManagerFactory...");
267
268         Map<String, String> properties = new HashMap<>();
269         properties.put("javax.persistence.jdbc.url", config.dbConnectionUrl);
270         properties.put("javax.persistence.jdbc.driver", config.dbDriverClass);
271         if (!config.dbUserName.isBlank()) {
272             properties.put("javax.persistence.jdbc.user", config.dbUserName);
273         }
274         if (!config.dbPassword.isBlank()) {
275             properties.put("javax.persistence.jdbc.password", config.dbPassword);
276         }
277         if (config.dbUserName.isBlank() && config.dbPassword.isBlank()) {
278             logger.info("It is recommended to use a password to protect the JPA persistence data store");
279         }
280         if (!config.dbSyncMapping.isBlank()) {
281             logger.info("You are setting openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!");
282             properties.put("openjpa.jdbc.SynchronizeMappings", config.dbSyncMapping);
283         }
284
285         EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
286         logger.debug("Creating EntityManagerFactory...done");
287
288         return factory;
289     }
290
291     /**
292      * Closes EntityManagerFactory
293      */
294     protected void closeEntityManagerFactory() {
295         if (emf != null) {
296             emf.close();
297             emf = null;
298         }
299         logger.debug("Closing down entity objects...done");
300     }
301
302     /**
303      * Checks if EntityManagerFactory is open
304      *
305      * @return true when open, false otherwise
306      */
307     protected boolean isEntityManagerFactoryOpen() {
308         return emf != null && emf.isOpen();
309     }
310
311     /**
312      * Return the persistence unit as in persistence.xml file.
313      *
314      * @return the persistence unit name
315      */
316     protected String getPersistenceUnitName() {
317         return "default";
318     }
319
320     /**
321      * Retrieves the item for the given name from the item registry
322      *
323      * @param itemName
324      * @return item
325      */
326     private @Nullable Item getItemFromRegistry(String itemName) {
327         try {
328             return itemRegistry.getItem(itemName);
329         } catch (ItemNotFoundException e1) {
330             logger.error("Unable to get item type for {}", itemName);
331         }
332         return null;
333     }
334
335     @Override
336     public List<PersistenceStrategy> getDefaultStrategies() {
337         return List.of();
338     }
339 }