]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jpa] Add dynamic import, upgrade dependencies, add UI config (#13516)
authorWouter Born <github@maindrain.net>
Sat, 8 Oct 2022 19:25:05 +0000 (21:25 +0200)
committerGitHub <noreply@github.com>
Sat, 8 Oct 2022 19:25:05 +0000 (21:25 +0200)
* 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>
bundles/org.openhab.persistence.jpa/README.md
bundles/org.openhab.persistence.jpa/pom.xml
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaConfiguration.java
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaHistoricItem.java
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/JpaPersistenceService.java
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/StateHelper.java
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/model/JpaPersistentItem.java
bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/package-info.java [new file with mode: 0644]
bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/config/config.xml [new file with mode: 0644]
bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa.properties [new file with mode: 0644]

index 5d7eb8de7342515f6aa5b30a328ee9ba96a65e0a..39abfbdab1cdc80840aea1fab9f40035dd76761a 100644 (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
 
index bd7626404f83bd37d07298a1b31c0a6e3297cff3..63434b4ea32896d98e36f9540ea6e3c956c75889 100644 (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>
     <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>
index 582c46f122cc12922d3b3b87bf9f07ad0c7bdcbd..70007f420c8ccb3a8e9d2b0b78b1290891908bda 100644 (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");
     }
 }
index 7df6944d5734eb618ddaf0b123e603fe89913569..a9fd0f059ab52aab1411f69fe4e209836cda5918 100644 (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 {
index fc84d990b6b1b5e36de9815840c104028b769d0b..68753ab725135fa9e2ffecfcc01e3238c2ef9ad0 100644 (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();
     }
 }
index d2c3bd21c935c1ceae04b6364e42ef15344b1efe..b82f47947c442a0bcaebd63f74dcce87bc667183 100644 (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());
         }
index 97c93ff8f00abb78f300bc069d58b0a279a30464..68710e4103b8c9b4e47849dad34d2b1d6748e27c 100644 (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 = "";
diff --git a/bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/package-info.java b/bundles/org.openhab.persistence.jpa/src/main/java/org/openhab/persistence/jpa/internal/package-info.java
new file mode 100644 (file)
index 0000000..59c2c3f
--- /dev/null
@@ -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
+ */
diff --git a/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/config/config.xml
new file mode 100644 (file)
index 0000000..cb701ec
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa.properties b/bundles/org.openhab.persistence.jpa/src/main/resources/OH-INF/i18n/jpa.properties
new file mode 100644 (file)
index 0000000..0032237
--- /dev/null
@@ -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