[Developer] #424 show VPD policy function source
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
public record VpdFunctionSource(
|
||||||
|
String owner,
|
||||||
|
String objectName,
|
||||||
|
String objectType,
|
||||||
|
String source
|
||||||
|
) {
|
||||||
|
|
||||||
|
public boolean found() {
|
||||||
|
return source != null && !source.isBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,16 @@ package com.cloudhandson.vpdbackoffice.mapper;
|
|||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface VpdPolicyMapper {
|
public interface VpdPolicyMapper {
|
||||||
|
|
||||||
List<VpdPolicyView> findPolicies();
|
List<VpdPolicyView> findPolicies();
|
||||||
|
|
||||||
|
String findFunctionSource(
|
||||||
|
@Param("owner") String owner,
|
||||||
|
@Param("objectName") String objectName,
|
||||||
|
@Param("objectType") String objectType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.service;
|
package com.cloudhandson.vpdbackoffice.service;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -17,4 +19,33 @@ public class VpdPolicyService {
|
|||||||
public List<VpdPolicyView> findPolicies() {
|
public List<VpdPolicyView> findPolicies() {
|
||||||
return mapper.findPolicies();
|
return mapper.findPolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VpdFunctionSource findFunctionSource(String owner, String packageName, String functionName) {
|
||||||
|
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
||||||
|
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
||||||
|
String normalizedPackage = normalizeOptionalIdentifier(packageName);
|
||||||
|
String objectName = normalizedPackage == null ? normalizedFunction : normalizedPackage;
|
||||||
|
String objectType = normalizedPackage == null ? "FUNCTION" : "PACKAGE BODY";
|
||||||
|
String source = mapper.findFunctionSource(normalizedOwner, objectName, objectType);
|
||||||
|
return new VpdFunctionSource(normalizedOwner, objectName, objectType, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requiredIdentifier(String value, String label) {
|
||||||
|
String normalized = normalizeOptionalIdentifier(value);
|
||||||
|
if (normalized == null) {
|
||||||
|
throw new AppException(label + " 값이 없습니다.");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeOptionalIdentifier(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = value.trim().toUpperCase(Locale.ROOT);
|
||||||
|
if (!normalized.matches("[A-Z][A-Z0-9_$#]{0,127}")) {
|
||||||
|
throw new AppException("Oracle identifier 형식이 올바르지 않습니다: " + value);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.web;
|
package com.cloudhandson.vpdbackoffice.web;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.service.AppException;
|
||||||
import com.cloudhandson.vpdbackoffice.service.VpdPolicyService;
|
import com.cloudhandson.vpdbackoffice.service.VpdPolicyService;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class VpdPolicyController {
|
public class VpdPolicyController {
|
||||||
@@ -27,4 +29,22 @@ public class VpdPolicyController {
|
|||||||
}
|
}
|
||||||
return "vpd-policies";
|
return "vpd-policies";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/vpd-policies/function-source")
|
||||||
|
public String functionSource(
|
||||||
|
@RequestParam String owner,
|
||||||
|
@RequestParam(required = false) String packageName,
|
||||||
|
@RequestParam String functionName,
|
||||||
|
Model model
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
model.addAttribute("source", vpdPolicyService.findFunctionSource(owner, packageName, functionName));
|
||||||
|
} catch (AppException exception) {
|
||||||
|
model.addAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
model.addAttribute("errorMessage", message.message());
|
||||||
|
}
|
||||||
|
return "fragments/vpd-function-source :: source";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,12 @@
|
|||||||
FROM all_policies
|
FROM all_policies
|
||||||
ORDER BY object_owner, object_name, policy_name
|
ORDER BY object_owner, object_name, policy_name
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findFunctionSource" resultType="string">
|
||||||
|
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
||||||
|
FROM all_source
|
||||||
|
WHERE owner = UPPER(#{owner,jdbcType=VARCHAR})
|
||||||
|
AND name = UPPER(#{objectName,jdbcType=VARCHAR})
|
||||||
|
AND type = UPPER(#{objectType,jdbcType=VARCHAR})
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -457,6 +457,18 @@ body {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.policy-function-button {
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-source-cell {
|
||||||
|
background: var(--rw-surface) !important;
|
||||||
|
padding: 0 0 .75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ai-answer {
|
.ai-answer {
|
||||||
background: var(--rw-primary-soft);
|
background: var(--rw-primary-soft);
|
||||||
border: 1px solid var(--rw-border);
|
border: 1px solid var(--rw-border);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<body>
|
||||||
|
<div th:fragment="source">
|
||||||
|
<div class="alert alert-warning mb-0" th:if="${errorMessage}" th:text="${errorMessage}"></div>
|
||||||
|
<section class="probe-exchange" th:if="${source}">
|
||||||
|
<h3>
|
||||||
|
<span th:text="${source.owner() + '.' + source.objectName()}">OWNER.OBJECT</span>
|
||||||
|
<span class="text-muted" th:text="${' / ' + source.objectType()}"> / FUNCTION</span>
|
||||||
|
</h3>
|
||||||
|
<pre th:if="${source.found()}" th:text="${source.source()}"></pre>
|
||||||
|
<pre th:unless="${source.found()}">ALL_SOURCE에서 조회 가능한 소스가 없습니다. 현재 DB 계정의 소스 조회 권한을 확인하세요.</pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -34,13 +34,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr th:each="policy : ${policies}">
|
<th:block th:each="policy, iter : ${policies}">
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong th:text="${policy.objectDisplayName()}">ADMIN.TABLE</strong>
|
<strong th:text="${policy.objectDisplayName()}">ADMIN.TABLE</strong>
|
||||||
<div class="text-muted small" th:text="${policy.policyGroup()}">SYS_DEFAULT</div>
|
<div class="text-muted small" th:text="${policy.policyGroup()}">SYS_DEFAULT</div>
|
||||||
</td>
|
</td>
|
||||||
<td><code th:text="${policy.policyName()}">POLICY</code></td>
|
<td><code th:text="${policy.policyName()}">POLICY</code></td>
|
||||||
<td><code th:text="${policy.functionDisplayName()}">OWNER.FUNC</code></td>
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary policy-function-button"
|
||||||
|
type="button"
|
||||||
|
th:hx-get="@{/vpd-policies/function-source(owner=${policy.functionOwner()},packageName=${policy.packageName()},functionName=${policy.functionName()})}"
|
||||||
|
th:hx-target="${'#policy-source-' + iter.index}"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
<code th:text="${policy.functionDisplayName()}">OWNER.FUNC</code>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td th:text="${policy.statementTypes()} ?: '-'">SELECT</td>
|
<td th:text="${policy.statementTypes()} ?: '-'">SELECT</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge"
|
<span class="badge"
|
||||||
@@ -54,6 +63,14 @@
|
|||||||
<span class="ms-2">Long: <strong th:text="${policy.longPredicate()}">NO</strong></span>
|
<span class="ms-2">Long: <strong th:text="${policy.longPredicate()}">NO</strong></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="policy-source-cell">
|
||||||
|
<div th:id="${'policy-source-' + iter.index}" class="text-muted small">
|
||||||
|
Policy function을 클릭하면 소스가 표시됩니다.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</th:block>
|
||||||
<tr th:if="${#lists.isEmpty(policies)}">
|
<tr th:if="${#lists.isEmpty(policies)}">
|
||||||
<td colspan="7" class="text-muted">
|
<td colspan="7" class="text-muted">
|
||||||
조회 가능한 VPD policy가 없습니다. 정책이 없거나 현재 DB 계정에 dictionary 조회 권한이 없을 수 있습니다.
|
조회 가능한 VPD policy가 없습니다. 정책이 없거나 현재 DB 계정에 dictionary 조회 권한이 없을 수 있습니다.
|
||||||
|
|||||||
Reference in New Issue
Block a user