[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.
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.
Other drivers must be installed manually.
(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`.
| 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` |
| 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` |
| user | | if needed | database user name for connection |
| password | | if needed | database user password for connection |
| Property | Default | Required | Description |
| ------------ | ------- | :-------: | ------------------------------------------------------------ |
| 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/>`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 |
| password | | if needed | database user password for connection |
| syncmappings | | if needed | The OpenJPA synchronize mappings configuration |
## Adding support for other JPA supported databases

View File

@ -15,7 +15,8 @@
<name>openHAB Add-ons :: Bundles :: Persistence Service :: JPA</name>
<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>
<dependencies>
@ -23,13 +24,13 @@
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-all</artifactId>
<version>2.4.0</version>
<version>${openjpa.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.derby/derby -->
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.11.1.1</version>
<version>10.16.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -39,7 +40,7 @@
<plugin>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-maven-plugin</artifactId>
<version>3.1.0</version>
<version>${openjpa.version}</version>
<configuration>
<excludes>org/apache/bval/**</excludes>
<includes>**/model/*.class</includes>
@ -51,7 +52,7 @@
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<!-- set the version to be the same as the level in your runtime -->
<version>3.1.0</version>
<version>${openjpa.version}</version>
</dependency>
</dependencies>
<executions>

View File

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

View File

@ -16,9 +16,10 @@ import java.text.DateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
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.library.items.ContactItem;
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.persistence.HistoricItem;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
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
*
*/
@NonNullByDefault
public class JpaHistoricItem implements HistoricItem {
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)
* @return list of historic items
*/
public static List<HistoricItem> fromResultList(List<JpaPersistentItem> jpaQueryResult, Item item) {
List<HistoricItem> ret = new ArrayList<>();
for (JpaPersistentItem i : jpaQueryResult) {
HistoricItem hi = fromPersistedItem(i, item);
ret.add(hi);
}
return ret;
return jpaQueryResult.stream().map(pItem -> fromPersistedItem(pItem, item)).collect(Collectors.toList());
}
/**
* 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
* @return historic item
*/
@ -105,7 +103,7 @@ public class JpaHistoricItem implements HistoricItem {
if (item instanceof NumberItem) {
state = new DecimalType(Double.valueOf(pItem.getValue()));
} else if (item instanceof DimmerItem) {
state = new PercentType(Integer.valueOf(pItem.getValue()));
state = new PercentType(Integer.parseInt(pItem.getValue()));
} else if (item instanceof SwitchItem) {
state = OnOffType.valueOf(pItem.getValue());
} else if (item instanceof ContactItem) {
@ -113,7 +111,7 @@ public class JpaHistoricItem implements HistoricItem {
} else if (item instanceof RollershutterItem) {
state = PercentType.valueOf(pItem.getValue());
} 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()));
} else if (item instanceof LocationItem) {
PointType pType = null;
@ -125,7 +123,7 @@ public class JpaHistoricItem implements HistoricItem {
pType.setAltitude(new DecimalType(comps[2]));
}
}
state = pType;
state = pType == null ? UnDefType.UNDEF : pType;
} else if (item instanceof StringListType) {
state = new StringListType(pItem.getValue());
} else {

View File

@ -12,7 +12,6 @@
*/
package org.openhab.persistence.jpa.internal;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -27,6 +26,7 @@ import javax.persistence.Query;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
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.persistence.jpa.internal.model.JpaPersistentItem;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
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.Reference;
import org.slf4j.Logger;
@ -55,19 +55,36 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
@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 {
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 ItemRegistry itemRegistry;
private @Nullable EntityManagerFactory emf = null;
private @Nullable EntityManagerFactory emf;
private @NonNullByDefault({}) JpaConfiguration config;
private boolean initialized;
@Activate
public JpaPersistenceService(final @Reference ItemRegistry itemRegistry) {
public JpaPersistenceService(BundleContext context, Map<String, @Nullable Object> properties,
final @Reference 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
*/
protected @Nullable EntityManagerFactory getEntityManagerFactory() {
protected EntityManagerFactory getEntityManagerFactory() {
EntityManagerFactory emf = this.emf;
if (emf == null) {
emf = newEntityManagerFactory();
this.emf = 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
*/
@Deactivate
public void deactivate() {
logger.debug("Deactivating jpa persistence service");
logger.debug("Deactivating JPA persistence service");
closeEntityManagerFactory();
}
@Override
public String getId() {
return "jpa";
return SERVICE_ID;
}
@Override
public String getLabel(@Nullable Locale locale) {
return "JPA";
return SERVICE_LABEL;
}
@Override
@ -121,8 +134,8 @@ public class JpaPersistenceService implements QueryablePersistenceService {
return;
}
if (!JpaConfiguration.isInitialized) {
logger.debug("Trying to create EntityManagerFactory but we don't have configuration yet!");
if (!initialized) {
logger.debug("Cannot create EntityManagerFactory without a valid configuration!");
return;
}
@ -135,7 +148,7 @@ public class JpaPersistenceService implements QueryablePersistenceService {
pItem.setValue(newValue);
logger.debug("Stored new value: {}", newValue);
} 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;
}
pItem.setName(name);
@ -151,7 +164,7 @@ public class JpaPersistenceService implements QueryablePersistenceService {
em.getTransaction().commit();
logger.debug("Persisting item...done");
} catch (Exception e) {
logger.error("Error on persisting item! Rolling back!", e);
logger.error("Error while persisting item! Rolling back!", e);
em.getTransaction().rollback();
} finally {
em.close();
@ -162,20 +175,24 @@ public class JpaPersistenceService implements QueryablePersistenceService {
@Override
public Set<PersistenceItemInfo> getItemInfo() {
return Collections.emptySet();
return Set.of();
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
logger.debug("Querying for historic item: {}", filter.getItemName());
if (!JpaConfiguration.isInitialized) {
logger.warn("Trying to create EntityManagerFactory but we don't have configuration yet!");
return Collections.emptyList();
if (!initialized) {
logger.warn("Cannot create EntityManagerFactory without a valid configuration!");
return List.of();
}
String itemName = filter.getItemName();
Item item = getItemFromRegistry(itemName);
if (item == null) {
logger.debug("Item '{}' does not exist in the item registry", itemName);
return List.of();
}
String sortOrder;
if (filter.getOrdering() == Ordering.ASCENDING) {
@ -225,19 +242,19 @@ public class JpaPersistenceService implements QueryablePersistenceService {
logger.debug("Retrieving result list...done");
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();
return historicList;
} catch (Exception e) {
logger.error("Error on querying database!", e);
logger.error("Error while querying database!", e);
em.getTransaction().rollback();
} finally {
em.close();
}
return Collections.emptyList();
return List.of();
}
/**
@ -251,24 +268,24 @@ public class JpaPersistenceService implements QueryablePersistenceService {
Map<String, String> properties = new HashMap<>();
properties.put("javax.persistence.jdbc.url", config.dbConnectionUrl);
properties.put("javax.persistence.jdbc.driver", config.dbDriverClass);
if (config.dbUserName != null) {
if (!config.dbUserName.isBlank()) {
properties.put("javax.persistence.jdbc.user", config.dbUserName);
}
if (config.dbPassword != null) {
if (!config.dbPassword.isBlank()) {
properties.put("javax.persistence.jdbc.password", config.dbPassword);
}
if (config.dbUserName != null && config.dbPassword == null) {
logger.warn("JPA persistence - it is recommended to use a password to protect data store");
if (config.dbUserName.isBlank() && config.dbPassword.isBlank()) {
logger.info("It is recommended to use a password to protect the JPA persistence data store");
}
if (config.dbSyncMapping != null && !config.dbSyncMapping.isBlank()) {
logger.warn("You are settings openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!");
if (!config.dbSyncMapping.isBlank()) {
logger.info("You are setting openjpa.jdbc.SynchronizeMappings, I hope you know what you're doing!");
properties.put("openjpa.jdbc.SynchronizeMappings", config.dbSyncMapping);
}
EntityManagerFactory fac = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
EntityManagerFactory factory = Persistence.createEntityManagerFactory(getPersistenceUnitName(), properties);
logger.debug("Creating EntityManagerFactory...done");
return fac;
return factory;
}
/**
@ -317,6 +334,6 @@ public class JpaPersistenceService implements QueryablePersistenceService {
@Override
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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PointType;
@ -25,16 +26,16 @@ import org.openhab.core.types.State;
* @author Manfred Bergmann - Initial contribution
*
*/
@NonNullByDefault
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
* @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) {
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.TemporalType;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
@ -39,11 +40,12 @@ import org.openhab.core.types.UnDefType;
@Entity
@Table(name = "HISTORIC_ITEM")
@NonNullByDefault
public class JpaPersistentItem implements HistoricItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private @NonNullByDefault({}) Long id;
private String name = "";
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