[Spring] GeoServer Layer 의 PostGIS 스키마 및 테이블 명칭 알아내기

식빵·2023년 4월 14일
1

Spring Lab

목록 보기
20/35

이 글은 GIS 에서 사용되는 Geoserver 가 뭔지 알아야 읽을 수 있는 코드입니다.
GeoServer 가 뭔지 모르며, GIS 분야에 관심이 없다면 이 코드를 읽는 걸 추천하지 않습니다.

package me.dailycode.dao;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import me.dailycode.vo.LyrRefTableVO;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.HashMap;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;

/**
 * <h2>GeoServer 메타정보 조회 DAO</h2>
 */
@Repository("geoServerSimpleQueryRepository")
public class GeoServerSimpleQueryRepository {

    private RestTemplate restTemplate;
    private HttpHeaders httpHeaders;
    private UriComponentsBuilder builder;

    @Value("#{'${geoserver.url}:${geoserver.port}/geoserver/rest/'}")
    private String geoServerRestApiUrl;

    @Value("#{'${geoserver.user}:${geoserver.password}'}")
    private String basicAuthString;


    @PostConstruct
    public void init() {

        // 만약 성능이 중요한 코드면 apache httpClient 사용하는 Factory 를 적용합니다.
        // HttpComponentsClientHttpRequestFactory requestFactory 
        //    = new HttpComponentsClientHttpRequestFactory();

        // 성능이 중요하지 않다면 그냥 java 기본 httpClient 를 사용하는 Factory 를 적용합니다.
        SimpleClientHttpRequestFactory requestFactory 
        	= new SimpleClientHttpRequestFactory();

        // 타임아웃은 기본으로 걸어준다. 안 하면 10초 이상을 대기한다.
        requestFactory.setConnectTimeout(3000);
        requestFactory.setReadTimeout(3000);

        restTemplate = new RestTemplate(requestFactory);

        // 한글 깨짐 방지 - 이거는 jackson 버전에 따라 약간 상이한 거 같습니다.
        restTemplate.getMessageConverters()
                .add(0, new StringHttpMessageConverter(UTF_8));

        httpHeaders = new HttpHeaders();
        byte[] bytes = Base64.encodeBase64(basicAuthString.getBytes(UTF_8));
        httpHeaders.set("Authorization", "Basic " + new String(bytes));

        httpHeaders.setAccept(singletonList(MediaType.APPLICATION_JSON));
        builder = UriComponentsBuilder.fromUriString(geoServerRestApiUrl);

    }


    /**
     * <h2>레이어 참조 테이블 정보 조회(Layer Referencing Table Information Query)</h2>
     * geoserver 에 배포한 PostGIS 기반의 Layer 에 대한 실제 참조하는 <br>
     * 데이터베이스, 스키마, 테이블 명 정보를 조회한다.
     *
     * @return [데이터베이스_명, 스키마_명, 테이블_명] 정보를 갖는 LyrRefTableVO 객체
     */
    public LyrRefTableVO getLyrRefTableInfo(String workspaces, 
    										String datastores,
    										String featuretypes) {

        // 만약에 argument 가 null 이거나 공백 문자열이면 예외를 던집니다.
        if (!StringUtils.hasText(workspaces) || 
        	!StringUtils.hasText(datastores)  ||
            !StringUtils.hasText(featuretypes)) {
            throw new RuntimeException("모든 파라미터는 필수값입니다");
        }

        // layer 가 참조하는 PostGIS 테이블 명을 구하기 위한 rest api url 생성
        HashMap<String, String> expand = new HashMap<>();
        expand.put("workspaces", workspaces);
        expand.put("datastores", datastores);
        expand.put("featuretypes", featuretypes);
        UriComponents build
                = builder.cloneBuilder()
                .path("/workspaces/{workspaces}")
                .path("/datastores/{datastores}")
                .path("/featuretypes/{featuretypes}").buildAndExpand(expand);

        
        // 테이블 명칭 을 알아내기 위해 geoserver rest api 에 요청을 보냅니다.
        ResponseEntity<ObjectNode> featureTypesInfo
                = restTemplate.exchange(
                	build.toUri(),
                    HttpMethod.GET,
                    new HttpEntity<>(httpHeaders),
                    ObjectNode.class
                  );


        // 실제 참조하는 테이블 명칭(=nativeName) 조회합니다.
        ObjectNode body = featureTypesInfo.getBody();
        String table = body.findValue("nativeName").asText();


        // 이어서 스키마 정보를 조회하기 위한 2번째 rest 요청 수행합니다.
        String storeHref = body.findPath("store").path("href").asText();
        ResponseEntity<ObjectNode> dataStoreInfo
                = restTemplate.exchange(
                		URI.create(storeHref),
                        HttpMethod.GET,
                        new HttpEntity<>(httpHeaders),
                        ObjectNode.class);


        // 데이터베이스, 스키마 명 조회합니다.
        JsonNode arrayNode = dataStoreInfo.getBody()
                .findPath("connectionParameters")
                .findPath("entry");


        // 복잡해 보이지만 배열형태의 json 을 순회하면서 database 명, 
        // schema 명을 조회하는 코드입니다.
        String schema = null;
        String database = null;
        for (JsonNode jsonNode : arrayNode) {
            if (StringUtils.hasText(schema) && StringUtils.hasText(database)) {
                // 만약에 database, schema 명 모두 조회되면, 
                // 기존에 구한 table 명과 함게 묶어서 return 합니다.
                return LyrRefTableVO.builder()
                        .database(database)
                        .schema(schema)
                        .table(table).build();
            } else if ("schema".equals(jsonNode.get("@key").asText())) {
                schema = jsonNode.get("$").asText();
            } else if ("database".equals(jsonNode.get("@key").asText())) {
                database = jsonNode.get("$").asText();
            }
        }

        // 만약에 여기까지 오면 정보를 못 찾은 것으로 간주하여 예외를 던집니다.
        throw new RuntimeException("No DataBase (or Schema) Info found!");
    }

    /**
     * geoserver 에 배포한 PostGIS 기반의 Layer 가 참조하는<br>
     * {데이터베이스.스키마.테이블} 명을 문자열을 반환한다.<br>
     * 자세한 메소드 동작 방식 및 주석 설명은 {@link #getLyrRefTableInfo}을 읽어보기 바란다.
     * @return "{데이터베이스 명}.{스키마명}.{테이블 명}" 포맷의 문자열
     */
    public String getLyrRefTableName(String workspaces,
                                     String datastores,
                                     String featuretypes) {
        LyrRefTableVO lyrRefTableInfo 
        	= getLyrRefTableInfo(workspaces, datastores, featuretypes);
        
        return String.format("%s.%s.%s", 
        			lyrRefTableInfo.getDatabase(),
               		lyrRefTableInfo.getSchema(),
                	lyrRefTableInfo.getTable());
    }
}



위 코드에서 사용되는 LyrRefTableVO 는 아래와 같습니다.

package me.dailycode.vo;

import lombok.*;

/**
 * geoserver Layer 의 PostGIS Table 메타 정보를 담는 VO
 */
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class LyrRefTableVO {

    /**
     * 데이터베이스 명
     */
    private String database;

    /**
     * 스키마 명
     */
    private String schema;

    /**
     * 테이블 명
     */
    private String table;
}



GeoServer 가 제공하는 Rest API 가 궁금하면 아래 링크에 접속해보시기 바랍니다.

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글