이 글은 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 가 궁금하면 아래 링크에 접속해보시기 바랍니다.