2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.jpa.internal;
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Locale;
22 import javax.persistence.EntityManager;
23 import javax.persistence.EntityManagerFactory;
24 import javax.persistence.Persistence;
25 import javax.persistence.Query;
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;
52 * JPA based implementation of QueryablePersistenceService.
54 * @author Manfred Bergmann - Initial contribution
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 {
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";
67 private final Logger logger = LoggerFactory.getLogger(JpaPersistenceService.class);
69 private final ItemRegistry itemRegistry;
71 private @Nullable EntityManagerFactory emf;
73 private @NonNullByDefault({}) JpaConfiguration config;
75 private boolean initialized;
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");
83 config = new JpaConfiguration(properties);
85 } catch (IllegalArgumentException e) {
86 logger.warn("{}", e.getMessage());
91 * lazy loading because update() is called after activate()
93 * @return EntityManagerFactory
95 protected EntityManagerFactory getEntityManagerFactory() {
96 EntityManagerFactory emf = this.emf;
98 emf = newEntityManagerFactory();
105 * Closes the EntityPersistenceFactory
108 public void deactivate() {
109 logger.debug("Deactivating JPA persistence service");
110 closeEntityManagerFactory();
114 public String getId() {
119 public String getLabel(@Nullable Locale locale) {
120 return SERVICE_LABEL;
124 public void store(Item item) {
129 public void store(Item item, @Nullable String alias) {
130 logger.debug("Storing item: {}", item.getName());
132 if (item.getState() instanceof UnDefType) {
133 logger.debug("This item is of undefined type. Cannot persist it!");
138 logger.debug("Cannot create EntityManagerFactory without a valid configuration!");
142 // determine item name to be stored
143 String name = (alias != null) ? alias : item.getName();
145 JpaPersistentItem pItem = new JpaPersistentItem();
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());
155 pItem.setRealName(item.getName());
156 pItem.setTimestamp(new Date());
158 EntityManager em = getEntityManagerFactory().createEntityManager();
160 logger.debug("Persisting item...");
161 // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
162 em.getTransaction().begin();
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();
173 logger.debug("Storing item...done");
177 public Set<PersistenceItemInfo> getItemInfo() {
182 public Iterable<HistoricItem> query(FilterCriteria filter) {
183 logger.debug("Querying for historic item: {}", filter.getItemName());
186 logger.warn("Cannot create EntityManagerFactory without a valid configuration!");
190 String itemName = filter.getItemName();
191 Item item = getItemFromRegistry(itemName);
193 logger.debug("Item '{}' does not exist in the item registry", itemName);
198 if (filter.getOrdering() == Ordering.ASCENDING) {
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";
212 if (filter.getEndDate() != null) {
213 queryString += " AND n.timestamp <= :endDate";
216 queryString += " ORDER BY n.timestamp " + sortOrder;
218 logger.debug("The query: {}", queryString);
220 EntityManager em = getEntityManagerFactory().createEntityManager();
222 // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
223 em.getTransaction().begin();
225 logger.debug("Creating query...");
226 Query query = em.createQuery(queryString);
227 query.setParameter("itemName", item.getName());
229 query.setParameter("beginDate", Date.from(filter.getBeginDate().toInstant()));
232 query.setParameter("endDate", Date.from(filter.getEndDate().toInstant()));
235 query.setFirstResult(filter.getPageNumber() * filter.getPageSize());
236 query.setMaxResults(filter.getPageSize());
237 logger.debug("Creating query...done");
239 logger.debug("Retrieving result list...");
240 @SuppressWarnings("unchecked")
241 List<JpaPersistentItem> result = query.getResultList();
242 logger.debug("Retrieving result list...done");
244 List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item);
245 logger.debug("Convert to HistoricItem: {}", historicList.size());
247 em.getTransaction().commit();
250 } catch (Exception e) {
251 logger.error("Error while querying database!", e);
252 em.getTransaction().rollback();
261 * Creates a new EntityManagerFactory with properties read from openhab.cfg via JpaConfiguration.
263 * @return initialized EntityManagerFactory
265 protected EntityManagerFactory newEntityManagerFactory() {
266 logger.trace("Creating EntityManagerFactory...");
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);
274 if (!config.dbPassword.isBlank()) {
275 properties.put("javax.persistence.jdbc.password", config.dbPassword);
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");
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);
285 EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
286 logger.debug("Creating EntityManagerFactory...done");
292 * Closes EntityManagerFactory
294 protected void closeEntityManagerFactory() {
299 logger.debug("Closing down entity objects...done");
303 * Checks if EntityManagerFactory is open
305 * @return true when open, false otherwise
307 protected boolean isEntityManagerFactoryOpen() {
308 return emf != null && emf.isOpen();
312 * Return the persistence unit as in persistence.xml file.
314 * @return the persistence unit name
316 protected String getPersistenceUnitName() {
321 * Retrieves the item for the given name from the item registry
326 private @Nullable Item getItemFromRegistry(String itemName) {
328 return itemRegistry.getItem(itemName);
329 } catch (ItemNotFoundException e1) {
330 logger.error("Unable to get item type for {}", itemName);
336 public List<PersistenceStrategy> getDefaultStrategies() {