[Developer] #424 support ORDS handler source editing
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.ords;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record OrdsHandlerUpdateCommand(
|
||||||
|
long handlerId,
|
||||||
|
@NotBlank String source
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.domain.ords;
|
package com.cloudhandson.vpdbackoffice.domain.ords;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public record OrdsHandlerView(
|
public record OrdsHandlerView(
|
||||||
|
Long handlerId,
|
||||||
String schemaName,
|
String schemaName,
|
||||||
|
String parsingSchema,
|
||||||
String moduleName,
|
String moduleName,
|
||||||
String basePath,
|
String basePath,
|
||||||
String template,
|
String template,
|
||||||
@@ -15,4 +19,8 @@ public record OrdsHandlerView(
|
|||||||
public String fullPath() {
|
public String fullPath() {
|
||||||
return "/ords/" + schemaName + "/" + basePath + template;
|
return "/ords/" + schemaName + "/" + basePath + template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean plsqlEditable() {
|
||||||
|
return sourceType != null && sourceType.toLowerCase(Locale.ROOT).contains("plsql");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package com.cloudhandson.vpdbackoffice.mapper;
|
|||||||
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
||||||
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 OrdsMetadataMapper {
|
public interface OrdsMetadataMapper {
|
||||||
|
|
||||||
List<OrdsHandlerView> findHandlers();
|
List<OrdsHandlerView> findHandlers();
|
||||||
|
|
||||||
|
OrdsHandlerView findHandler(@Param("handlerId") long handlerId);
|
||||||
|
|
||||||
String findHandlerPackageSource();
|
String findHandlerPackageSource();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.service;
|
package com.cloudhandson.vpdbackoffice.service;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerUpdateCommand;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
||||||
import com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper;
|
import com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class OrdsMetadataService {
|
public class OrdsMetadataService {
|
||||||
|
|
||||||
private final OrdsMetadataMapper mapper;
|
private final OrdsMetadataMapper mapper;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
public OrdsMetadataService(OrdsMetadataMapper mapper) {
|
public OrdsMetadataService(OrdsMetadataMapper mapper, JdbcTemplate jdbcTemplate) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OrdsHandlerView> findHandlers() {
|
public List<OrdsHandlerView> findHandlers() {
|
||||||
String packageSource = mapper.findHandlerPackageSource();
|
String packageSource = mapper.findHandlerPackageSource();
|
||||||
return mapper.findHandlers().stream()
|
return mapper.findHandlers().stream()
|
||||||
.map(handler -> new OrdsHandlerView(
|
.map(handler -> new OrdsHandlerView(
|
||||||
|
handler.handlerId(),
|
||||||
handler.schemaName(),
|
handler.schemaName(),
|
||||||
|
handler.parsingSchema(),
|
||||||
handler.moduleName(),
|
handler.moduleName(),
|
||||||
handler.basePath(),
|
handler.basePath(),
|
||||||
handler.template(),
|
handler.template(),
|
||||||
@@ -30,4 +38,37 @@ public class OrdsMetadataService {
|
|||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateHandlerSource(OrdsHandlerUpdateCommand command) {
|
||||||
|
if (command.source() == null || command.source().isBlank()) {
|
||||||
|
throw new AppException("ORDS Handler Source는 필수입니다.");
|
||||||
|
}
|
||||||
|
OrdsHandlerView handler = mapper.findHandler(command.handlerId());
|
||||||
|
if (handler == null) {
|
||||||
|
throw new AppException("수정할 ORDS Handler를 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
String sourceType = handler.sourceType() == null ? "" : handler.sourceType().toLowerCase(Locale.ROOT);
|
||||||
|
if (!sourceType.contains("plsql")) {
|
||||||
|
throw new AppException("현재는 PL/SQL ORDS Handler만 수정할 수 있습니다: " + handler.sourceType());
|
||||||
|
}
|
||||||
|
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||||
|
if (currentUser == null || !currentUser.equalsIgnoreCase(handler.parsingSchema())) {
|
||||||
|
throw new AppException("ORDS Handler 수정은 parsing schema로 DB에 연결했을 때만 가능합니다. 현재 연결 사용자: "
|
||||||
|
+ currentUser + ", Handler parsing schema: " + handler.parsingSchema());
|
||||||
|
}
|
||||||
|
jdbcTemplate.update("""
|
||||||
|
BEGIN
|
||||||
|
ORDS.DEFINE_HANDLER(
|
||||||
|
p_module_name => ?,
|
||||||
|
p_pattern => ?,
|
||||||
|
p_method => ?,
|
||||||
|
p_source_type => ORDS.source_type_plsql,
|
||||||
|
p_source => ?,
|
||||||
|
p_items_per_page => 0
|
||||||
|
);
|
||||||
|
COMMIT;
|
||||||
|
END;
|
||||||
|
""", handler.moduleName(), handler.template(), handler.method(), command.source());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.web;
|
package com.cloudhandson.vpdbackoffice.web;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerUpdateCommand;
|
||||||
|
import com.cloudhandson.vpdbackoffice.service.AppException;
|
||||||
import com.cloudhandson.vpdbackoffice.service.OrdsMetadataService;
|
import com.cloudhandson.vpdbackoffice.service.OrdsMetadataService;
|
||||||
|
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.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class OrdsHandlerController {
|
public class OrdsHandlerController {
|
||||||
@@ -19,4 +25,22 @@ public class OrdsHandlerController {
|
|||||||
model.addAttribute("handlers", ordsMetadataService.findHandlers());
|
model.addAttribute("handlers", ordsMetadataService.findHandlers());
|
||||||
return "ords-handlers";
|
return "ords-handlers";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ords-handlers/update")
|
||||||
|
public String update(
|
||||||
|
@RequestParam long handlerId,
|
||||||
|
@RequestParam String source,
|
||||||
|
RedirectAttributes redirectAttributes
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
ordsMetadataService.updateHandlerSource(new OrdsHandlerUpdateCommand(handlerId, source));
|
||||||
|
redirectAttributes.addFlashAttribute("message", "ORDS Handler Source를 저장했습니다.");
|
||||||
|
} catch (AppException exception) {
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage error = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
redirectAttributes.addFlashAttribute("errorMessage", error.message());
|
||||||
|
}
|
||||||
|
return "redirect:/ords-handlers";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,27 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper">
|
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper">
|
||||||
|
<sql id="handlerColumns">
|
||||||
|
h.id AS handler_id,
|
||||||
|
s.pattern AS schema_name,
|
||||||
|
s.parsing_schema,
|
||||||
|
m.name AS module_name,
|
||||||
|
m.uri_prefix AS base_path,
|
||||||
|
t.uri_template AS template,
|
||||||
|
h.method,
|
||||||
|
h.source_type,
|
||||||
|
DBMS_LOB.SUBSTR(h.source, 12000, 1) AS source,
|
||||||
|
(
|
||||||
|
SELECT LISTAGG(p.name || ' -> :' || p.bind_variable_name || ' (' || p.source_type || ')', CHR(10))
|
||||||
|
WITHIN GROUP (ORDER BY p.name)
|
||||||
|
FROM dba_ords_parameters p
|
||||||
|
WHERE p.handler_id = h.id
|
||||||
|
) AS parameters,
|
||||||
|
CAST(NULL AS VARCHAR2(1)) AS package_source
|
||||||
|
</sql>
|
||||||
|
|
||||||
<select id="findHandlers" resultType="com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView">
|
<select id="findHandlers" resultType="com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView">
|
||||||
SELECT s.pattern AS schema_name,
|
SELECT <include refid="handlerColumns"/>
|
||||||
m.name AS module_name,
|
|
||||||
m.uri_prefix AS base_path,
|
|
||||||
t.uri_template AS template,
|
|
||||||
h.method,
|
|
||||||
h.source_type,
|
|
||||||
DBMS_LOB.SUBSTR(h.source, 12000, 1) AS source,
|
|
||||||
(
|
|
||||||
SELECT LISTAGG(p.name || ' -> :' || p.bind_variable_name || ' (' || p.source_type || ')', CHR(10))
|
|
||||||
WITHIN GROUP (ORDER BY p.name)
|
|
||||||
FROM dba_ords_parameters p
|
|
||||||
WHERE p.handler_id = h.id
|
|
||||||
) AS parameters,
|
|
||||||
CAST(NULL AS VARCHAR2(1)) AS package_source
|
|
||||||
FROM dba_ords_schemas s
|
FROM dba_ords_schemas s
|
||||||
JOIN dba_ords_modules m ON m.schema_id = s.id
|
JOIN dba_ords_modules m ON m.schema_id = s.id
|
||||||
JOIN dba_ords_templates t ON t.module_id = m.id
|
JOIN dba_ords_templates t ON t.module_id = m.id
|
||||||
@@ -25,6 +31,16 @@
|
|||||||
ORDER BY m.name, t.uri_template, h.method
|
ORDER BY m.name, t.uri_template, h.method
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findHandler" resultType="com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView">
|
||||||
|
SELECT <include refid="handlerColumns"/>
|
||||||
|
FROM dba_ords_schemas s
|
||||||
|
JOIN dba_ords_modules m ON m.schema_id = s.id
|
||||||
|
JOIN dba_ords_templates t ON t.module_id = m.id
|
||||||
|
JOIN dba_ords_handlers h ON h.template_id = t.id
|
||||||
|
WHERE s.parsing_schema = 'CB_ORDS'
|
||||||
|
AND h.id = #{handlerId,jdbcType=NUMERIC}
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findHandlerPackageSource" resultType="string">
|
<select id="findHandlerPackageSource" resultType="string">
|
||||||
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
||||||
FROM all_source
|
FROM all_source
|
||||||
|
|||||||
@@ -347,6 +347,32 @@ body {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-editor {
|
||||||
|
background: #211f1d;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: #fbf9f7;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||||
|
font-size: .78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
min-height: 320px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-editor:focus {
|
||||||
|
background: #211f1d;
|
||||||
|
color: #fbf9f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
align-items: center;
|
||||||
|
background: var(--rw-surface-muted);
|
||||||
|
border-top: 1px solid var(--rw-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: .55rem .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.table-pre {
|
.table-pre {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
<p>Bearer Token을 DB 컨텍스트로 변환하는 ORDS Handler와 PL/SQL 흐름을 확인합니다.</p>
|
<p>Bearer Token을 DB 컨텍스트로 변환하는 ORDS Handler와 PL/SQL 흐름을 확인합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
||||||
|
<div class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}"></div>
|
||||||
|
|
||||||
<section class="content-band" th:each="handler : ${handlers}">
|
<section class="content-band" th:each="handler : ${handlers}">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2 th:text="${handler.method() + ' ' + handler.fullPath()}">POST /ords/...</h2>
|
<h2 th:text="${handler.method() + ' ' + handler.fullPath()}">POST /ords/...</h2>
|
||||||
@@ -16,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="meta-row">
|
<div class="meta-row">
|
||||||
<span>Schema: <strong th:text="${handler.schemaName()}">CB_ORDS</strong></span>
|
<span>Schema: <strong th:text="${handler.schemaName()}">CB_ORDS</strong></span>
|
||||||
|
<span>Parsing Schema: <strong th:text="${handler.parsingSchema()}">CB_ORDS</strong></span>
|
||||||
<span>Module: <strong th:text="${handler.moduleName()}">cb.agent.security</strong></span>
|
<span>Module: <strong th:text="${handler.moduleName()}">cb.agent.security</strong></span>
|
||||||
<span>Template: <strong th:text="${handler.template()}">vpd/documents</strong></span>
|
<span>Template: <strong th:text="${handler.template()}">vpd/documents</strong></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +30,18 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="probe-exchange">
|
<section class="probe-exchange">
|
||||||
<h3>Handler Source</h3>
|
<h3>Handler Source</h3>
|
||||||
<pre th:text="${handler.source()}"></pre>
|
<form method="post" action="/ords-handlers/update">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
<input type="hidden" name="handlerId" th:value="${handler.handlerId()}">
|
||||||
|
<textarea class="form-control code-editor" name="source" rows="16" spellcheck="false"
|
||||||
|
th:text="${handler.source()}"></textarea>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-sm rw-btn-primary" type="submit"
|
||||||
|
th:disabled="${!handler.plsqlEditable()}">
|
||||||
|
저장
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section class="probe-exchange span-2">
|
<section class="probe-exchange span-2">
|
||||||
<h3>Bearer Token -> User Context Package</h3>
|
<h3>Bearer Token -> User Context Package</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user