fix #477: filter vpd targets by schema
This commit is contained in:
@@ -13,7 +13,7 @@ public interface VpdPolicyMapper {
|
|||||||
|
|
||||||
List<VpdPolicyView> findPolicies();
|
List<VpdPolicyView> findPolicies();
|
||||||
|
|
||||||
List<VpdTargetView> findVpdTargets();
|
List<VpdTargetView> findVpdTargets(@Param("owner") String owner);
|
||||||
|
|
||||||
List<String> findPolicyNameOptions();
|
List<String> findPolicyNameOptions();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import com.cloudhandson.vpdbackoffice.domain.vpd.VpdTargetView;
|
|||||||
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 java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -27,7 +29,7 @@ public class VpdPolicyService {
|
|||||||
private final VpdPolicyMapper mapper;
|
private final VpdPolicyMapper mapper;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final OpenAiCompatibleClient aiClient;
|
private final OpenAiCompatibleClient aiClient;
|
||||||
private volatile CacheEntry<List<VpdTargetView>> vpdTargetsCache;
|
private final Map<String, CacheEntry<List<VpdTargetView>>> vpdTargetsCache = new ConcurrentHashMap<>();
|
||||||
private volatile CacheEntry<VpdPolicyFormOptions> formOptionsCache;
|
private volatile CacheEntry<VpdPolicyFormOptions> formOptionsCache;
|
||||||
|
|
||||||
public VpdPolicyService(VpdPolicyMapper mapper, JdbcTemplate jdbcTemplate, OpenAiCompatibleClient aiClient) {
|
public VpdPolicyService(VpdPolicyMapper mapper, JdbcTemplate jdbcTemplate, OpenAiCompatibleClient aiClient) {
|
||||||
@@ -41,12 +43,18 @@ public class VpdPolicyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<VpdTargetView> findVpdTargets() {
|
public List<VpdTargetView> findVpdTargets() {
|
||||||
CacheEntry<List<VpdTargetView>> cached = vpdTargetsCache;
|
return findVpdTargets(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<VpdTargetView> findVpdTargets(String owner) {
|
||||||
|
String normalizedOwner = normalizeOptionalIdentifier(owner);
|
||||||
|
String cacheKey = normalizedOwner == null ? "__MANAGED__" : normalizedOwner;
|
||||||
|
CacheEntry<List<VpdTargetView>> cached = vpdTargetsCache.get(cacheKey);
|
||||||
if (cached != null && !cached.expired()) {
|
if (cached != null && !cached.expired()) {
|
||||||
return cached.value();
|
return cached.value();
|
||||||
}
|
}
|
||||||
List<VpdTargetView> targets = List.copyOf(mapper.findVpdTargets());
|
List<VpdTargetView> targets = List.copyOf(mapper.findVpdTargets(normalizedOwner));
|
||||||
vpdTargetsCache = new CacheEntry<>(targets, System.currentTimeMillis() + CATALOG_CACHE_MILLIS);
|
vpdTargetsCache.put(cacheKey, new CacheEntry<>(targets, System.currentTimeMillis() + CATALOG_CACHE_MILLIS));
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +281,7 @@ public class VpdPolicyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clearCatalogCache() {
|
public void clearCatalogCache() {
|
||||||
vpdTargetsCache = null;
|
vpdTargetsCache.clear();
|
||||||
formOptionsCache = null;
|
formOptionsCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,27 +22,42 @@ public class VpdPolicyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/vpd-policies")
|
@GetMapping("/vpd-policies")
|
||||||
public String policies(Model model) {
|
public String policies(
|
||||||
populatePolicyModel(model);
|
@RequestParam(required = false) String schemaOwner,
|
||||||
|
Model model
|
||||||
|
) {
|
||||||
|
populatePolicyModel(schemaOwner, model);
|
||||||
return "vpd-policies";
|
return "vpd-policies";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/vpd-filter-policies")
|
@GetMapping("/vpd-filter-policies")
|
||||||
public String filterPolicies(Model model) {
|
public String filterPolicies(
|
||||||
populatePolicyModel(model);
|
@RequestParam(required = false) String schemaOwner,
|
||||||
|
Model model
|
||||||
|
) {
|
||||||
|
populatePolicyModel(schemaOwner, model);
|
||||||
return "vpd-filter-policies";
|
return "vpd-filter-policies";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populatePolicyModel(Model model) {
|
private void populatePolicyModel(String schemaOwner, Model model) {
|
||||||
try {
|
try {
|
||||||
|
String selectedSchemaOwner = schemaOwner == null ? "" : schemaOwner.trim().toUpperCase();
|
||||||
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
||||||
model.addAttribute("vpdTargets", vpdPolicyService.findVpdTargets());
|
model.addAttribute("vpdTargets", vpdPolicyService.findVpdTargets(selectedSchemaOwner));
|
||||||
|
model.addAttribute("selectedSchemaOwner", selectedSchemaOwner);
|
||||||
model.addAttribute("formOptions", vpdPolicyService.formOptions());
|
model.addAttribute("formOptions", vpdPolicyService.formOptions());
|
||||||
} catch (DataAccessException exception) {
|
} catch (DataAccessException exception) {
|
||||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
model.addAttribute("runtimeError", message);
|
model.addAttribute("runtimeError", message);
|
||||||
model.addAttribute("policies", List.of());
|
model.addAttribute("policies", List.of());
|
||||||
model.addAttribute("vpdTargets", List.of());
|
model.addAttribute("vpdTargets", List.of());
|
||||||
|
model.addAttribute("selectedSchemaOwner", "");
|
||||||
|
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
||||||
|
} catch (AppException exception) {
|
||||||
|
model.addAttribute("errorMessage", exception.getMessage());
|
||||||
|
model.addAttribute("policies", List.of());
|
||||||
|
model.addAttribute("vpdTargets", List.of());
|
||||||
|
model.addAttribute("selectedSchemaOwner", "");
|
||||||
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,10 @@
|
|||||||
SELECT owner, object_name, object_type
|
SELECT owner, object_name, object_type
|
||||||
FROM all_objects
|
FROM all_objects
|
||||||
WHERE object_type IN ('TABLE', 'VIEW')
|
WHERE object_type IN ('TABLE', 'VIEW')
|
||||||
AND owner IN (SELECT owner FROM managed_owners)
|
AND (
|
||||||
|
(#{owner,jdbcType=VARCHAR} IS NULL AND owner IN (SELECT owner FROM managed_owners))
|
||||||
|
OR owner = UPPER(#{owner,jdbcType=VARCHAR})
|
||||||
|
)
|
||||||
AND owner NOT IN ('SYS', 'SYSTEM', 'ORDS_METADATA', 'ORDS_PUBLIC_USER')
|
AND owner NOT IN ('SYS', 'SYSTEM', 'ORDS_METADATA', 'ORDS_PUBLIC_USER')
|
||||||
AND owner NOT LIKE 'APEX\_%' ESCAPE '\'
|
AND owner NOT LIKE 'APEX\_%' ESCAPE '\'
|
||||||
AND owner NOT LIKE 'C##%'
|
AND owner NOT LIKE 'C##%'
|
||||||
|
|||||||
@@ -16,6 +16,33 @@
|
|||||||
<div class="alert alert-success" th:if="${successMessage}" th:text="${successMessage}">등록되었습니다.</div>
|
<div class="alert alert-success" th:if="${successMessage}" th:text="${successMessage}">등록되었습니다.</div>
|
||||||
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}">처리할 수 없습니다.</div>
|
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}">처리할 수 없습니다.</div>
|
||||||
|
|
||||||
|
<section class="content-band">
|
||||||
|
<div class="section-heading">
|
||||||
|
<div>
|
||||||
|
<h2>VPD 적용 대상 Schema</h2>
|
||||||
|
<p class="section-subtitle">개별 적용 드롭다운에 표시할 TABLE/VIEW 스키마를 선택합니다.</p>
|
||||||
|
</div>
|
||||||
|
<span class="badge text-bg-secondary" th:text="${#lists.size(vpdTargets)}">0</span>
|
||||||
|
</div>
|
||||||
|
<form method="get" action="/vpd-filter-policies" class="inline-form">
|
||||||
|
<label>
|
||||||
|
Schema
|
||||||
|
<select class="form-select form-select-sm" name="schemaOwner" onchange="this.form.submit()">
|
||||||
|
<option value="" th:selected="${selectedSchemaOwner == null || selectedSchemaOwner == ''}">관리 대상 스키마</option>
|
||||||
|
<option th:each="owner : ${formOptions.schemaOwners()}"
|
||||||
|
th:value="${owner}"
|
||||||
|
th:text="${owner}"
|
||||||
|
th:selected="${owner == selectedSchemaOwner}"></option>
|
||||||
|
<option th:if="${selectedSchemaOwner != null && selectedSchemaOwner != '' && !#lists.contains(formOptions.schemaOwners(), selectedSchemaOwner)}"
|
||||||
|
th:value="${selectedSchemaOwner}"
|
||||||
|
th:text="${selectedSchemaOwner + ' / 조회 가능한 객체 없음'}"
|
||||||
|
selected></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button class="btn btn-sm rw-btn-secondary" type="submit">조회</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>Filter 등록/수정</h2>
|
<h2>Filter 등록/수정</h2>
|
||||||
|
|||||||
@@ -30,10 +30,27 @@
|
|||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>VPD 적용 대상 TABLE/VIEW</h2>
|
<h2>VPD 적용 대상 TABLE/VIEW</h2>
|
||||||
<p class="section-subtitle">Oracle DB catalog 기준의 TABLE/VIEW 목록입니다. ORDS는 이 객체를 HTTP로 서빙하는 별도 레이어입니다.</p>
|
<p class="section-subtitle">Oracle DB catalog 기준의 TABLE/VIEW 목록입니다. Schema를 선택하면 해당 스키마 객체를 조회합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge text-bg-secondary" th:text="${#lists.size(vpdTargets)}">0</span>
|
<span class="badge text-bg-secondary" th:text="${#lists.size(vpdTargets)}">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="get" action="/vpd-policies" class="inline-form mb-3">
|
||||||
|
<label>
|
||||||
|
Schema
|
||||||
|
<select class="form-select form-select-sm" name="schemaOwner" onchange="this.form.submit()">
|
||||||
|
<option value="" th:selected="${selectedSchemaOwner == null || selectedSchemaOwner == ''}">관리 대상 스키마</option>
|
||||||
|
<option th:each="owner : ${formOptions.schemaOwners()}"
|
||||||
|
th:value="${owner}"
|
||||||
|
th:text="${owner}"
|
||||||
|
th:selected="${owner == selectedSchemaOwner}"></option>
|
||||||
|
<option th:if="${selectedSchemaOwner != null && selectedSchemaOwner != '' && !#lists.contains(formOptions.schemaOwners(), selectedSchemaOwner)}"
|
||||||
|
th:value="${selectedSchemaOwner}"
|
||||||
|
th:text="${selectedSchemaOwner + ' / 조회 가능한 객체 없음'}"
|
||||||
|
selected></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button class="btn btn-sm rw-btn-secondary" type="submit">조회</button>
|
||||||
|
</form>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle">
|
<table class="table table-sm align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user