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