[jpa] Add dynamic import, upgrade dependencies, add UI config (#13516)

* Adds a dynamic package import so JDBC drivers on the classpath can be used
* Upgrades OpenJPA from 2.4.0 to 3.2.2
* Upgrades Derby JDBC driver from 10.11.1.1 to 10.16.1.1
* Adds config.xml and ConfigurableService annotation so add-on can be configured using the UI
* Adds null annotations on all classes
* Prevent NPEs and some code cleanup

See also:

* https://openjpa.apache.org/builds/3.2.2/apache-openjpa/RELEASE-NOTES.html
* https://community.openhab.org/t/jpa-with-mysql-or-mariadb/138679

Fixes #13375

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2022-10-08 21:25:05 +02:00 committed by GitHub
parent d18322f860
commit 0306f4508f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 84 deletions

View File

@ -6,7 +6,7 @@ The service uses an abstraction layer that theoretically allows it to support ma
It will create one table named `historic_item` where all item states are stored. It will create one table named `historic_item` where all item states are stored.
The item state is stored in a string representation. The item state is stored in a string representation.
The service currently supports MySQL, Apache Derby and PostgreSQL databases. The service currently supports Apache Derby, MariaDB, MySQL and PostgreSQL databases.
Only the embedded Apache Derby database driver is included. Only the embedded Apache Derby database driver is included.
Other drivers must be installed manually. Other drivers must be installed manually.
(See below for more information on that.) (See below for more information on that.)
@ -15,12 +15,13 @@ Other drivers must be installed manually.
This service can be configured in the file `services/jpa.cfg`. This service can be configured in the file `services/jpa.cfg`.
| Property | Default | Required | Description | | Property | Default | Required | Description |
| -------- | ------- | :-------: | ------------------------------------------------------------ | | ------------ | ------- | :-------: | ------------------------------------------------------------ |
| url | | Yes | JDBC connection URL. Examples:<br/><br/>`jdbc:postgresql://hab.local:5432/openhab`<br/>`jdbc:derby://hab.local:1527/openhab;create=true`<br/>`jdbc:mysql://localhost:3306/openhab` | | url | | Yes | JDBC connection URL. Examples:<br/><br/>`jdbc:derby://hab.local:1527/openhab;create=true`<br/>`jdbc:mariadb://localhost:3306/openhab`<br/>`jdbc:mysql://localhost:3306/openhab`<br/>`jdbc:postgresql://hab.local:5432/openhab` |
| driver | | Yes | database driver. Examples:<br/><br/>`org.postgresql.Driver`<br/>`org.apache.derby.jdbc.ClientDriver`<br/>`com.mysql.jdbc.Driver`<br/></br>Only the Apache Derby driver is included with the service. Drivers for other databases must be installed manually. This is a trivial process. Normally JDBC database drivers are packaged as OSGi bundles and can just be dropped into the `addons` folder. This has the advantage that users can update their drivers as needed. The following database drivers are known to work:<br/><br/>`postgresql-9.4-1203-jdbc41.jar`<br/>`postgresql-9.4-1206-jdbc41.jar` | | driver | | Yes | database driver. Examples:<br/><br/>`com.mysql.jdbc.Driver`<br/>`org.apache.derby.jdbc.ClientDriver``org.mariadb.jdbc.Driver`<br/><br/>`org.postgresql.Driver`<br/></br>Only the Apache Derby driver is included with the service. Drivers for other databases must be installed manually. This is a trivial process. Normally JDBC database drivers are packaged as OSGi bundles and can just be dropped into the `addons` folder. This has the advantage that users can update their drivers as needed. The following database drivers are known to work:<br/><br/>`postgresql-9.4-1203-jdbc41.jar`<br/>`postgresql-9.4-1206-jdbc41.jar` |
| user | | if needed | database user name for connection | | user | | if needed | database user name for connection |
| password | | if needed | database user password for connection | | password | | if needed | database user password for connection |
| syncmappings | | if needed | The OpenJPA synchronize mappings configuration |
## Adding support for other JPA supported databases ## Adding support for other JPA supported databases

View File

@ -15,7 +15,8 @@
<name>openHAB Add-ons :: Bundles :: Persistence Service :: JPA</name> <name>openHAB Add-ons :: Bundles :: Persistence Service :: JPA</name>
<properties> <properties>
<bnd.importpackage>!com.ibm.*,!com.sun.*,!oracle.*,!org.apache.bval.*,!org.apache.geronimo.*,!org.apache.avalon.*,!org.apache.log,!org.apache.tools.*,!org.apache.xerces.*,!org.jboss.*,!org.postgresql.*,!org.slf4j.impl,!weblogic.*,!javax.rmi</bnd.importpackage> <bnd.importpackage>!com.ibm.*,!com.sun.*,!oracle.*,!javax.interceptor.*,!javax.enterprise.*,!javax.rmi,!org.apache.bval.*,!net.sf.cglib.*,!org.apache.commons.beanutils.*,!org.apache.geronimo.*,!org.apache.avalon.*,!org.apache.log,!org.apache.tools.*,!org.apache.xerces.*,!org.jboss.*,!org.postgresql.*,!org.slf4j.impl,!weblogic.*</bnd.importpackage>
<openjpa.version>3.2.2</openjpa.version>
</properties> </properties>
<dependencies> <dependencies>
@ -23,13 +24,13 @@
<dependency> <dependency>
<groupId>org.apache.openjpa</groupId> <groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-all</artifactId> <artifactId>openjpa-all</artifactId>
<version>2.4.0</version> <version>${openjpa.version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.derby/derby --> <!-- https://mvnrepository.com/artifact/org.apache.derby/derby -->
<dependency> <dependency>
<groupId>org.apache.derby</groupId> <groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId> <artifactId>derby</artifactId>
<version>10.11.1.1</version> <version>10.16.1.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@ -39,7 +40,7 @@
<plugin> <plugin>
<groupId>org.apache.openjpa</groupId> <groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-maven-plugin</artifactId> <artifactId>openjpa-maven-plugin</artifactId>
<version>3.1.0</version> <version>${openjpa.version}</version>
<configuration> <configuration>
<excludes>org/apache/bval/**</excludes> <excludes>org/apache/bval/**</excludes>
<includes>**/model/*.class</includes> <includes>**/model/*.class</includes>
@ -51,7 +52,7 @@
<groupId>org.apache.openjpa</groupId> <groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId> <artifactId>openjpa</artifactId>
<!-- set the version to be the same as the level in your runtime --> <!-- set the version to be the same as the level in your runtime -->
<version>3.1.0</version> <version>${openjpa.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<executions> <executions>

View File

@ -14,6 +14,8 @@ package org.openhab.persistence.jpa.internal;
import java.util.Map; import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -24,6 +26,7 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - migrated to 3.x * @author Kai Kreuzer - migrated to 3.x
* *
*/ */
@NonNullByDefault
public class JpaConfiguration { public class JpaConfiguration {
private final Logger logger = LoggerFactory.getLogger(JpaConfiguration.class); private final Logger logger = LoggerFactory.getLogger(JpaConfiguration.class);
@ -33,51 +36,51 @@ public class JpaConfiguration {
private static final String CFG_PASSWORD = "password"; private static final String CFG_PASSWORD = "password";
private static final String CFG_SYNCMAPPING = "syncmappings"; private static final String CFG_SYNCMAPPING = "syncmappings";
public static boolean isInitialized = false;
public final String dbConnectionUrl; public final String dbConnectionUrl;
public final String dbDriverClass; public final String dbDriverClass;
public final String dbUserName; public final String dbUserName;
public final String dbPassword; public final String dbPassword;
public final String dbSyncMapping; public final String dbSyncMapping;
public JpaConfiguration(final Map<String, Object> properties) { public JpaConfiguration(final Map<String, @Nullable Object> properties) throws IllegalArgumentException {
logger.debug("Update config..."); logger.debug("Creating JPA config...");
String param = (String) properties.get(CFG_CONNECTION_URL); String param = (String) properties.get(CFG_CONNECTION_URL);
logger.debug("url: {}", param); logger.debug("url: {}", param);
if (param == null) { if (param == null) {
logger.warn("Connection url is required in jpa.cfg!"); throw new IllegalArgumentException("Connection URL is required in JPA configuration!");
} else if (param.isBlank()) { } else if (param.isBlank()) {
logger.warn("Empty connection url in jpa.cfg!"); throw new IllegalArgumentException("Empty connection URL in JPA configuration!");
} }
dbConnectionUrl = param; dbConnectionUrl = param;
param = (String) properties.get(CFG_DRIVER_CLASS); param = (String) properties.get(CFG_DRIVER_CLASS);
logger.debug("driver: {}", param); logger.debug("driver: {}", param);
if (param == null) { if (param == null) {
logger.warn("Driver class is required in jpa.cfg!"); throw new IllegalArgumentException("Driver class is required in JPA configuration!");
} else if (param.isBlank()) { } else if (param.isBlank()) {
logger.warn("Empty driver class in jpa.cfg!"); throw new IllegalArgumentException("Empty driver class in JPA configuration!");
} }
dbDriverClass = param; dbDriverClass = param;
if (properties.get(CFG_USERNAME) == null) { param = (String) properties.get(CFG_USERNAME);
logger.info("{} was not specified!", CFG_USERNAME); if (param == null) {
logger.info("{} was not specified in JPA configuration!", CFG_USERNAME);
} }
dbUserName = (String) properties.get(CFG_USERNAME); dbUserName = param == null ? "" : param;
if (properties.get(CFG_PASSWORD) == null) { param = (String) properties.get(CFG_PASSWORD);
logger.info("{} was not specified!", CFG_PASSWORD); if (param == null) {
logger.info("{} was not specified in JPA configuration!", CFG_PASSWORD);
} }
dbPassword = (String) properties.get(CFG_PASSWORD); dbPassword = param == null ? "" : param;
if (properties.get(CFG_SYNCMAPPING) == null) { param = (String) properties.get(CFG_SYNCMAPPING);
logger.debug("{} was not specified!", CFG_SYNCMAPPING); if (param == null) {
logger.debug("{} was not specified in JPA configuration!", CFG_SYNCMAPPING);
} }
dbSyncMapping = (String) properties.get(CFG_SYNCMAPPING); dbSyncMapping = param == null ? "" : param;
isInitialized = true; logger.debug("Creating JPA config... done");
logger.debug("Update config... done");
} }
} }

View File

@ -16,9 +16,10 @@ import java.text.DateFormat;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.items.Item; import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem; import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DateTimeItem; import org.openhab.core.library.items.DateTimeItem;
@ -37,6 +38,7 @@ import org.openhab.core.library.types.StringListType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.persistence.HistoricItem; import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.persistence.jpa.internal.model.JpaPersistentItem; import org.openhab.persistence.jpa.internal.model.JpaPersistentItem;
/** /**
@ -45,6 +47,7 @@ import org.openhab.persistence.jpa.internal.model.JpaPersistentItem;
* @author Manfred Bergmann - Initial contribution * @author Manfred Bergmann - Initial contribution
* *
*/ */
@NonNullByDefault
public class JpaHistoricItem implements HistoricItem { public class JpaHistoricItem implements HistoricItem {
private final String name; private final String name;
@ -78,25 +81,20 @@ public class JpaHistoricItem implements HistoricItem {
} }
/** /**
* This method maps a jpa result item to this historic item. * This method maps {@link JpaPersistentItem}s to {@link HistoricItem}s.
* *
* @param jpaQueryResult the result which jpa items * @param jpaQueryResult the result with jpa items
* @param item used for query information, like the state (State) * @param item used for query information, like the state (State)
* @return list of historic items * @return list of historic items
*/ */
public static List<HistoricItem> fromResultList(List<JpaPersistentItem> jpaQueryResult, Item item) { public static List<HistoricItem> fromResultList(List<JpaPersistentItem> jpaQueryResult, Item item) {
List<HistoricItem> ret = new ArrayList<>(); return jpaQueryResult.stream().map(pItem -> fromPersistedItem(pItem, item)).collect(Collectors.toList());
for (JpaPersistentItem i : jpaQueryResult) {
HistoricItem hi = fromPersistedItem(i, item);
ret.add(hi);
}
return ret;
} }
/** /**
* Converts the string value of the persisted item to the state of a HistoricItem. * Converts the string value of the persisted item to the state of a {@link HistoricItem}.
* *
* @param pItem the persisted JpaPersistentItem * @param pItem the persisted {@link JpaPersistentItem}
* @param item the source reference Item * @param item the source reference Item
* @return historic item * @return historic item
*/ */
@ -105,7 +103,7 @@ public class JpaHistoricItem implements HistoricItem {
if (item instanceof NumberItem) { if (item instanceof NumberItem) {
state = new DecimalType(Double.valueOf(pItem.getValue())); state = new DecimalType(Double.valueOf(pItem.getValue()));
} else if (item instanceof DimmerItem) { } else if (item instanceof DimmerItem) {
state = new PercentType(Integer.valueOf(pItem.getValue())); state = new PercentType(Integer.parseInt(pItem.getValue()));
} else if (item instanceof SwitchItem) { } else if (item instanceof SwitchItem) {
state = OnOffType.valueOf(pItem.getValue()); state = OnOffType.valueOf(pItem.getValue());
} else if (item instanceof ContactItem) { } else if (item instanceof ContactItem) {
@ -113,7 +111,7 @@ public class JpaHistoricItem implements HistoricItem {
} else if (item instanceof RollershutterItem) { } else if (item instanceof RollershutterItem) {
state = PercentType.valueOf(pItem.getValue()); state = PercentType.valueOf(pItem.getValue());
} else if (item instanceof DateTimeItem) { } else if (item instanceof DateTimeItem) {
state = new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(pItem.getValue())), state = new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(pItem.getValue())),
ZoneId.systemDefault())); ZoneId.systemDefault()));
} else if (item instanceof LocationItem) { } else if (item instanceof LocationItem) {
PointType pType = null; PointType pType = null;
@ -125,7 +123,7 @@ public class JpaHistoricItem implements HistoricItem {
pType.setAltitude(new DecimalType(comps[2])); pType.setAltitude(new DecimalType(comps[2]));
} }
} }
state = pType; state = pType == null ? UnDefType.UNDEF : pType;
} else if (item instanceof StringListType) { } else if (item instanceof StringListType) {
state = new StringListType(pItem.getValue()); state = new StringListType(pItem.getValue());
} else { } else {

View File

@ -12,7 +12,6 @@
*/ */
package org.openhab.persistence.jpa.internal; package org.openhab.persistence.jpa.internal;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -27,6 +26,7 @@ import javax.persistence.Query;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.items.Item; import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.ItemRegistry;
@ -40,9 +40,9 @@ import org.openhab.core.persistence.strategy.PersistenceStrategy;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.openhab.persistence.jpa.internal.model.JpaPersistentItem; import org.openhab.persistence.jpa.internal.model.JpaPersistentItem;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -55,19 +55,36 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
@Component(service = { PersistenceService.class, @Component(service = { PersistenceService.class,
QueryablePersistenceService.class }, configurationPid = "org.openhab.jpa", configurationPolicy = ConfigurationPolicy.REQUIRE) QueryablePersistenceService.class }, configurationPid = "org.openhab.jpa", //
property = Constants.SERVICE_PID + "=org.openhab.jpa")
@ConfigurableService(category = "persistence", label = "JPA Persistence Service", description_uri = JpaPersistenceService.CONFIG_URI)
public class JpaPersistenceService implements QueryablePersistenceService { public class JpaPersistenceService implements QueryablePersistenceService {
private static final String SERVICE_ID = "jpa";
private static final String SERVICE_LABEL = "JPA";
protected static final String CONFIG_URI = "persistence:jpa";
private final Logger logger = LoggerFactory.getLogger(JpaPersistenceService.class); private final Logger logger = LoggerFactory.getLogger(JpaPersistenceService.class);
private final ItemRegistry itemRegistry; private final ItemRegistry itemRegistry;
private @Nullable EntityManagerFactory emf = null; private @Nullable EntityManagerFactory emf;
private @NonNullByDefault({}) JpaConfiguration config; private @NonNullByDefault({}) JpaConfiguration config;
private boolean initialized;
@Activate @Activate
public JpaPersistenceService(final @Reference ItemRegistry itemRegistry) { public JpaPersistenceService(BundleContext context, Map<String, @Nullable Object> properties,
final @Reference ItemRegistry itemRegistry) {
this.itemRegistry = itemRegistry; this.itemRegistry = itemRegistry;
logger.debug("Activating JPA persistence service");
try {
config = new JpaConfiguration(properties);
initialized = true;
} catch (IllegalArgumentException e) {
logger.warn("{}", e.getMessage());
}
} }
/** /**
@ -75,36 +92,32 @@ public class JpaPersistenceService implements QueryablePersistenceService {
* *
* @return EntityManagerFactory * @return EntityManagerFactory
*/ */
protected @Nullable EntityManagerFactory getEntityManagerFactory() { protected EntityManagerFactory getEntityManagerFactory() {
EntityManagerFactory emf = this.emf;
if (emf == null) { if (emf == null) {
emf = newEntityManagerFactory(); emf = newEntityManagerFactory();
this.emf = emf;
} }
return emf; return emf;
} }
@Activate
public void activate(BundleContext context, Map<String, Object> properties) {
logger.debug("Activating jpa persistence service");
config = new JpaConfiguration(properties);
}
/** /**
* Closes the EntityPersistenceFactory * Closes the EntityPersistenceFactory
*/ */
@Deactivate @Deactivate
public void deactivate() { public void deactivate() {
logger.debug("Deactivating jpa persistence service"); logger.debug("Deactivating JPA persistence service");
closeEntityManagerFactory(); closeEntityManagerFactory();
} }
@Override @Override
public String getId() { public String getId() {
return "jpa"; return SERVICE_ID;
} }
@Override @Override
public String getLabel(@Nullable Locale locale) { public String getLabel(@Nullable Locale locale) {
return "JPA"; return SERVICE_LABEL;
} }
@Override @Override
@ -121,8 +134,8 @@ public class JpaPersistenceService implements QueryablePersistenceService {
return; return;
} }
if (!JpaConfiguration.isInitialized) { if (!initialized) {
logger.debug("Trying to create EntityManagerFactory but we don't have configuration yet!"); logger.debug("Cannot create EntityManagerFactory without a valid configuration!");
return; return;
} }
@ -135,7 +148,7 @@ public class JpaPersistenceService implements QueryablePersistenceService {
pItem.setValue(newValue); pItem.setValue(newValue);
logger.debug("Stored new value: {}", newValue); logger.debug("Stored new value: {}", newValue);
} catch (Exception e1) { } catch (Exception e1) {
logger.error("Error on converting state value to string: {}", e1.getMessage()); logger.error("Error while converting state value to string: {}", e1.getMessage());
return; return;
} }
pItem.setName(name); pItem.setName(name);
@ -151,7 +164,7 @@ public class JpaPersistenceService implements QueryablePersistenceService {
em.getTransaction().commit(); em.getTransaction().commit();
logger.debug("Persisting item...done"); logger.debug("Persisting item...done");
} catch (Exception e) { } catch (Exception e) {
logger.error("Error on persisting item! Rolling back!", e); logger.error("Error while persisting item! Rolling back!", e);
em.getTransaction().rollback(); em.getTransaction().rollback();
} finally { } finally {
em.close(); em.close();
@ -162,20 +175,24 @@ public class JpaPersistenceService implements QueryablePersistenceService {
@Override @Override
public Set<PersistenceItemInfo> getItemInfo() { public Set<PersistenceItemInfo> getItemInfo() {
return Collections.emptySet(); return Set.of();
} }
@Override @Override
public Iterable<HistoricItem> query(FilterCriteria filter) { public Iterable<HistoricItem> query(FilterCriteria filter) {
logger.debug("Querying for historic item: {}", filter.getItemName()); logger.debug("Querying for historic item: {}", filter.getItemName());
if (!JpaConfiguration.isInitialized) { if (!initialized) {
logger.warn("Trying to create EntityManagerFactory but we don't have configuration yet!"); logger.warn("Cannot create EntityManagerFactory without a valid configuration!");
return Collections.emptyList(); return List.of();
} }
String itemName = filter.getItemName(); String itemName = filter.getItemName();
Item item = getItemFromRegistry(itemName); Item item = getItemFromRegistry(itemName);
if (item == null) {
logger.debug("Item '{}' does not exist in the item registry", itemName);
return List.of();
}
String sortOrder; String sortOrder;
if (filter.getOrdering() == Ordering.ASCENDING) { if (filter.getOrdering() == Ordering.ASCENDING) {
@ -225,19 +242,19 @@ public class JpaPersistenceService implements QueryablePersistenceService {
logger.debug("Retrieving result list...done"); logger.debug("Retrieving result list...done");
List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item); List<HistoricItem> historicList = JpaHistoricItem.fromResultList(result, item);
logger.debug("{}", String.format("Convert to HistoricItem: %d", historicList.size())); logger.debug("Convert to HistoricItem: {}", historicList.size());
em.getTransaction().commit(); em.getTransaction().commit();
return historicList; return historicList;
} catch (Exception e) { } catch (Exception e) {
logger.error("Error on querying database!", e); logger.error("Error while querying database!", e);
em.getTransaction().rollback(); em.getTransaction().rollback();
} finally { } finally {
em.close(); em.close();
} }
return Collections.emptyList(); return List.of();
} }
/** /**
@ -251,24 +268,24 @@ public class JpaPersistenceService implements QueryablePersistenceService {
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put("javax.persistence.jdbc.url", config.dbConnectionUrl); properties.put("javax.persistence.jdbc.url", config.dbConnectionUrl);
properties.put("javax.persistence.jdbc.driver", config.dbDriverClass); properties.put("javax.persistence.jdbc.driver", config.dbDriverClass);
if (config.dbUserName != null) { if (!config.dbUserName.isBlank()) {
properties.put("javax.persistence.jdbc.user", config.dbUserName); properties.put("javax.persistence.jdbc.user", config.dbUserName);
} }
if (config.dbPassword != null) { if (!config.dbPassword.isBlank()) {
properties.put("javax.persistence.jdbc.password", config.dbPassword); properties.put("javax.persistence.jdbc.password", config.dbPassword);
} }
if (config.dbUserName != null && config.dbPassword == null) { if (config.dbUserName.isBlank() && config.dbPassword.isBlank()) {
logger.warn("JPA persistence - it is recommended to use a password to protect data store"); logger.info("It is recommended to use a password to protect the JPA persistence data store");
} }
if (config.dbSyncMapping != null && !config.dbSyncMapping.isBlank()) { if (!config.dbSyncMapping.isBlank()) {
logger.warn("You are settings openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!"); logger.info("You are setting openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!");
properties.put("openjpa.jdbc.SynchronizeMappings", config.dbSyncMapping); properties.put("openjpa.jdbc.SynchronizeMappings", config.dbSyncMapping);
} }
EntityManagerFactory fac = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties); EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
logger.debug("Creating EntityManagerFactory...done"); logger.debug("Creating EntityManagerFactory...done");
return fac; return factory;
} }
/** /**
@ -317,6 +334,6 @@ public class JpaPersistenceService implements QueryablePersistenceService {
@Override @Override
public List<PersistenceStrategy> getDefaultStrategies() { public List<PersistenceStrategy> getDefaultStrategies() {
return Collections.emptyList(); return List.of();
} }
} }

View File

@ -14,6 +14,7 @@ package org.openhab.persistence.jpa.internal;
import java.util.Locale; import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.PointType;
@ -25,16 +26,16 @@ import org.openhab.core.types.State;
* @author Manfred Bergmann - Initial contribution * @author Manfred Bergmann - Initial contribution
* *
*/ */
@NonNullByDefault
public class StateHelper { public class StateHelper {
/** /**
* Converts the given State to a string that can be persisted in db * Converts the given State to a string that can be persisted in the database.
* *
* @param state the state of the item to be persisted * @param state the state of the item to be persisted
* @return state converted as string * @return state converted as string
* @throws Exception
*/ */
public static String toString(State state) throws Exception { public static String toString(State state) {
if (state instanceof DateTimeType) { if (state instanceof DateTimeType) {
return String.valueOf(((DateTimeType) state).getZonedDateTime().toInstant().toEpochMilli()); return String.valueOf(((DateTimeType) state).getZonedDateTime().toInstant().toEpochMilli());
} }

View File

@ -26,6 +26,7 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.persistence.HistoricItem; import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
@ -39,11 +40,12 @@ import org.openhab.core.types.UnDefType;
@Entity @Entity
@Table(name = "HISTORIC_ITEM") @Table(name = "HISTORIC_ITEM")
@NonNullByDefault
public class JpaPersistentItem implements HistoricItem { public class JpaPersistentItem implements HistoricItem {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private @NonNullByDefault({}) Long id;
private String name = ""; private String name = "";
private String realName = ""; private String realName = "";

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
@org.osgi.annotation.bundle.Header(name = org.osgi.framework.Constants.DYNAMICIMPORT_PACKAGE, value = "*")
package org.openhab.persistence.jpa.internal;
/**
* This dynamic import is required for loading the JDBC driver class.
*
* @author Wouter Born - Initial contribution
*/

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="persistence:jpa">
<parameter name="url" type="text" required="true">
<label>Database URL</label>
<description><![CDATA[JDBC connection URL.<br>Examples:<br>jdbc:derby://hab.local:1527/openhab;create=true<br>jdbc:mariadb://localhost:3306/openhab<br>jdbc:mysql://localhost:3306/openhab<br>jdbc:postgresql://hab.local:5432/openhab]]></description>
</parameter>
<parameter name="driver" type="text" required="true">
<label>Database Driver</label>
<description><![CDATA[The JDBC driver class name for the connection.<br>Examples:<br>com.mysql.jdbc.Driver<br>org.apache.derby.jdbc.ClientDriver<br>org.mariadb.jdbc.Driver<br>org.postgresql.Driver]]></description>
</parameter>
<parameter name="user" type="text">
<label>Database User</label>
<description>The database user name for the connection.</description>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Database Password</label>
<description>The database user password for the connection.</description>
</parameter>
<parameter name="syncmappings" type="text">
<label>Synchronize Mappings</label>
<description>The OpenJPA synchronize mappings configuration.</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,14 @@
persistence.config.jpa.driver.label = Database Driver
persistence.config.jpa.driver.description = The JDBC driver class name for the connection.<br>Examples:<br>com.mysql.jdbc.Driver<br>org.apache.derby.jdbc.ClientDriver<br>org.mariadb.jdbc.Driver<br>org.postgresql.Driver
persistence.config.jpa.password.label = Database Password
persistence.config.jpa.password.description = The database user password for the connection.
persistence.config.jpa.syncmappings.label = Synchronize Mappings
persistence.config.jpa.syncmappings.description = The OpenJPA synchronize mappings configuration.
persistence.config.jpa.url.label = Database URL
persistence.config.jpa.url.description = JDBC connection URL.<br>Examples:<br>jdbc:derby://hab.local:1527/openhab;create=true<br>jdbc:mariadb://localhost:3306/openhab<br>jdbc:mysql://localhost:3306/openhab<br>jdbc:postgresql://hab.local:5432/openhab
persistence.config.jpa.user.label = Database User
persistence.config.jpa.user.description = The database user name for the connection.
# service
service.persistence.jpa.label = JPA Persistence Service