Post

Apache Fuseki API

Apache Fuseki API

1. Maven 의존성 추가

1
2
3
4
5
6
<dependency>
    <groupId>org.apache.jena</groupId>
    <artifactId>apache-jena-libs</artifactId>
    <type>pom</type>
    <version>6.0.0</version>
</dependency>

2. Indexer

온톨로지 파일(OWL, RDF, TTL 등)을 색인(Index)하거나 서버에 업로드(Insert)하는 방법이 있다.

Jena의 DatasetAccessor(GSP: Graph Store Protocol)를 사용하는 방식과 SPARQL UPDATE 쿼리를 사용하는 방식이 있다.

아래는 온톨로지 파일 전체를 데이터셋에 색인할 때 가장 빠르고 효율적인 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.sparql.exec.http.GSP;

public class Indexer {
    public static void main(String[] args) {
        // GSP(Graph Store Protocol) 엔드포인트는 /data를 사용
        String serviceEndpoint = "http://localhost:3030/my_dataset/data";

        // 모델 읽기
        Model model = RDFDataMgr.loadModel("ontology.ttl");

        // GSP 서비스를 사용하여 전송
        GSP.service(serviceEndpoint)
                .defaultGraph()
                .POST(model.getGraph()); // POST는 추가, PUT은 덮어쓰기

        System.out.println("색인 성공");
    }
}

3. SELECT

QueryExecution.service() 빌더 형식을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.RDFNode;

public class Select {
    public static void main(String[] args) {
        // Fuseki 서버 주소
        String serviceEndpoint = "http://localhost:3030/my_dataset/sparql";

        // SPARQL
        String queryString = """
                PREFIX ex: <http://example.org/>
                SELECT ?subject ?label
                WHERE {
                  ?subject rdfs:label ?label
                }
                LIMIT 20
                """;

        try (QueryExecution qexec = QueryExecution.service(serviceEndpoint)
                .query(queryString)
                // Fuseki 서버에 아이디/패스워드 설정한 경우
//                .httpClient(java.net.http.HttpClient.newBuilder()
//                        .authenticator(new java.net.Authenticator() {
//                            protected java.net.PasswordAuthentication getPasswordAuthentication() {
//                                return new java.net.PasswordAuthentication("admin", "password".toCharArray());
//                            }
//                        }).build())
                .build()) {

            // SELECT 결과 가져오기
            ResultSet results = qexec.execSelect();

            // 결과 루프
            while (results.hasNext()) {
                QuerySolution qs = results.nextSolution();

                // 리소스(URI)와 리터럴(값) 구분해서 가져오기
                RDFNode subject = qs.get("subject");
                RDFNode label = qs.get("label");

                System.out.println("subject: " + subject + " | label: " + label);
            }
        } catch (Exception e) {
            System.err.println("Fuseki 연결 실패: " + e.getMessage());
        }
    }
}

4. ParameterizedSparqlString

SQL의 PreparedStatement와 유사하게, 쿼리 템플릿에 변수를 안전하게 주입하여 SPARQL Injection 공격을 방지하고, URI나 리터럴의 복잡한 구문(따옴표 처리 등)을 라이브러리가 자동으로 해결해 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.apache.jena.query.ParameterizedSparqlString;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.query.ResultSetFormatter;

public class ParameterizedSparqlExample {
    public static void main(String[] args) {
        // Fuseki 서버 주소
        String fusekiService = "http://localhost:3030/my_dataset/sparql";

        // 쿼리 템플릿
        ParameterizedSparqlString pss = new ParameterizedSparqlString();
        pss.setCommandText("""
                PREFIX ex: <http://example.org/>
                SELECT ?name ?age
                WHERE {
                  ?person ex:id ?id ;
                          ex:name ?name ;
                          ex:age ?age .
                }
                """);

        // 파라미터 바인딩
        pss.setLiteral("id", 12345);

        // 특정 타입의 리터럴이나 URI를 주입할 때
//         pss.setIri("person", "http://example.org/user/john");

        // 실행
        try (QueryExecution qexec = QueryExecution.service(fusekiService)
                .query(pss.asQuery()) // ParameterizedSparqlString을 Query 객체로 변환
                .build()) {

            ResultSet results = qexec.execSelect();
            ResultSetFormatter.out(System.out, results);
        }
    }
}

