package com.cloudhandson.vpdbackoffice.service; import com.cloudhandson.vpdbackoffice.domain.structured.StructuredDataPreview; import com.cloudhandson.vpdbackoffice.domain.structured.StructuredDataTable; import java.util.List; import java.util.Map; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class StructuredDataService { private static final int ROW_LIMIT = 50; private final JdbcTemplate jdbcTemplate; private final DataCatalog catalog; public StructuredDataService( JdbcTemplate jdbcTemplate, DataCatalog catalog ) { this.jdbcTemplate = jdbcTemplate; this.catalog = catalog; } public DataCatalog catalog() { return catalog; } public String owner() { return catalog.owner(); } public List tables() { return catalog.objects(); } public String defaultKey() { return catalog.objects().getFirst().key(); } public StructuredDataTable requireTable(String key) { return catalog.require(key); } public StructuredDataPreview preview(String key) { StructuredDataTable table = requireTable(key); try { List columns = jdbcTemplate.query( """ SELECT column_name FROM all_tab_columns WHERE owner = ? AND table_name = ? ORDER BY column_id """, (resultSet, rowNum) -> resultSet.getString(1), catalog.owner(), table.tableName()); if (columns.isEmpty()) { throw new AppException("정형 데이터 테이블의 컬럼 정보를 찾을 수 없습니다."); } List previewColumns = table.previewColumns().isEmpty() ? columns : table.previewColumns(); if (!columns.containsAll(previewColumns)) { throw new AppException("환경 카탈로그의 미리보기 컬럼이 실제 객체와 일치하지 않습니다."); } List> rows = jdbcTemplate.queryForList( previewSql(table, previewColumns), ROW_LIMIT); return new StructuredDataPreview(table, previewColumns, rows, ROW_LIMIT); } catch (DataAccessException exception) { throw new AppException("정형 데이터를 조회할 수 없습니다. " + catalog.owner() + " 조회 권한과 대상 객체 상태를 확인하세요."); } } /** * The table is selected from a closed application whitelist, so the query * text remains fixed and no request value can become a SQL identifier. */ String previewSql(StructuredDataTable table) { return previewSql(table, table.previewColumns()); } private String previewSql( StructuredDataTable table, List previewColumns ) { StructuredDataTable approved = requireTable(table.key()); if (!approved.tableName().equals(table.tableName())) { throw new AppException("선택할 수 없는 카탈로그 객체입니다."); } String projection = previewColumns == null || previewColumns.isEmpty() ? "*" : previewColumns.stream() .map(column -> "\"" + column + "\"") .reduce((left, right) -> left + ", " + right) .orElseThrow(); return "SELECT " + projection + " FROM \"" + catalog.owner() + "\".\"" + approved.tableName() + "\" WHERE ROWNUM <= ?"; } }