2 * Copyright (c) 2010-2024 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.EntityExistsException;
23 import javax.persistence.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.Persistence;
26 import javax.persistence.Query;
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;
53 * JPA based implementation of QueryablePersistenceService.
55 * @author Manfred Bergmann - Initial contribution
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 {
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";
68 private final Logger logger = LoggerFactory.getLogger(JpaPersistenceService.class);
70 private final ItemRegistry itemRegistry;
72 private @Nullable EntityManagerFactory emf;
74 private @NonNullByDefault({}) JpaConfiguration config;
76 private boolean initialized;
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");
84 config = new JpaConfiguration(properties);
86 } catch (IllegalArgumentException e) {
87 logger.warn("{}", e.getMessage());
92 * lazy loading because update() is called after activate()
94 * @return EntityManagerFactory
96 protected EntityManagerFactory getEntityManagerFactory() {
97 EntityManagerFactory emf = this.emf;
99 emf = newEntityManagerFactory();
106 * Closes the EntityPersistenceFactory
109 public void deactivate() {
110 logger.debug("Deactivating JPA persistence service");
111 closeEntityManagerFactory();
115 public String getId() {
120 public String getLabel(@Nullable Locale locale) {
121 return SERVICE_LABEL;
125 public void store(Item item) {
130 public void store(Item item, @Nullable String alias) {
131 logger.debug("Storing item: {}", item.getName());
133 if (item.getState() instanceof UnDefType) {
134 logger.debug("This item is of undefined type. Cannot persist it!");
139 logger.debug("Cannot create EntityManagerFactory without a valid configuration!");
143 // determine item name to be stored
144 String name = (alias != null) ? alias : item.getName();
146 JpaPersistentItem pItem = new JpaPersistentItem();
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());
156 pItem.setRealName(item.getName());
157 pItem.setTimestamp(new Date());
159 EntityManager em = getEntityManagerFactory().createEntityManager();
161 logger.debug("Persisting item...");
162 // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
163 em.getTransaction().begin();
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);
173 logger.error("Error while persisting item! Rolling back!", e);
175 em.getTransaction().rollback();
180 logger.debug("Storing item...done");
184 public Set<PersistenceItemInfo> getItemInfo() {
189 public Iterable<HistoricItem> query(FilterCriteria filter) {
190 logger.debug("Querying for historic item: {}", filter.getItemName());
193 logger.warn("Cannot create EntityManagerFactory without a valid configuration!");
197 String itemName = filter.getItemName();
198 if (itemName == null) {
199 logger.warn("Item name is missing in filter {}", filter);
202 Item item = getItemFromRegistry(itemName);
204 logger.debug("Item '{}' does not exist in the item registry", itemName);
209 if (filter.getOrdering() == Ordering.ASCENDING) {
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";
223 if (filter.getEndDate() != null) {
224 queryString += " AND n.timestamp <= :endDate";
227 queryString += " ORDER BY n.timestamp " + sortOrder;
229 logger.debug("The query: {}", queryString);
231 EntityManager em = getEntityManagerFactory().createEntityManager();
233 // In RESOURCE_LOCAL calls to EntityManager require a begin/commit
234 em.getTransaction().begin();
236 logger.debug("Creating query...");
237 Query query = em.createQuery(queryString);
238 query.setParameter("itemName", item.getName());
240 query.setParameter("beginDate", Date.from(filter.getBeginDate().toInstant()));
243 query.setParameter("endDate", Date.from(filter.getEndDate().toInstant()));
246 query.setFirstResult(filter.getPageNumber() * filter.getPageSize());
247 query.setMaxResults(filter.getPageSize());
248 logger.debug("Creating query...done");
250 logger.debug("Retrieving result list...");
251 @SuppressWarnings("unchecked")
252 List<JpaPersistentItem> result = query.getResultList();
253 logger.debug("Retrieving result list...done");
255 List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item);
256 logger.debug("Convert to HistoricItem: {}", historicList.size());
258 em.getTransaction().commit();
261 } catch (Exception e) {
262 logger.error("Error while querying database!", e);
263 em.getTransaction().rollback();
272 * Creates a new EntityManagerFactory with properties read from openhab.cfg via JpaConfiguration.
274 * @return initialized EntityManagerFactory
276 protected EntityManagerFactory newEntityManagerFactory() {
277 logger.trace("Creating EntityManagerFactory...");
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);
285 if (!config.dbPassword.isBlank()) {
286 properties.put("javax.persistence.jdbc.password", config.dbPassword);
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");
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);
296 EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
297 logger.debug("Creating EntityManagerFactory...done");
303 * Closes EntityManagerFactory
305 protected void closeEntityManagerFactory() {
310 logger.debug("Closing down entity objects...done");
314 * Checks if EntityManagerFactory is open
316 * @return true when open, false otherwise
318 protected boolean isEntityManagerFactoryOpen() {
319 return emf != null && emf.isOpen();
323 * Return the persistence unit as in persistence.xml file.
325 * @return the persistence unit name
327 protected String getPersistenceUnitName() {
332 * Retrieves the item for the given name from the item registry
337 private @Nullable Item getItemFromRegistry(String itemName) {
339 return itemRegistry.getItem(itemName);
340 } catch (ItemNotFoundException e1) {
341 logger.error("Unable to get item type for {}", itemName);
347 public List<PersistenceStrategy> getDefaultStrategies() {