Add persistence for PDP concepts

This review adds the JPA annotations to PDP group/subgroup and PDP for
persisting to the database.

It also updates the Provider API as requested by other team members.

Issue-ID: POLICY-1095
Change-Id: I8188afb763849ede9680f3751b464d9d76c27196
Signed-off-by: liamfallon <liam.fallon@est.tech>
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroup.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroup.java
index c696794..a59d1af 100644
--- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroup.java
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroup.java
@@ -27,6 +27,8 @@
 
 import lombok.Data;
 import lombok.NoArgsConstructor;
+
+import org.onap.policy.models.base.PfNameVersion;
 import org.onap.policy.models.base.PfUtils;
 import org.onap.policy.models.pdp.enums.PdpState;
 
@@ -38,7 +40,7 @@
  */
 @Data
 @NoArgsConstructor
-public class PdpGroup {
+public class PdpGroup implements PfNameVersion {
     private String name;
     private String version;
     private String description;
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroups.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroups.java
index ea1c8ff..32e0b1a 100644
--- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroups.java
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpGroups.java
@@ -20,7 +20,11 @@
 
 package org.onap.policy.models.pdp.concepts;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
@@ -34,4 +38,17 @@
 @ToString
 public class PdpGroups {
     private List<PdpGroup> groups;
+
+    /**
+     * Get the contents of this class as a list of PDP group maps.
+     * @return the PDP groups in a list of maps
+     */
+    public List<Map<String, PdpGroup>> toMapList() {
+        final Map<String, PdpGroup> pdpGroupMap = new LinkedHashMap<>();
+        for (PdpGroup pdpGroup : groups) {
+            pdpGroupMap.put(pdpGroup.getName() + ':' + pdpGroup.getVersion() , pdpGroup);
+        }
+
+        return Collections.singletonList(pdpGroupMap);
+    }
 }
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdp.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdp.java
new file mode 100644
index 0000000..0a9aa58
--- /dev/null
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdp.java
@@ -0,0 +1,246 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Model
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.pdp.persistence.concepts;
+
+import java.io.Serializable;
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.Table;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.utils.validation.Assertions;
+import org.onap.policy.models.base.PfAuthorative;
+import org.onap.policy.models.base.PfConcept;
+import org.onap.policy.models.base.PfKey;
+import org.onap.policy.models.base.PfReferenceKey;
+import org.onap.policy.models.base.PfValidationMessage;
+import org.onap.policy.models.base.PfValidationResult;
+import org.onap.policy.models.base.PfValidationResult.ValidationResult;
+import org.onap.policy.models.pdp.concepts.Pdp;
+import org.onap.policy.models.pdp.enums.PdpHealthStatus;
+import org.onap.policy.models.pdp.enums.PdpState;
+
+/**
+ * Class to represent a PDP in the database.
+ *
+ * @author Liam Fallon (liam.fallon@est.tech)
+ */
+@Entity
+@Table(name = "Pdp")
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class JpaPdp extends PfConcept implements PfAuthorative<Pdp>, Serializable {
+    private static final long serialVersionUID = -357224425637789775L;
+
+    @EmbeddedId
+    private PfReferenceKey key;
+
+    @Column
+    private PdpState pdpState;
+
+    @Column
+    private PdpHealthStatus healthy;
+
+    @Column
+    private String message;
+
+    /**
+     * The Default Constructor creates a {@link JpaPdp} object with a null key.
+     */
+    public JpaPdp() {
+        this(new PfReferenceKey());
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdp} object with the given concept key.
+     *
+     * @param key the key
+     */
+    public JpaPdp(@NonNull final PfReferenceKey key) {
+        this(key, PdpState.PASSIVE, PdpHealthStatus.UNKNOWN);
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdp} object with all mandatory fields.
+     *
+     * @param key the key
+     * @param pdpState the state of the PDP
+     * @param healthy the health state of the PDP
+     */
+    public JpaPdp(@NonNull final PfReferenceKey key, @NonNull final PdpState pdpState,
+            @NonNull PdpHealthStatus healthy) {
+        this.key = key;
+        this.pdpState = pdpState;
+        this.healthy = healthy;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param copyConcept the concept to copy from
+     */
+    public JpaPdp(final JpaPdp copyConcept) {
+        super(copyConcept);
+    }
+
+    /**
+     * Authorative constructor.
+     *
+     * @param authorativeConcept the authorative concept to copy from
+     */
+    public JpaPdp(final Pdp authorativeConcept) {
+        this.fromAuthorative(authorativeConcept);
+    }
+
+    @Override
+    public Pdp toAuthorative() {
+        Pdp pdp = new Pdp();
+
+        pdp.setInstanceId(key.getLocalName());
+        pdp.setPdpState(pdpState);
+        pdp.setHealthy(healthy);
+        pdp.setMessage(message);
+
+        return pdp;
+    }
+
+    @Override
+    public void fromAuthorative(final Pdp pdp) {
+        if (this.getKey().isNullKey()) {
+            this.setKey(new PfReferenceKey());
+            getKey().setLocalName(pdp.getInstanceId());
+        }
+
+        this.setPdpState(pdp.getPdpState());
+        this.setHealthy(pdp.getHealthy());
+        this.setMessage(pdp.getMessage());
+    }
+
+    @Override
+    public List<PfKey> getKeys() {
+        return getKey().getKeys();
+    }
+
+    @Override
+    public void clean() {
+        key.clean();
+
+        if (message != null) {
+            message = message.trim();
+        }
+    }
+
+    @Override
+    public PfValidationResult validate(final PfValidationResult resultIn) {
+        PfValidationResult result = resultIn;
+
+        if (key.isNullKey()) {
+            result.addValidationMessage(
+                    new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID, "key is a null key"));
+        }
+
+        result = key.validate(result);
+
+        if (key.getParentConceptKey().isNullKey()) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "parent of key is a null key"));
+        }
+
+        if (PfKey.NULL_KEY_NAME.equals(key.getParentLocalName())) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "local name of parent of key is null"));
+        }
+
+        if (pdpState == null) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "PDP state may not be null"));
+        }
+
+        if (healthy == null) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "PDP health status may not be null"));
+        }
+
+        if (StringUtils.isBlank(message)) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "message may not be blank"));
+        }
+
+        return result;
+    }
+
+    @Override
+    public int compareTo(final PfConcept otherConcept) {
+        if (otherConcept == null) {
+            return -1;
+        }
+        if (this == otherConcept) {
+            return 0;
+        }
+        if (getClass() != otherConcept.getClass()) {
+            return this.hashCode() - otherConcept.hashCode();
+        }
+
+        final JpaPdp other = (JpaPdp) otherConcept;
+        if (!key.equals(other.key)) {
+            return key.compareTo(other.key);
+        }
+
+        int result = ObjectUtils.compare(pdpState, other.pdpState);
+        if (result != 0) {
+            return result;
+        }
+
+        result = ObjectUtils.compare(healthy, other.healthy);
+        if (result != 0) {
+            return result;
+        }
+
+        return ObjectUtils.compare(message, other.message);
+    }
+
+    @Override
+    public PfConcept copyTo(@NonNull final PfConcept target) {
+        Assertions.instanceOf(target, JpaPdp.class);
+
+        final JpaPdp copy = ((JpaPdp) target);
+        copy.setKey(new PfReferenceKey(key));
+        copy.setPdpState(pdpState);
+        copy.setHealthy(healthy);
+        copy.setMessage(message);
+
+        return copy;
+    }
+}
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpGroup.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpGroup.java
new file mode 100644
index 0000000..9c896a3
--- /dev/null
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpGroup.java
@@ -0,0 +1,307 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Model
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.pdp.persistence.concepts;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.utils.validation.Assertions;
+import org.onap.policy.common.utils.validation.ParameterValidationUtils;
+import org.onap.policy.models.base.PfAuthorative;
+import org.onap.policy.models.base.PfConcept;
+import org.onap.policy.models.base.PfConceptKey;
+import org.onap.policy.models.base.PfKey;
+import org.onap.policy.models.base.PfReferenceKey;
+import org.onap.policy.models.base.PfUtils;
+import org.onap.policy.models.base.PfValidationMessage;
+import org.onap.policy.models.base.PfValidationResult;
+import org.onap.policy.models.base.PfValidationResult.ValidationResult;
+import org.onap.policy.models.pdp.concepts.PdpGroup;
+import org.onap.policy.models.pdp.concepts.PdpSubGroup;
+import org.onap.policy.models.pdp.enums.PdpState;
+
+/**
+ * Class to represent a PDP group in the database.
+ *
+ * @author Liam Fallon (liam.fallon@est.tech)
+ */
+@Entity
+@Table(name = "PdpGroup")
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class JpaPdpGroup extends PfConcept implements PfAuthorative<PdpGroup> {
+    private static final long serialVersionUID = -357224425637789775L;
+
+    @EmbeddedId
+    private PfConceptKey key;
+
+    @Column
+    private String description;
+
+    @Column
+    private PdpState pdpGroupState;
+
+    @ElementCollection
+    private Map<String, String> properties;
+
+    // @formatter:off
+    @OneToMany
+    @CollectionTable(joinColumns = {
+            @JoinColumn(name = "pdpGroupParentKeyName",    referencedColumnName = "parentKeyName"),
+            @JoinColumn(name = "pdpGroupParentKeyVersion", referencedColumnName = "parentKeyVersion"),
+            @JoinColumn(name = "pdpGroupParentLocalName",  referencedColumnName = "parentLocalName"),
+            @JoinColumn(name = "pdpGroupLocalName",        referencedColumnName = "localName")
+        })
+    // @formatter:on
+    private List<JpaPdpSubGroup> pdpSubGroups;
+
+    /**
+     * The Default Constructor creates a {@link JpaPdpGroup} object with a null key.
+     */
+    public JpaPdpGroup() {
+        this(new PfConceptKey());
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdpGroup} object with the given concept key.
+     *
+     * @param key the key
+     */
+    public JpaPdpGroup(@NonNull final PfConceptKey key) {
+        this(key, PdpState.PASSIVE, new ArrayList<>());
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdpGroup} object with all mandatory fields.
+     *
+     * @param key the key
+     * @param pdpGroupState State of the PDP group
+     */
+    public JpaPdpGroup(@NonNull final PfConceptKey key, @NonNull final PdpState pdpGroupState,
+            @NonNull final List<JpaPdpSubGroup> pdpSubGroups) {
+        this.key = key;
+        this.pdpGroupState = pdpGroupState;
+        this.pdpSubGroups = pdpSubGroups;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param copyConcept the concept to copy from
+     */
+    public JpaPdpGroup(final JpaPdpGroup copyConcept) {
+        super(copyConcept);
+    }
+
+    /**
+     * Authorative constructor.
+     *
+     * @param authorativeConcept the authorative concept to copy from
+     */
+    public JpaPdpGroup(final PdpGroup authorativeConcept) {
+        this.fromAuthorative(authorativeConcept);
+    }
+
+    @Override
+    public PdpGroup toAuthorative() {
+        PdpGroup pdpGroup = new PdpGroup();
+
+        pdpGroup.setName(getKey().getName());
+        pdpGroup.setVersion(getKey().getVersion());
+        pdpGroup.setDescription(description);
+        pdpGroup.setPdpGroupState(pdpGroupState);
+
+        pdpGroup.setProperties(properties == null ? null : new LinkedHashMap<>(properties));
+
+        pdpGroup.setPdpSubgroups(new ArrayList<>(pdpSubGroups.size()));
+        for (JpaPdpSubGroup jpaPdpSubgroup : pdpSubGroups) {
+            pdpGroup.getPdpSubgroups().add(jpaPdpSubgroup.toAuthorative());
+        }
+
+        return pdpGroup;
+    }
+
+    @Override
+    public void fromAuthorative(final PdpGroup pdpGroup) {
+        this.setKey(new PfConceptKey(pdpGroup.getName(), pdpGroup.getVersion()));
+
+        this.description = pdpGroup.getDescription();
+        this.pdpGroupState = pdpGroup.getPdpGroupState();
+
+        this.properties =
+                (pdpGroup.getProperties() == null ? null : new LinkedHashMap<>(pdpGroup.getProperties()));
+
+        this.pdpSubGroups = new ArrayList<>();
+        for (PdpSubGroup pdpSubgroup : pdpGroup.getPdpSubgroups()) {
+            JpaPdpSubGroup jpaPdpSubGroup = new JpaPdpSubGroup();
+            jpaPdpSubGroup.setKey(new PfReferenceKey(getKey(), pdpSubgroup.getPdpType()));
+            jpaPdpSubGroup.fromAuthorative(pdpSubgroup);
+            this.pdpSubGroups.add(jpaPdpSubGroup);
+        }
+    }
+
+    @Override
+    public List<PfKey> getKeys() {
+        List<PfKey> keyList = getKey().getKeys();
+
+        for (JpaPdpSubGroup jpaPdpSubgroup : pdpSubGroups) {
+            keyList.addAll(jpaPdpSubgroup.getKeys());
+        }
+
+
+        return keyList;
+    }
+
+    @Override
+    public void clean() {
+        key.clean();
+
+        description = (description == null ? null : description.trim());
+
+        if (properties != null) {
+            Map<String, String> cleanedPropertyMap = new LinkedHashMap<>();
+            for (Entry<String, String> propertyEntry : properties.entrySet()) {
+                cleanedPropertyMap.put(propertyEntry.getKey().trim(), propertyEntry.getValue().trim());
+            }
+            properties = cleanedPropertyMap;
+        }
+
+        for (JpaPdpSubGroup jpaPdpSubgroup : pdpSubGroups) {
+            jpaPdpSubgroup.clean();
+        }
+    }
+
+    @Override
+    public PfValidationResult validate(final PfValidationResult resultIn) {
+        PfValidationResult result = resultIn;
+
+        if (key.isNullKey()) {
+            result.addValidationMessage(
+                    new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID, "key is a null key"));
+        }
+
+        result = key.validate(result);
+
+        if (StringUtils.isBlank(description)) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "description may not be blank"));
+        }
+
+        if (properties != null) {
+            for (Entry<String, String> propertyEntry : properties.entrySet()) {
+                if (!ParameterValidationUtils.validateStringParameter(propertyEntry.getKey())) {
+                    result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                            "a property key may not be null or blank"));
+                }
+                if (!ParameterValidationUtils.validateStringParameter(propertyEntry.getValue())) {
+                    result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                            "a property value may not be null or blank"));
+                }
+            }
+        }
+
+
+        if (pdpSubGroups == null) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "a PDP group must have a list of PDP subgroups"));
+        } else {
+            for (JpaPdpSubGroup jpaPdpSubgroup : pdpSubGroups) {
+                result = jpaPdpSubgroup.validate(result);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public int compareTo(final PfConcept otherConcept) {
+        if (otherConcept == null) {
+            return -1;
+        }
+        if (this == otherConcept) {
+            return 0;
+        }
+        if (getClass() != otherConcept.getClass()) {
+            return this.getClass().getCanonicalName().compareTo(otherConcept.getClass().getCanonicalName());
+        }
+
+        final JpaPdpGroup other = (JpaPdpGroup) otherConcept;
+        if (!key.equals(other.key)) {
+            return key.compareTo(other.key);
+        }
+
+        int result = ObjectUtils.compare(description, other.description);
+        if (result != 0) {
+            return result;
+        }
+
+        result = ObjectUtils.compare(pdpGroupState, other.pdpGroupState);
+        if (result != 0) {
+            return result;
+        }
+
+        result = PfUtils.compareObjects(properties, other.properties);
+        if (result != 0) {
+            return result;
+        }
+
+        return PfUtils.compareObjects(pdpSubGroups, other.pdpSubGroups);
+    }
+
+    @Override
+    public PfConcept copyTo(@NonNull final PfConcept target) {
+        Assertions.instanceOf(target, JpaPdpGroup.class);
+
+        final JpaPdpGroup copy = ((JpaPdpGroup) target);
+        copy.setKey(new PfConceptKey(key));
+
+        copy.setDescription(description);
+        copy.setPdpGroupState(pdpGroupState);
+        copy.setProperties(properties == null ? null : new LinkedHashMap<>(properties));
+        copy.setPdpSubGroups(PfUtils.mapList(pdpSubGroups, JpaPdpSubGroup::new));
+
+        return copy;
+    }
+}
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpSubGroup.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpSubGroup.java
new file mode 100644
index 0000000..5164d34
--- /dev/null
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpSubGroup.java
@@ -0,0 +1,406 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Model
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.pdp.persistence.concepts;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+
+import org.onap.policy.common.utils.validation.Assertions;
+import org.onap.policy.common.utils.validation.ParameterValidationUtils;
+import org.onap.policy.models.base.PfAuthorative;
+import org.onap.policy.models.base.PfConcept;
+import org.onap.policy.models.base.PfConceptKey;
+import org.onap.policy.models.base.PfKey;
+import org.onap.policy.models.base.PfKeyUse;
+import org.onap.policy.models.base.PfReferenceKey;
+import org.onap.policy.models.base.PfUtils;
+import org.onap.policy.models.base.PfValidationMessage;
+import org.onap.policy.models.base.PfValidationResult;
+import org.onap.policy.models.base.PfValidationResult.ValidationResult;
+import org.onap.policy.models.pdp.concepts.Pdp;
+import org.onap.policy.models.pdp.concepts.PdpSubGroup;
+import org.onap.policy.models.pdp.concepts.PolicyTypeIdent;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+
+/**
+ * Class to represent a PDP subgroup in the database.
+ *
+ * @author Liam Fallon (liam.fallon@est.tech)
+ */
+@Entity
+@Table(name = "PdpSubGroup")
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class JpaPdpSubGroup extends PfConcept implements PfAuthorative<PdpSubGroup> {
+    private static final long serialVersionUID = -357224425637789775L;
+
+    @EmbeddedId
+    private PfReferenceKey key;
+
+    @ElementCollection
+    private List<PfConceptKey> supportedPolicyTypes;
+
+    @ElementCollection
+    private List<PfConceptKey> policies;
+
+    @Column
+    private int currentInstanceCount;
+
+    @Column
+    private int desiredInstanceCount;
+
+    @ElementCollection
+    private Map<String, String> properties;
+
+    // @formatter:ofF
+    @OneToMany
+    @CollectionTable(joinColumns = {
+            @JoinColumn(name = "pdpSubGroupParentKeyName",    referencedColumnName = "parentKeyName"),
+            @JoinColumn(name = "pdpSubGroupParentKeyVersion", referencedColumnName = "parentKeyVersion"),
+            @JoinColumn(name = "pdpSubGroupParentLocalName",  referencedColumnName = "parentLocalName"),
+            @JoinColumn(name = "pdpSubGroupLocalName",        referencedColumnName = "localName")
+        })
+    // formatter:on
+    private List<JpaPdp> pdpInstances;
+
+    /**
+     * The Default Constructor creates a {@link JpaPdpSubGroup} object with a null key.
+     */
+    public JpaPdpSubGroup() {
+        this(new PfReferenceKey());
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdpSubGroup} object with the given concept key.
+     *
+     * @param key the key
+     */
+    public JpaPdpSubGroup(@NonNull final PfReferenceKey key) {
+        this(key, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
+    }
+
+    /**
+     * The Key Constructor creates a {@link JpaPdpSubGroup} object with all mandatory fields.
+     *
+     * @param key the key
+     * @param supportedPolicyTypes Supported policy types
+     * @param policies policies deployed on this PDP subgroups
+     * @param pdpInstances the PDP instances on this PDP subgroups
+     */
+    public JpaPdpSubGroup(@NonNull final PfReferenceKey key, @NonNull final List<PfConceptKey> supportedPolicyTypes,
+            @NonNull List<PfConceptKey> policies, @NonNull final List<JpaPdp> pdpInstances) {
+        this.key = key;
+        this.supportedPolicyTypes = supportedPolicyTypes;
+        this.policies = policies;
+        this.pdpInstances = pdpInstances;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param copyConcept the concept to copy from
+     */
+    public JpaPdpSubGroup(final JpaPdpSubGroup copyConcept) {
+        super(copyConcept);
+    }
+
+    /**
+     * Authorative constructor.
+     *
+     * @param authorativeConcept the authorative concept to copy from
+     */
+    public JpaPdpSubGroup(final PdpSubGroup authorativeConcept) {
+        this.fromAuthorative(authorativeConcept);
+    }
+
+    @Override
+    public PdpSubGroup toAuthorative() {
+        PdpSubGroup pdpSubgroup = new PdpSubGroup();
+
+        pdpSubgroup.setPdpType(getKey().getLocalName());
+
+        pdpSubgroup.setSupportedPolicyTypes(new ArrayList<>());
+        for (PfConceptKey supportedPolicyTypeKey : supportedPolicyTypes) {
+            PolicyTypeIdent supportedPolicyTypeIdent =
+                    new PolicyTypeIdent(supportedPolicyTypeKey.getName(), supportedPolicyTypeKey.getVersion());
+            pdpSubgroup.getSupportedPolicyTypes().add(supportedPolicyTypeIdent);
+        }
+
+        pdpSubgroup.setPolicies(new ArrayList<>());
+        for (PfConceptKey policyKey : policies) {
+            ToscaPolicy toscaPolicy = new ToscaPolicy();
+            toscaPolicy.setName(policyKey.getName());
+            toscaPolicy.setVersion(policyKey.getVersion());
+            pdpSubgroup.getPolicies().add(toscaPolicy);
+        }
+
+        pdpSubgroup.setCurrentInstanceCount(currentInstanceCount);
+        pdpSubgroup.setDesiredInstanceCount(desiredInstanceCount);
+        pdpSubgroup.setProperties(properties == null ? null : new LinkedHashMap<>(properties));
+
+        pdpSubgroup.setPdpInstances(new ArrayList<>());
+        for (JpaPdp jpaPdp : pdpInstances) {
+            pdpSubgroup.getPdpInstances().add(jpaPdp.toAuthorative());
+        }
+
+        return pdpSubgroup;
+    }
+
+    @Override
+    public void fromAuthorative(final PdpSubGroup pdpSubgroup) {
+        if (this.getKey().isNullKey()) {
+            this.setKey(new PfReferenceKey());
+            getKey().setLocalName(pdpSubgroup.getPdpType());
+        }
+
+        this.supportedPolicyTypes = new ArrayList<>();
+        for (PolicyTypeIdent supportedPolicyType : pdpSubgroup.getSupportedPolicyTypes()) {
+            this.supportedPolicyTypes
+                    .add(new PfConceptKey(supportedPolicyType.getName(), supportedPolicyType.getVersion()));
+        }
+
+
+        this.policies = new ArrayList<>();
+        for (ToscaPolicy toscaPolicy : pdpSubgroup.getPolicies()) {
+            this.policies.add(new PfConceptKey(toscaPolicy.getName(), toscaPolicy.getVersion()));
+        }
+
+        this.currentInstanceCount = pdpSubgroup.getCurrentInstanceCount();
+        this.desiredInstanceCount = pdpSubgroup.getDesiredInstanceCount();
+        this.properties =
+                (pdpSubgroup.getProperties() == null ? null : new LinkedHashMap<>(pdpSubgroup.getProperties()));
+
+        this.pdpInstances = new ArrayList<>();
+        for (Pdp pdp : pdpSubgroup.getPdpInstances()) {
+            JpaPdp jpaPdp = new JpaPdp();
+            jpaPdp.setKey(new PfReferenceKey(getKey(), pdp.getInstanceId()));
+            jpaPdp.fromAuthorative(pdp);
+            this.pdpInstances.add(jpaPdp);
+        }
+    }
+
+    @Override
+    public List<PfKey> getKeys() {
+        List<PfKey> keyList = getKey().getKeys();
+
+        for (PfConceptKey ptkey : supportedPolicyTypes) {
+            keyList.add(new PfKeyUse(ptkey));
+        }
+
+        for (PfConceptKey pkey : policies) {
+            keyList.add(new PfKeyUse(pkey));
+        }
+
+        for (JpaPdp jpaPdp : pdpInstances) {
+            keyList.addAll(jpaPdp.getKeys());
+        }
+
+
+        return keyList;
+    }
+
+    @Override
+    public void clean() {
+        key.clean();
+
+        for (PfConceptKey ptkey : supportedPolicyTypes) {
+            ptkey.clean();
+        }
+
+        for (PfConceptKey pkey : policies) {
+            pkey.clean();
+        }
+
+        if (properties != null) {
+            Map<String, String> cleanedPropertyMap = new LinkedHashMap<>();
+            for (Entry<String, String> propertyEntry : properties.entrySet()) {
+                cleanedPropertyMap.put(propertyEntry.getKey().trim(), propertyEntry.getValue().trim());
+            }
+            properties = cleanedPropertyMap;
+        }
+
+        for (JpaPdp jpaPdp : pdpInstances) {
+            jpaPdp.clean();
+        }
+    }
+
+    @Override
+    public PfValidationResult validate(final PfValidationResult resultIn) {
+        PfValidationResult result = resultIn;
+
+        if (key.isNullKey()) {
+            result.addValidationMessage(
+                    new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID, "key is a null key"));
+        }
+
+        result = key.validate(result);
+
+        if (key.getParentConceptKey().isNullKey()) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "parent of key is a null key"));
+        }
+
+        if (currentInstanceCount < 0) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "the current instance count of a PDP group may not be negative"));
+        }
+
+        if (desiredInstanceCount < 0) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "the desired instance count of a PDP group may not be negative"));
+        }
+
+        if (properties != null) {
+            for (Entry<String, String> propertyEntry : properties.entrySet()) {
+                if (!ParameterValidationUtils.validateStringParameter(propertyEntry.getKey())) {
+                    result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                            "a property key may not be null or blank"));
+                }
+                if (!ParameterValidationUtils.validateStringParameter(propertyEntry.getValue())) {
+                    result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                            "a property value may not be null or blank"));
+                }
+            }
+        }
+
+
+        return validateSubConcepts(result);
+    }
+
+    /**
+     * Validate collections of sub concepts.
+     *
+     * @param result the result in which to store the validation result
+     * @return the validation result including the results of this method
+     */
+    private PfValidationResult validateSubConcepts(PfValidationResult result) {
+        if (supportedPolicyTypes == null || supportedPolicyTypes.isEmpty()) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "a PDP subgroup must support at least one policy type"));
+        } else {
+            for (PfConceptKey supportedPolicyType : supportedPolicyTypes) {
+                result = supportedPolicyType.validate(result);
+            }
+        }
+
+        if (policies == null) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "a PDP subgroup must have a list of policies"));
+        } else {
+            for (PfConceptKey policyKey : policies) {
+                result = policyKey.validate(result);
+            }
+        }
+
+        if (pdpInstances == null) {
+            result.addValidationMessage(new PfValidationMessage(key, this.getClass(), ValidationResult.INVALID,
+                    "a PDP subgroup must have a list of PDPs"));
+        } else {
+            for (JpaPdp jpaPdp : pdpInstances) {
+                result = jpaPdp.validate(result);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public int compareTo(final PfConcept otherConcept) {
+        if (otherConcept == null) {
+            return -1;
+        }
+        if (this == otherConcept) {
+            return 0;
+        }
+        if (getClass() != otherConcept.getClass()) {
+            return this.hashCode() - otherConcept.hashCode();
+        }
+
+        final JpaPdpSubGroup other = (JpaPdpSubGroup) otherConcept;
+        if (!key.equals(other.key)) {
+            return key.compareTo(other.key);
+        }
+
+        int result = PfUtils.compareObjects(supportedPolicyTypes, other.supportedPolicyTypes);
+        if (result != 0) {
+            return result;
+        }
+
+        result = PfUtils.compareObjects(policies, other.policies);
+        if (result != 0) {
+            return result;
+        }
+
+        if (currentInstanceCount != other.currentInstanceCount) {
+            return currentInstanceCount - other.currentInstanceCount;
+        }
+
+        if (desiredInstanceCount != other.desiredInstanceCount) {
+            return desiredInstanceCount - other.desiredInstanceCount;
+        }
+
+        result = PfUtils.compareObjects(properties, other.properties);
+        if (result != 0) {
+            return result;
+        }
+
+        return PfUtils.compareObjects(pdpInstances, other.pdpInstances);
+    }
+
+    @Override
+    public PfConcept copyTo(@NonNull final PfConcept target) {
+        Assertions.instanceOf(target, JpaPdpSubGroup.class);
+
+        final JpaPdpSubGroup copy = ((JpaPdpSubGroup) target);
+        copy.setKey(new PfReferenceKey(key));
+
+        copy.setSupportedPolicyTypes(PfUtils.mapList(supportedPolicyTypes, PfConceptKey::new));
+        copy.setPolicies(PfUtils.mapList(policies, PfConceptKey::new));
+        copy.setCurrentInstanceCount(currentInstanceCount);
+        copy.setDesiredInstanceCount(desiredInstanceCount);
+        copy.setProperties(properties == null ? null : new LinkedHashMap<>(properties));
+        copy.setPdpInstances(PfUtils.mapList(pdpInstances, JpaPdp::new));
+
+        return copy;
+    }
+}
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/provider/PdpProvider.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/provider/PdpProvider.java
index 6af2d21..a32f5a4 100644
--- a/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/provider/PdpProvider.java
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/persistence/provider/PdpProvider.java
@@ -21,20 +21,28 @@
 package org.onap.policy.models.pdp.persistence.provider;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import javax.ws.rs.core.Response;
+
 import lombok.NonNull;
 
 import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.base.PfModelException;
+import org.onap.policy.models.base.PfModelRuntimeException;
+import org.onap.policy.models.base.PfValidationResult;
 import org.onap.policy.models.dao.PfDao;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
-import org.onap.policy.models.pdp.concepts.PdpGroups;
 import org.onap.policy.models.pdp.concepts.PdpStatistics;
 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
+import org.onap.policy.models.pdp.persistence.concepts.JpaPdpGroup;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This class provides the provision of information on PAP concepts in the database to callers.
@@ -42,6 +50,8 @@
  * @author Liam Fallon (liam.fallon@est.tech)
  */
 public class PdpProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(PdpProvider.class);
+
     /**
      * Get PDP groups.
      *
@@ -51,9 +61,19 @@
      * @return the PDP groups found
      * @throws PfModelException on errors getting PDP groups
      */
-    public PdpGroups getPdpGroups(@NonNull final PfDao dao, final String name, final String version)
+    public List<PdpGroup> getPdpGroups(@NonNull final PfDao dao, final String name, final String version)
             throws PfModelException {
-        return new PdpGroups();
+
+        PfConceptKey jpaPdpGroupKey = new PfConceptKey(name, version);
+        JpaPdpGroup jpaPdpGroup = dao.get(JpaPdpGroup.class, jpaPdpGroupKey);
+
+        if (jpaPdpGroup != null) {
+            return Collections.singletonList(jpaPdpGroup.toAuthorative());
+        } else {
+            String errorMessage = "PDP group not found: " + jpaPdpGroupKey.getId();
+            LOGGER.warn(errorMessage);
+            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+        }
     }
 
     /**
@@ -64,21 +84,21 @@
      * @return the PDP groups found
      * @throws PfModelException on errors getting policies
      */
-    public PdpGroups getLatestPdpGroups(@NonNull final PfDao dao, final String name) throws PfModelException {
-        return new PdpGroups();
+    public List<PdpGroup> getLatestPdpGroups(@NonNull final PfDao dao, final String name) throws PfModelException {
+        return new ArrayList<>();
     }
 
     /**
      * Get a filtered list of PDP groups.
      *
      * @param dao the DAO to use to access the database
-     * @param pdpType The PDP type filter for the returned PDP groups
+     * @param pdpType The PDP type filter for the returned PDP groups, null to get policy types across PDP subgroups
      * @param supportedPolicyTypes a list of policy type name/version pairs that the PDP groups must support.
      * @return the PDP groups found
      */
-    public PdpGroups getFilteredPdpGroups(@NonNull final PfDao dao, @NonNull final String pdpType,
+    public List<PdpGroup> getFilteredPdpGroups(@NonNull final PfDao dao, final String pdpType,
             @NonNull final List<Pair<String, String>> supportedPolicyTypes) {
-        return new PdpGroups();
+        return new ArrayList<>();
     }
 
     /**
@@ -89,9 +109,33 @@
      * @return the PDP groups created
      * @throws PfModelException on errors creating PDP groups
      */
-    public PdpGroups createPdpGroups(@NonNull final PfDao dao, @NonNull final PdpGroups pdpGroups)
+    public List<PdpGroup> createPdpGroups(@NonNull final PfDao dao, @NonNull final List<PdpGroup> pdpGroups)
             throws PfModelException {
-        return new PdpGroups();
+
+        for (PdpGroup pdpGroup : pdpGroups) {
+            JpaPdpGroup jpaPdpGroup = new JpaPdpGroup();;
+            jpaPdpGroup.fromAuthorative(pdpGroup);
+
+            PfValidationResult validationResult = jpaPdpGroup.validate(new PfValidationResult());
+            if (!validationResult.isOk()) {
+                String errorMessage = "pdp group \"" + jpaPdpGroup.getId() + "\" is not valid \n" + validationResult;
+                LOGGER.warn(errorMessage);
+                throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            }
+
+            dao.create(jpaPdpGroup);
+        }
+
+        // Return the created PDP groups
+        List<PdpGroup> returnPdpGroups = new ArrayList<>();
+
+        for (PdpGroup pdpGroup : pdpGroups) {
+            JpaPdpGroup jpaPdpGroup =
+                    dao.get(JpaPdpGroup.class, new PfConceptKey(pdpGroup.getName(), pdpGroup.getVersion()));
+            returnPdpGroups.add(jpaPdpGroup.toAuthorative());
+        }
+
+        return returnPdpGroups;
     }
 
     /**
@@ -102,9 +146,9 @@
      * @return the PDP groups updated
      * @throws PfModelException on errors updating PDP groups
      */
-    public PdpGroups updatePdpGroups(@NonNull final PfDao dao, @NonNull final PdpGroups pdpGroups)
+    public List<PdpGroup> updatePdpGroups(@NonNull final PfDao dao, @NonNull final List<PdpGroup> pdpGroups)
             throws PfModelException {
-        return new PdpGroups();
+        return new ArrayList<>();
     }
 
 
@@ -163,7 +207,7 @@
      */
     public void updatePdpStatistics(@NonNull final PfDao dao, @NonNull final String pdpGroupName,
             @NonNull final String pdpGroupVersion, @NonNull final String pdpType, @NonNull final String pdpInstanceId,
-            @NonNull final PdpStatistics pdppStatistics)  throws PfModelException {
+            @NonNull final PdpStatistics pdppStatistics) throws PfModelException {
         // Not implemented yet
     }
 
@@ -171,12 +215,12 @@
      * Get deployed policies.
      *
      * @param dao the DAO to use to access the database
-     * @param name the name of the policy to get, null to get all policies
-     * @return the policies deployed as a map of policy lists keyed by PDP group
+     * @param name the name of the policy to get deployed policies for, null to get all deployed policies
+     * @return the policies deployed as a map of policy lists keyed by PDP group name and version
      * @throws PfModelException on errors getting policies
      */
-    public Map<PdpGroup, List<ToscaPolicy>> getDeployedPolicyList(@NonNull final PfDao dao, final String name)
-            throws PfModelException {
+    public Map<Pair<String, String>, List<ToscaPolicy>> getDeployedPolicyList(@NonNull final PfDao dao,
+            final String name) throws PfModelException {
         return new LinkedHashMap<>();
     }
 }
diff --git a/models-pdp/src/test/java/org/onap/policy/models/persistence/provider/PdpProviderTest.java b/models-pdp/src/test/java/org/onap/policy/models/persistence/provider/PdpProviderTest.java
new file mode 100644
index 0000000..14990f9
--- /dev/null
+++ b/models-pdp/src/test/java/org/onap/policy/models/persistence/provider/PdpProviderTest.java
@@ -0,0 +1,230 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.persistence.provider;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.resources.ResourceUtils;
+import org.onap.policy.models.dao.DaoParameters;
+import org.onap.policy.models.dao.PfDao;
+import org.onap.policy.models.dao.PfDaoFactory;
+import org.onap.policy.models.dao.impl.DefaultPfDao;
+import org.onap.policy.models.pdp.concepts.Pdp;
+import org.onap.policy.models.pdp.concepts.PdpGroup;
+import org.onap.policy.models.pdp.concepts.PdpGroups;
+import org.onap.policy.models.pdp.concepts.PdpSubGroup;
+import org.onap.policy.models.pdp.concepts.PolicyTypeIdent;
+import org.onap.policy.models.pdp.enums.PdpHealthStatus;
+import org.onap.policy.models.pdp.enums.PdpState;
+import org.onap.policy.models.pdp.persistence.provider.PdpProvider;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+
+/**
+ * Test the {@link SimpleToscaProvider} class.
+ *
+ * @author Liam Fallon (liam.fallon@est.tech)
+ */
+public class PdpProviderTest {
+    private Connection connection;
+    private PfDao pfDao;
+    private StandardCoder standardCoder;
+
+
+    /**
+     * Set up the DAO towards the database.
+     *
+     * @throws Exception on database errors
+     */
+    @Before
+    public void setupDao() throws Exception {
+        // Use the JDBC UI "jdbc:h2:mem:testdb" to test towards the h2 database
+        // Use the JDBC UI "jdbc:mariadb://localhost:3306/policy" to test towards a locally installed mariadb instance
+        connection = DriverManager.getConnection("jdbc:h2:mem:testdb", "policy", "P01icY");
+
+        final DaoParameters daoParameters = new DaoParameters();
+        daoParameters.setPluginClass(DefaultPfDao.class.getCanonicalName());
+
+        // Use the persistence unit ToscaConceptTest to test towards the h2 database
+        // Use the persistence unit ToscaConceptMariaDBTest to test towards a locally installed mariadb instance
+        daoParameters.setPersistenceUnit("ToscaConceptTest");
+
+        pfDao = new PfDaoFactory().createPfDao(daoParameters);
+        pfDao.init(daoParameters);
+    }
+
+    /**
+     * Set up GSON.
+     */
+    @Before
+    public void setupGson() {
+        standardCoder = new StandardCoder();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        pfDao.close();
+        connection.close();
+    }
+
+    @Test
+    public void testPoliciesGet() throws Exception {
+        /*
+         * try { new PdpProvider().gePdpGroupst(null, null); fail("test should throw an exception here"); } catch
+         * (Exception exc) { assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+         *
+         * try { new SimpleToscaProvider().getPolicies(null, new PfConceptKey());
+         * fail("test should throw an exception here"); } catch (Exception exc) {
+         * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+         *
+         * try { new SimpleToscaProvider().getPolicies(pfDao, null); fail("test should throw an exception here"); }
+         * catch (Exception exc) { assertEquals("policyKey is marked @NonNull but is null", exc.getMessage()); }
+         */
+
+        String originalJson = ResourceUtils.getResourceAsString("testdata/PdpGroups0.json");
+
+        @SuppressWarnings("unchecked")
+        PdpGroups pdpGroups0 = standardCoder.decode(originalJson, PdpGroups.class);
+
+        PdpGroups createdPdpGroups0 = new PdpGroups();
+        createdPdpGroups0.setGroups(new PdpProvider().createPdpGroups(pfDao, pdpGroups0.getGroups()));
+        String createdJson = standardCoder.encode(createdPdpGroups0);
+        assertEquals(originalJson.replaceAll("\\s+", ""), createdJson.replaceAll("\\s+", ""));
+
+        PdpGroups gotPdpGroups0 = new PdpGroups();
+        gotPdpGroups0.setGroups(new PdpProvider().getPdpGroups(pfDao, "PdpGroup0", "1.2.3"));
+
+        String gotJson = standardCoder.encode(gotPdpGroups0);
+
+        assertEquals(originalJson.replaceAll("\\s+", ""), gotJson.replaceAll("\\s+", ""));
+
+    }
+    /*
+     * @Test public void testPolicyCreate() throws Exception { try { new SimpleToscaProvider().createPolicies(null,
+     * null); fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().createPolicies(null, new JpaToscaServiceTemplate());
+     * fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().createPolicies(pfDao, null); fail("test should throw an exception here"); } catch
+     * (Exception exc) { assertEquals("serviceTemplate is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * ToscaServiceTemplate toscaServiceTemplate = standardCoder.decode(
+     * ResourceUtils.getResourceAsString("policies/vCPE.policy.monitoring.input.tosca.json"),
+     * ToscaServiceTemplate.class);
+     *
+     * JpaToscaServiceTemplate originalServiceTemplate = new JpaToscaServiceTemplate();
+     * originalServiceTemplate.fromAuthorative(toscaServiceTemplate);
+     *
+     * assertNotNull(originalServiceTemplate); JpaToscaServiceTemplate createdServiceTemplate = new
+     * SimpleToscaProvider().createPolicies(pfDao, originalServiceTemplate);
+     *
+     * assertEquals(originalServiceTemplate, createdServiceTemplate); }
+     *
+     * @Test public void testPolicyUpdate() throws Exception { try { new SimpleToscaProvider().updatePolicies(null,
+     * null); fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().updatePolicies(null, new JpaToscaServiceTemplate());
+     * fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().updatePolicies(pfDao, null); fail("test should throw an exception here"); } catch
+     * (Exception exc) { assertEquals("serviceTemplate is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * ToscaServiceTemplate toscaServiceTemplate = standardCoder.decode(
+     * ResourceUtils.getResourceAsString("policies/vCPE.policy.monitoring.input.tosca.json"),
+     * ToscaServiceTemplate.class);
+     *
+     * JpaToscaServiceTemplate originalServiceTemplate = new JpaToscaServiceTemplate();
+     * originalServiceTemplate.fromAuthorative(toscaServiceTemplate);
+     *
+     * assertNotNull(originalServiceTemplate); JpaToscaServiceTemplate updatedServiceTemplate = new
+     * SimpleToscaProvider().updatePolicies(pfDao, originalServiceTemplate);
+     *
+     * assertEquals(originalServiceTemplate, updatedServiceTemplate); }
+     *
+     * @Test public void testPoliciesDelete() throws Exception { try { new SimpleToscaProvider().deletePolicy(null,
+     * null); fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().deletePolicy(null, new PfConceptKey());
+     * fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("dao is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * try { new SimpleToscaProvider().deletePolicy(pfDao, null); fail("test should throw an exception here"); } catch
+     * (Exception exc) { assertEquals("policyKey is marked @NonNull but is null", exc.getMessage()); }
+     *
+     * ToscaServiceTemplate toscaServiceTemplate = standardCoder.decode(
+     * ResourceUtils.getResourceAsString("policies/vCPE.policy.monitoring.input.tosca.json"),
+     * ToscaServiceTemplate.class);
+     *
+     * JpaToscaServiceTemplate originalServiceTemplate = new JpaToscaServiceTemplate();
+     * originalServiceTemplate.fromAuthorative(toscaServiceTemplate);
+     *
+     * assertNotNull(originalServiceTemplate); JpaToscaServiceTemplate createdServiceTemplate = new
+     * SimpleToscaProvider().createPolicies(pfDao, originalServiceTemplate);
+     *
+     * assertEquals(originalServiceTemplate, createdServiceTemplate);
+     *
+     * PfConceptKey policyKey = new PfConceptKey("onap.restart.tca:1.0.0");
+     *
+     * JpaToscaServiceTemplate deletedServiceTemplate = new SimpleToscaProvider().deletePolicy(pfDao, new
+     * PfConceptKey(policyKey));
+     *
+     * assertEquals(originalServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey),
+     * deletedServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey));
+     *
+     * try { new SimpleToscaProvider().getPolicies(pfDao, new PfConceptKey(policyKey));
+     * fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("policy not found: onap.restart.tca:1.0.0", exc.getMessage()); } }
+     *
+     * @Test public void testAssertPoliciesExist() throws PfModelException { JpaToscaServiceTemplate testServiceTemplate
+     * = new JpaToscaServiceTemplate();
+     *
+     * try { new SimpleToscaProvider().createPolicies(pfDao, testServiceTemplate);
+     * fail("test should throw an exception here"); } catch (Exception exc) {
+     * assertEquals("topology template not specified on service template", exc.getMessage()); }
+     *
+     * testServiceTemplate.setTopologyTemplate(new JpaToscaTopologyTemplate()); try { new
+     * SimpleToscaProvider().createPolicies(pfDao, testServiceTemplate); fail("test should throw an exception here"); }
+     * catch (Exception exc) { assertEquals("no policies specified on topology template of service template",
+     * exc.getMessage()); }
+     *
+     * testServiceTemplate.getTopologyTemplate().setPolicies(new JpaToscaPolicies()); try { new
+     * SimpleToscaProvider().createPolicies(pfDao, testServiceTemplate); fail("test should throw an exception here"); }
+     * catch (Exception exc) {
+     * assertEquals("list of policies specified on topology template of service template is empty", exc.getMessage()); }
+     *
+     * }
+     */
+}
diff --git a/models-pdp/src/test/resources/META-INF/persistence.xml b/models-pdp/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 0000000..079ba49
--- /dev/null
+++ b/models-pdp/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ============LICENSE_START=======================================================
+   Copyright (C) 2019 Nordix Foundation.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+  SPDX-License-Identifier: Apache-2.0
+  ============LICENSE_END=========================================================
+-->
+
+<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
+    <persistence-unit name="ToscaConceptTest" transaction-type="RESOURCE_LOCAL">
+        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+
+        <class>org.onap.policy.models.dao.converters.CDataConditioner</class>
+        <class>org.onap.policy.models.dao.converters.Uuid2String</class>
+        <class>org.onap.policy.models.base.PfConceptKey</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicy</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpGroup</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class>
+
+        <properties>
+            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
+            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb" />
+            <property name="javax.persistence.jdbc.user" value="policy" />
+            <property name="javax.persistence.jdbc.password" value="P01icY" />
+            <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
+            <property name="eclipselink.ddl-generation.output-mode" value="database" />
+            <property name="eclipselink.logging.level" value="INFO" />
+        </properties>
+    </persistence-unit>
+
+    <persistence-unit name="ToscaConceptMariaDBTest" transaction-type="RESOURCE_LOCAL">
+        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+
+        <class>org.onap.policy.models.dao.converters.CDataConditioner</class>
+        <class>org.onap.policy.models.dao.converters.Uuid2String</class>
+        <class>org.onap.policy.models.base.PfConceptKey</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicy</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpGroup</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class>
+        <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class>
+
+        <properties>
+            <property name="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver" />
+            <property name="javax.persistence.jdbc.url" value="jdbc:mariadb://localhost:3306/policy" />
+            <property name="javax.persistence.jdbc.user" value="policy" />
+            <property name="javax.persistence.jdbc.password" value="P01icY" />
+            <property name="javax.persistence.schema-generation.database.action" value="create" />
+
+            <property name="eclipselink.logging.level" value="ALL" />
+            <property name="eclipselink.logging.level.jpa" value="ALL" />
+            <property name="eclipselink.logging.level.ddl" value="ALL" />
+            <property name="eclipselink.logging.level.connection" value="ALL" />
+            <property name="eclipselink.logging.level.sql" value="ALL" />
+            <property name="eclipselink.logging.level.transaction" value="ALL" />
+            <property name="eclipselink.logging.level.sequencing" value="ALL" />
+            <property name="eclipselink.logging.level.server" value="ALL" />
+            <property name="eclipselink.logging.level.query" value="ALL" />
+            <property name="eclipselink.logging.level.properties" value="ALL" />
+
+            <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
+            <property name="eclipselink.ddl-generation.output-mode" value="database" />
+            <property name="eclipselink.logging.level" value="INFO" />
+        </properties>
+    </persistence-unit>
+</persistence>
diff --git a/models-pdp/src/test/resources/testdata/PdpGroups0.json b/models-pdp/src/test/resources/testdata/PdpGroups0.json
new file mode 100644
index 0000000..6ebdd6c
--- /dev/null
+++ b/models-pdp/src/test/resources/testdata/PdpGroups0.json
@@ -0,0 +1,43 @@
+{
+    "groups": [
+        {
+            "name": "PdpGroup0",
+            "version": "1.2.3",
+            "description": "group description",
+            "pdpGroupState": "PASSIVE",
+            "properties": {
+                "groupProperty0": "Value of Group Property 0"
+            },
+            "pdpSubgroups": [
+                {
+                    "pdpType": "APEX",
+                    "supportedPolicyTypes": [
+                        {
+                            "name": "policy.type.0",
+                            "version": "1.2.3"
+                        }
+                    ],
+                    "policies": [
+                        {
+                            "name": "Policy0",
+                            "version": "4.5.6"
+                        }
+                    ],
+                    "currentInstanceCount": 123,
+                    "desiredInstanceCount": 456,
+                    "properties": {
+                        "subgroupProperty0": "Value of sub Group Property 0"
+                    },
+                    "pdpInstances": [
+                        {
+                            "instanceId": "apex-0",
+                            "pdpState": "ACTIVE",
+                            "healthy": "NOT_HEALTHY",
+                            "message": "message from PDP"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file