From 547cc03a99d5c7392e6be628c8b2d350e715d094 Mon Sep 17 00:00:00 2001 From: "adheli.tavares" Date: Tue, 22 Jun 2021 13:50:52 +0100 Subject: PolicyAudit creation when deploy/undeploy triggered. Creates audits for policy when: - deploy - undeploy - undeploy when failure Issue-ID: POLICY-2899 Change-Id: Ib1a7cc4f826b5dceefcd5c7ba5250647f2cc0121 Signed-off-by: adheli.tavares --- .../policy/pap/main/rest/PapRestControllerV1.java | 26 +++- .../pap/main/rest/PdpGroupDeleteControllerV1.java | 4 +- .../pap/main/rest/PdpGroupDeleteProvider.java | 4 +- .../pap/main/rest/PdpGroupDeployControllerV1.java | 25 ++-- .../pap/main/rest/PdpGroupDeployProvider.java | 10 +- .../policy/pap/main/rest/PolicyAuditManager.java | 118 +++++++++++++++++ .../onap/policy/pap/main/rest/ProviderBase.java | 33 +++-- .../org/onap/policy/pap/main/rest/SessionData.java | 27 +++- main/src/main/resources/META-INF/persistence.xml | 3 +- .../pap/main/rest/PapRestControllerV1Test.java | 41 +++++- .../onap/policy/pap/main/rest/ProviderSuper.java | 2 + .../pap/main/rest/TestPdpGroupDeleteProvider.java | 30 ++--- .../pap/main/rest/TestPdpGroupDeployProvider.java | 139 ++++++++++----------- .../pap/main/rest/TestPolicyAuditManager.java | 96 ++++++++++++++ .../policy/pap/main/rest/TestProviderBase.java | 2 +- .../onap/policy/pap/main/rest/TestSessionData.java | 4 +- main/src/test/resources/META-INF/persistence.xml | 3 +- 17 files changed, 426 insertions(+), 141 deletions(-) create mode 100644 main/src/main/java/org/onap/policy/pap/main/rest/PolicyAuditManager.java create mode 100644 main/src/test/java/org/onap/policy/pap/main/rest/TestPolicyAuditManager.java diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PapRestControllerV1.java b/main/src/main/java/org/onap/policy/pap/main/rest/PapRestControllerV1.java index 43b6b48b..2dab2996 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/PapRestControllerV1.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PapRestControllerV1.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2019 Nordix Foundation. + * Copyright (C) 2019-2021 Nordix Foundation. * Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,8 +31,10 @@ import java.net.HttpURLConnection; import java.util.UUID; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.SecurityContext; import org.onap.policy.models.base.PfModelException; /** @@ -71,12 +73,12 @@ public class PapRestControllerV1 { public static final String VERSION_MINOR_NAME = "X-MinorVersion"; public static final String VERSION_MINOR_DESCRIPTION = - "Used to request or communicate a MINOR version back from the client" - + " to the server, and from the server back to the client"; + "Used to request or communicate a MINOR version back from the client" + + " to the server, and from the server back to the client"; public static final String VERSION_PATCH_NAME = "X-PatchVersion"; public static final String VERSION_PATCH_DESCRIPTION = "Used only to communicate a PATCH version in a response for" - + " troubleshooting purposes only, and will not be provided by" + " the client on request"; + + " troubleshooting purposes only, and will not be provided by" + " the client on request"; public static final String VERSION_LATEST_NAME = "X-LatestVersion"; public static final String VERSION_LATEST_DESCRIPTION = "Used only to communicate an API's latest version"; @@ -95,6 +97,9 @@ public class PapRestControllerV1 { public static final String AUTHORIZATION_ERROR_MESSAGE = "Authorization Error"; public static final String SERVER_ERROR_MESSAGE = "Internal Server Error"; + @Context + SecurityContext securityContext; + /** * Adds version headers to the response. * @@ -103,7 +108,7 @@ public class PapRestControllerV1 { */ public ResponseBuilder addVersionControlHeaders(ResponseBuilder respBuilder) { return respBuilder.header(VERSION_MINOR_NAME, "0").header(VERSION_PATCH_NAME, "0").header(VERSION_LATEST_NAME, - API_VERSION); + API_VERSION); } /** @@ -121,6 +126,17 @@ public class PapRestControllerV1 { return respBuilder.header(REQUEST_ID_NAME, requestId); } + /** + * Get the user principal name from security context. + * @return username as {@link String} + */ + public String getPrincipal() { + if (securityContext != null) { + return securityContext.getUserPrincipal().getName(); + } + return ""; + } + /** * Functions that throw {@link PfModelException}. */ diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteControllerV1.java b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteControllerV1.java index a76914a0..fac18626 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteControllerV1.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteControllerV1.java @@ -127,7 +127,7 @@ public class PdpGroupDeleteControllerV1 extends PapRestControllerV1 { @ApiParam(value = "PDP Policy Name", required = true) @PathParam("name") String policyName) { return doUndeployOperation(requestId, "undeploy policy failed", - () -> provider.undeploy(new ToscaConceptIdentifierOptVersion(policyName, null))); + () -> provider.undeploy(new ToscaConceptIdentifierOptVersion(policyName, null), getPrincipal())); } /** @@ -169,7 +169,7 @@ public class PdpGroupDeleteControllerV1 extends PapRestControllerV1 { @ApiParam(value = "PDP Policy Version", required = true) @PathParam("version") String version) { return doUndeployOperation(requestId, "undeploy policy failed", - () -> provider.undeploy(new ToscaConceptIdentifierOptVersion(policyName, version))); + () -> provider.undeploy(new ToscaConceptIdentifierOptVersion(policyName, version), getPrincipal())); } /** diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteProvider.java b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteProvider.java index f80b1bd1..6f39a715 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteProvider.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeleteProvider.java @@ -93,8 +93,8 @@ public class PdpGroupDeleteProvider extends ProviderBase { * @param policyIdent identifier of the policy to be undeployed * @throws PfModelException if an error occurred */ - public void undeploy(ToscaConceptIdentifierOptVersion policyIdent) throws PfModelException { - process(policyIdent, this::undeployPolicy); + public void undeploy(ToscaConceptIdentifierOptVersion policyIdent, String user) throws PfModelException { + process(user, policyIdent, this::undeployPolicy); } /** diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployControllerV1.java b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployControllerV1.java index 890d40bc..d8fe8fef 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployControllerV1.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployControllerV1.java @@ -4,6 +4,7 @@ * ================================================================================ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2021 Bell Canada. All rights reserved. + * Modifications Copyright (C) 2021 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,8 +51,8 @@ public class PdpGroupDeployControllerV1 extends PapRestControllerV1 { public static final String POLICY_STATUS_URI = "/policy/pap/v1/policies/status"; public static final String DEPLOYMENT_RESPONSE_MSG = "Use the policy status url to fetch the latest status. " - + "Kindly note that when a policy is successfully undeployed," - + " it will no longer appear in policy status response."; + + "Kindly note that when a policy is successfully undeployed," + + " it will no longer appear in policy status response."; private static final Logger logger = LoggerFactory.getLogger(PdpGroupDeployControllerV1.class); @@ -90,10 +91,10 @@ public class PdpGroupDeployControllerV1 extends PapRestControllerV1 { // @formatter:on public Response updateGroupPolicies( - @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, - @ApiParam(value = "List of PDP Group Deployments", required = true) DeploymentGroups groups) { - - return doOperation(requestId, "update policy deployments failed", () -> provider.updateGroupPolicies(groups)); + @HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "List of PDP Group Deployments", required = true) DeploymentGroups groups) { + return doOperation(requestId, "update policy deployments failed", + () -> provider.updateGroupPolicies(groups, getPrincipal())); } /** @@ -129,10 +130,9 @@ public class PdpGroupDeployControllerV1 extends PapRestControllerV1 { // @formatter:on public Response deployPolicies(@HeaderParam(REQUEST_ID_NAME) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, - @ApiParam(value = "PDP Policies; only the name is required", - required = true) PdpDeployPolicies policies) { - - return doOperation(requestId, "deploy policies failed", () -> provider.deployPolicies(policies)); + @ApiParam(value = "PDP Policies; only the name is required", required = true) PdpDeployPolicies policies) { + return doOperation(requestId, "deploy policies failed", + () -> provider.deployPolicies(policies, getPrincipal())); } /** @@ -147,15 +147,14 @@ public class PdpGroupDeployControllerV1 extends PapRestControllerV1 { try { runnable.run(); return addLoggingHeaders(addVersionControlHeaders(Response.status(Status.ACCEPTED)), requestId) - .entity(new PdpGroupDeployResponse(DEPLOYMENT_RESPONSE_MSG, POLICY_STATUS_URI)) - .build(); + .entity(new PdpGroupDeployResponse(DEPLOYMENT_RESPONSE_MSG, POLICY_STATUS_URI)).build(); } catch (PfModelException | PfModelRuntimeException e) { logger.warn(errmsg, e); var resp = new PdpGroupDeployResponse(); resp.setErrorDetails(e.getErrorResponse().getErrorMessage()); return addLoggingHeaders(addVersionControlHeaders(Response.status(e.getErrorResponse().getResponseCode())), - requestId).entity(resp).build(); + requestId).entity(resp).build(); } } } diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployProvider.java b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployProvider.java index 75affaf3..151e5145 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployProvider.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PdpGroupDeployProvider.java @@ -85,16 +85,17 @@ public class PdpGroupDeployProvider extends ProviderBase { * Updates policies in specific PDP groups. * * @param groups PDP group deployments to be updated + * @param user user triggering deployment * @throws PfModelException if an error occurred */ - public void updateGroupPolicies(DeploymentGroups groups) throws PfModelException { + public void updateGroupPolicies(DeploymentGroups groups, String user) throws PfModelException { ValidationResult result = groups.validatePapRest(); if (!result.isValid()) { String msg = result.getResult().trim(); throw new PfModelException(Status.BAD_REQUEST, msg); } - process(groups, this::updateGroups); + process(user, groups, this::updateGroups); } /** @@ -383,9 +384,10 @@ public class PdpGroupDeployProvider extends ProviderBase { * Deploys or updates PDP policies using the simple API. * * @param policies PDP policies + * @param user user triggering deployment * @throws PfModelException if an error occurred */ - public void deployPolicies(PdpDeployPolicies policies) throws PfModelException { + public void deployPolicies(PdpDeployPolicies policies, String user) throws PfModelException { try { MyPdpDeployPolicies checked = coder.convert(policies, MyPdpDeployPolicies.class); ValidationResult result = new BeanValidator().validateTop(PdpDeployPolicies.class.getSimpleName(), checked); @@ -397,7 +399,7 @@ public class PdpGroupDeployProvider extends ProviderBase { throw new PfModelException(Status.INTERNAL_SERVER_ERROR, "cannot decode request", e); } - process(policies, this::deploySimplePolicies); + process(user, policies, this::deploySimplePolicies); } /** diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/PolicyAuditManager.java b/main/src/main/java/org/onap/policy/pap/main/rest/PolicyAuditManager.java new file mode 100644 index 00000000..c200bb13 --- /dev/null +++ b/main/src/main/java/org/onap/policy/pap/main/rest/PolicyAuditManager.java @@ -0,0 +1,118 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.pap.main.rest; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import org.onap.policy.models.pap.concepts.PolicyAudit; +import org.onap.policy.models.pap.concepts.PolicyAudit.AuditAction; +import org.onap.policy.models.provider.PolicyModelsProvider; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to manage operations related to audit of policies. + * + * @author Adheli Tavares (adheli.tavares@est.tech) + * + */ +public class PolicyAuditManager { + private static final Logger logger = LoggerFactory.getLogger(PolicyAuditManager.class); + + /* + * Set of policies to be audited. + */ + @Getter(value = AccessLevel.PROTECTED) + private List auditRecords = new ArrayList<>(); + + private PolicyModelsProvider provider; + + /** + * Default constructor. + */ + public PolicyAuditManager(PolicyModelsProvider provider) { + this.provider = provider; + } + + /** + * Builds an audit object. + * + * @param policyId policy under action + * @param pdpGroup pdpGroup which the policy is related to + * @param pdpType pdp type + * @param action which action was taken on policy + * @param user which user started the action + * @return PolicyAudit object + */ + public PolicyAudit buildAudit(ToscaConceptIdentifier policyId, String pdpGroup, String pdpType, AuditAction action, + String user) { + return PolicyAudit.builder().action(action).pdpGroup(pdpGroup).pdpType(pdpType).policy(policyId) + .timestamp(Instant.now().truncatedTo(ChronoUnit.SECONDS)).user(user).build(); + } + + /** + * Add deployments to the list of audits. + * + * @param policyId policy under deploy + * @param pdpGroup PdpGroup + * @param pdpType PDP type + * @param user user whom triggered the deploy + */ + public void addDeploymentAudit(ToscaConceptIdentifier policyId, String pdpGroup, String pdpType, String user) { + logger.info("Registering a deploy for policy {}", policyId); + auditRecords.add(buildAudit(policyId, pdpGroup, pdpType, AuditAction.DEPLOYMENT, user)); + } + + /** + * Add deployments to the list of audits. + * + * @param policyId policy under undeploy + * @param pdpGroup pdpGroup which the policy is related to + * @param pdpType PDP type + * @param user user whom triggered the undeploy + */ + public void addUndeploymentAudit(ToscaConceptIdentifier policyId, String pdpGroup, String pdpType, String user) { + logger.info("Registering an undeploy for policy {}", policyId); + auditRecords.add(buildAudit(policyId, pdpGroup, pdpType, AuditAction.UNDEPLOYMENT, user)); + } + + /** + * Create audit registers in DB. + * If an exception happens, list is not cleared up, exception is logged. + */ + public void saveRecordsToDb() { + if (!auditRecords.isEmpty()) { + logger.info("sending audit records to database: {}", auditRecords); + try { + provider.createAuditRecords(auditRecords); + auditRecords.clear(); + } catch (RuntimeException excpt) { + // not throwing the exception to not stop the main request. + logger.error("Failed saving the audit records in DB.", excpt); + } + } + } +} diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/ProviderBase.java b/main/src/main/java/org/onap/policy/pap/main/rest/ProviderBase.java index 6fa0ef3d..d5ec8563 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/ProviderBase.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/ProviderBase.java @@ -52,6 +52,7 @@ import org.onap.policy.pap.main.notification.PolicyNotifier; */ public abstract class ProviderBase { public static final String DB_ERROR_MSG = "DB error"; + public static final String DEFAULT_USER = "PAP"; /** * Lock used when updating PDPs. @@ -86,11 +87,13 @@ public abstract class ProviderBase { /** * Processes a policy request. * + * @param user user triggering request * @param request PDP policy request * @param processor function that processes the request * @throws PfModelException if an error occurred */ - protected void process(T request, BiConsumerWithEx processor) throws PfModelException { + protected void process(String user, T request, BiConsumerWithEx processor) + throws PfModelException { synchronized (updateLock) { SessionData data; @@ -98,7 +101,7 @@ public abstract class ProviderBase { try (PolicyModelsProvider dao = daoFactory.create()) { - data = new SessionData(dao); + data = new SessionData(dao, user); processor.accept(data, request); // make all of the DB updates @@ -119,6 +122,17 @@ public abstract class ProviderBase { } } + /** + * Processes a policy request. + * + * @param request PDP policy request + * @param processor function that processes the request + * @throws PfModelException if an error occurred + */ + protected void process(T request, BiConsumerWithEx processor) throws PfModelException { + this.process(DEFAULT_USER, request, processor); + } + /** * Process a single policy from the request. * @@ -127,14 +141,14 @@ public abstract class ProviderBase { * @throws PfModelException if an error occurred */ protected void processPolicy(SessionData data, ToscaConceptIdentifierOptVersion desiredPolicy) - throws PfModelException { + throws PfModelException { ToscaPolicy policy = getPolicy(data, desiredPolicy); Collection groups = getGroups(data, policy.getTypeIdentifier()); if (groups.isEmpty()) { throw new PfModelException(Status.BAD_REQUEST, "policy not supported by any PDP group: " - + desiredPolicy.getName() + " " + desiredPolicy.getVersion()); + + desiredPolicy.getName() + " " + desiredPolicy.getVersion()); } var updater = makeUpdater(data, policy, desiredPolicy); @@ -155,7 +169,7 @@ public abstract class ProviderBase { * @return a function to update a subgroup */ protected abstract Updater makeUpdater(SessionData data, ToscaPolicy policy, - ToscaConceptIdentifierOptVersion desiredPolicy); + ToscaConceptIdentifierOptVersion desiredPolicy); /** * Finds the active PDP group(s) that supports the given policy type. @@ -167,7 +181,7 @@ public abstract class ProviderBase { * @throws PfModelException if an error occurred */ private Collection getGroups(SessionData data, ToscaConceptIdentifier policyType) - throws PfModelException { + throws PfModelException { return data.getActivePdpGroupsByPolicyType(policyType); } @@ -180,8 +194,7 @@ public abstract class ProviderBase { * @param updater function to update a group * @throws PfModelException if an error occurred */ - private void upgradeGroup(SessionData data, PdpGroup group, Updater updater) - throws PfModelException { + private void upgradeGroup(SessionData data, PdpGroup group, Updater updater) throws PfModelException { var updated = false; @@ -251,14 +264,14 @@ public abstract class ProviderBase { ToscaPolicy policy = data.getPolicy(ident); if (policy == null) { throw new PfModelRuntimeException(Status.NOT_FOUND, - "cannot find policy: " + ident.getName() + " " + ident.getVersion()); + "cannot find policy: " + ident.getName() + " " + ident.getVersion()); } return policy; } catch (PfModelException e) { throw new PfModelRuntimeException(e.getErrorResponse().getResponseCode(), - e.getErrorResponse().getErrorMessage(), e); + e.getErrorResponse().getErrorMessage(), e); } } diff --git a/main/src/main/java/org/onap/policy/pap/main/rest/SessionData.java b/main/src/main/java/org/onap/policy/pap/main/rest/SessionData.java index 235c852e..32103159 100644 --- a/main/src/main/java/org/onap/policy/pap/main/rest/SessionData.java +++ b/main/src/main/java/org/onap/policy/pap/main/rest/SessionData.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import lombok.Getter; import org.apache.commons.lang3.tuple.Pair; import org.onap.policy.models.base.PfModelException; import org.onap.policy.models.pap.concepts.PolicyNotification; @@ -103,20 +104,30 @@ public class SessionData { */ private Set policiesToBeUndeployed = new HashSet<>(); + /** + * User starting requests. + */ + @Getter + private final String user; + /** * Tracks policy deployment status so notifications can be generated. */ private final DeploymentStatus deployStatus; + private PolicyAuditManager auditManager; /** * Constructs the object. * * @param dao DAO provider + * @param user user triggering the request */ - public SessionData(PolicyModelsProvider dao) { + public SessionData(PolicyModelsProvider dao, String user) { this.dao = dao; this.deployStatus = makeDeploymentStatus(dao); + this.auditManager = makePolicyAuditManager(dao); + this.user = user; } /** @@ -435,6 +446,9 @@ public class SessionData { dao.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList())); } + // send audits records to DB + auditManager.saveRecordsToDb(); + // flush deployment status records to the DB deployStatus.flush(notification); } @@ -459,12 +473,13 @@ public class SessionData { * @param pdpType PDP type (i.e., PdpSubGroup) containing the PDP of interest * @throws PfModelException if an error occurred */ - protected void trackDeploy(ToscaPolicy policy, Collection pdps, String pdpGroup, - String pdpType) throws PfModelException { + protected void trackDeploy(ToscaPolicy policy, Collection pdps, String pdpGroup, String pdpType) + throws PfModelException { ToscaConceptIdentifier policyId = policy.getIdentifier(); policiesToBeDeployed.put(policyId, policy); addData(policyId, pdps, pdpGroup, pdpType, true); + auditManager.addDeploymentAudit(policyId, pdpGroup, pdpType, user); } /** @@ -479,7 +494,9 @@ public class SessionData { protected void trackUndeploy(ToscaConceptIdentifier policyId, Collection pdps, String pdpGroup, String pdpType) throws PfModelException { policiesToBeUndeployed.add(policyId); + addData(policyId, pdps, pdpGroup, pdpType, false); + auditManager.addUndeploymentAudit(policyId, pdpGroup, pdpType, user); } /** @@ -511,4 +528,8 @@ public class SessionData { protected DeploymentStatus makeDeploymentStatus(PolicyModelsProvider dao) { return new DeploymentStatus(dao); } + + protected PolicyAuditManager makePolicyAuditManager(PolicyModelsProvider dao) { + return new PolicyAuditManager(dao); + } } diff --git a/main/src/main/resources/META-INF/persistence.xml b/main/src/main/resources/META-INF/persistence.xml index a9624f35..f3fdcc4b 100644 --- a/main/src/main/resources/META-INF/persistence.xml +++ b/main/src/main/resources/META-INF/persistence.xml @@ -1,7 +1,7 @@