5. Update

데이터를 수정(INSERT, DELETE, UPDATE)할 때 사용하는 API는 UpdateExecution이다. QueryExecution과 마찬가지로 빌더(Builder) 패턴을 사용하여 직관적이고 표준적인 HTTP 통신을 지원한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.sparql.exec.http.GSP;

public class Update {
    public static void main(String[] args) {
        // GSP 엔드포인트는 /data를 사용
        String gspEndpoint = "http://localhost:3030/my_dataset/data";

        // 파일 읽기
        Model model = RDFDataMgr.loadModel("ontology.ttl");

        // GSP 빌더 사용 (Jena 6 정석)
        GSP.service(gspEndpoint)
                .defaultGraph()
                .PUT(model.getGraph()); // POST는 추가, PUT은 덮어쓰기

        System.out.println("색인 완료");
    }
}

6. Delete

1) 조건부 삭제 (SPARQL DELETE)

특정 패턴에 맞는 데이터를 삭제할 때 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.apache.jena.update.UpdateExecution;
import org.apache.jena.update.UpdateFactory;

public class Delete {
    public static void main(String[] args) {
        // GSP 주소는 /update 엔드포인트를 사용
        String updateEndpoint = "http://localhost:3030/my_dataset/update";

        // 조건부 삭제 쿼리 (특정 URI와 관련된 모든 데이터 삭제 예시)
        String deleteQuery = """
                PREFIX ex: <http://example.org/ns#>
                DELETE WHERE {
                  ex:subject1 ?p ?o .
                }
                """;

        try {
            UpdateExecution.service(updateEndpoint)
                    .update(UpdateFactory.create(deleteQuery))
                    .build()
                    .execute();

            System.out.println("삭제 성공");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2) 그래프 전체 삭제 (GSP DELETE)

쿼리문 작성 없이 HTTP DELETE 메서드를 사용하여 특정 그래프(또는 기본 그래프)의 모든 데이터를 한 번에 삭제하는 방식이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.apache.jena.sparql.exec.http.GSP;

public class GspDelete {
    public static void main(String[] args) {
        // GSP 주소는 /data 엔드포인트를 사용
        String gspEndpoint = "http://localhost:3030/my_dataset/data";

        try {
            // 기본 그래프(Default Graph)의 모든 내용을 삭제
            GSP.service(gspEndpoint)
                    .defaultGraph()
                    .DELETE();

            // 특정 Named Graph를 삭제
//            GSP.service(gspEndpoint)
//               .graphName("http://example.org/myGraph")
//               .DELETE();

            System.out.println("전체 그래프 삭제 완료");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

7.Export Data

Dataset Store Protocol을 사용하여 데이터셋 전체(기본 그래프 + 모든 네임드 그래프)을 export 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.apache.jena.query.Dataset;
import org.apache.jena.query.DatasetFactory;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.riot.RDFFormat;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.exec.http.DSP;

import java.io.FileOutputStream;

public class ExportAll {
    public static void main(String[] args) {
        // Dataset Store Protocol (DSP) 엔드포인트
        // Fuseki에서 데이터셋 전체를 다룰 때는 보통 /data 또는 /get 엔드포인트를 사용
        String dspEndpoint = "http://localhost:3030/my_dataset/data";

        try {
            // 전체 데이터셋(Named Graphs 포함) 가져오기
            // DatasetGraph 반환
            DatasetGraph datasetGraph = DSP.service(dspEndpoint).GET();

            // DatasetGraph를 Dataset으로 변환
            Dataset dataset = DatasetFactory.wrap(datasetGraph);

            // 로컬 파일로 저장 (Named Graphs를 유지하기 위해 TriG 형식 사용)
            // TriG나 N-Quads는 여러 그래프를 한 파일에 저장할 수 있음
            try (FileOutputStream out = new FileOutputStream("export_dataset.trig")) {
                RDFDataMgr.write(out, dataset, RDFFormat.TRIG);
                System.out.println("전체 데이터셋(Named Graphs 포함) 내보내기 성공!");
                System.out.println("결과 파일: export_dataset.trig");
            }
        } catch (Exception e) {
            System.err.println("데이터 내보내기 실패: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.