Jena RDF api 활용하기 (3)

     

벌써 3번째 포스팅이네요. 생각보다 내용이 많네요 .. 아직 반도 못한듯..


일단 참고하는 홈페이지 설명서 주소는 다음과 같습니다.


http://jena.sourceforge.net/tutorial/RDF_API/index.html#ch-Navigating%20a%20Model


Controlling prefix


지난시간에 외부에  파일을 읽고 쓰는 것까지 했습니다. 홈페이지에서 보면 Reading RDF까지 했군요. 다음에 나오는것은 프리픽스 컨트롤입니다. 프리픽스라고 하는것은 네임스페이스의 이름같은 것을 말합니다. 일단 프리픽스라는것 자체의 뜻은 접두어를 뜻하는데 RDF에서는 네임스페이스의 이름으로 정의하고 해당 네임스페이스가 사용된 소스 앞에 prefix:property 식으로 사용됩니다. 이런 프리픽스를 코드상에서 컨트롤 가능하다는 소리인데 다음 예제를 보시면 이해가 빠르게 가실 겁니다.


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
 
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.util.FileManager;
 
import java.io.*;
 
 
public class Main extends Object {
 
                              
    public static void main (String args[]) {
 
        Model m = ModelFactory.createDefaultModel();
         String nsA = "http://somewhere/else#";
         String nsB = "http://nowhere/else#";
         Resource root = m.createResource( nsA + "root" );
         Property P = m.createProperty( nsA + "P" );
         Property Q = m.createProperty( nsB + "Q" );
         Resource x = m.createResource( nsA + "x" );
         Resource y = m.createResource( nsA + "y" );
         Resource z = m.createResource( nsA + "z" );
         m.add( root, P, x ).add( root, P, y ).add( y, Q, z );
         System.out.println"# -- no special prefixes defined" );
         m.write( System.out );
         System.out.println"# -- nsA defined" );
         m.setNsPrefix( "nsA", nsA );
         m.write( System.out );
         System.out.println"# -- nsA and cat defined" );
         m.setNsPrefix( "cat", nsB );
         m.write( System.out );
       
    }
}
 


위에 코드를 실행하면 다음과 같은 결과가 출력됩니다.


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
# -- no special prefixes defined
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:j.0="http://nowhere/else#"
    xmlns:j.1="http://somewhere/else#" > 
  <rdf:Description rdf:about="http://somewhere/else#y">
    <j.0:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#root">
    <j.1:P rdf:resource="http://somewhere/else#y"/>
    <j.1:P rdf:resource="http://somewhere/else#x"/>
  </rdf:Description>
</rdf:RDF>
# -- nsA defined
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:j.0="http://nowhere/else#"
    xmlns:nsA="http://somewhere/else#" > 
  <rdf:Description rdf:about="http://somewhere/else#y">
    <j.0:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#root">
    <nsA:P rdf:resource="http://somewhere/else#y"/>
    <nsA:P rdf:resource="http://somewhere/else#x"/>
  </rdf:Description>
</rdf:RDF>
# -- nsA and cat defined
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:cat="http://nowhere/else#"
    xmlns:nsA="http://somewhere/else#" > 
  <rdf:Description rdf:about="http://somewhere/else#y">
    <cat:Q rdf:resource="http://somewhere/else#z"/>
  </rdf:Description>
  <rdf:Description rdf:about="http://somewhere/else#root">
    <nsA:P rdf:resource="http://somewhere/else#y"/>
    <nsA:P rdf:resource="http://somewhere/else#x"/>
  </rdf:Description>
</rdf:RDF>


코드와 비교해 봤을때 이 결과는 3가지로 나눌 수 있는데 경계에 #표시로 설명을 달아놨습니다. 맨 처음에는 네임스페이스를 사용했지만 네임스페이스의 이름을 지정하지 않았을 경우입니다. 즉 prefix의 이름을 선언하지 않았죠. jena에서 prefix의 이름을 지정하지 않은 네임스페이스에 임의로 j.0 과 j.1이라는 이름을 지어서 사용했습니다. 


두번째 코드에서는 nsA로 사용한 네임스페이스의 이름을 "nsA"로 설정한 후 출력합니다. 그 결과 nsA를 사용한 property에서는 j.1대신 nsA라는 명칭으로 사용된 것을 볼 수 있습니다. 


마지막 코드는 nsB가 사용된 네임스페이스의 이름을 "cat"으로 바꿧습니다. 출력된 결과를 보면 네임스페이스의 이름들이 각각 cat과 nsA로 된 것을 확인할 수 있습니다. 이렇게 하나하나 일일이 설정하는 방법보다 쉽게 코드를 통해서 획일화된 prefix관리가 가능합니다. 실질적으로 이기능을 어따 쓸지는 모르겠지만요 ^^...



Navigating a Model


다음은 모델 찾기 입니다. Navigating a Model인데 번역이 까다롭군요..

리소스의 URI가 주어질 경우 Model.getResource(String uri) 메소드를 사용해 모델에서 해당 리소스를 검색할 수 있습니다. 찾는 URI가 없다면 새로 만든다고 합니다.


리소스의 속성에 접근하는 방법은 여러가지가 있는데 Resource.getProperty(Property p) 메소드를 사용하면 리소스에서 속성을 얻을 수 있습니다. 하지만 이렇게 얻은 property가 property타입이 아니라 상태(statement) 타입이라고 합니다. 이를 활용해서 위의 코드중에 vcard:N의 리소스를 얻을려면 다음과 같이 코딩하면 됩니다. 


1
2
Resource name = (Resource) vcard.getProperty(VCARD.N)
                                .getObject();


getproperty를 통해 해당 statement를 얻은다음 statement가 가리키는 대상을 getObject() 메소드로 받아오는 것입니다. 화살표가 가르키는 대상이 리소스임이 확실할 때는 위에 코드에서 getObject()대신 getResource()를 사용해도 됩니다. 반대로 문자열일때는 getString()을 사용합니다.


그런데 만약 특정 리소스가 두개의 속성을 가질수 있습니다. 예를들어 아담스미스의 닉네임이 "Smithy"와 "Adman"를 가진다고 한다면 위와같은 검색으로는 두개의 속성을 모두 표현하지 못한다고 합니다. 아마 처음 선언한속성은 묻히고 가낭 나중에 선언한 속성값이 리턴되는 모양입니다. 제 생각에는 동일한 이름의 속성을 두개를 갖는 자료 모델은 그리 흔하지 않기 때문에 이 부분은 그다지 활용성이 없다고 생각됩니다. 그래도 혹시 두개의 속성을 갖는 자료형이 있을 수 있으니 이럴경우에는 이전 포스팅에서 언급한 iterator 명령어를 사용하면 가지고 있는 속성 전부를 확인할 수 있다고 합니다. 이 부분의 예제는 간단하므로 패스하도록 하겠습니다. 자세한건 홈페이지를 참고하세요~



Querying a model


nevigating의 경우 우리가 찾고자하는 리소스에 대한 uri가 있어야 합니다. 여기서는 질의문을 통해 원하는 리소스를 찾는 기능을 설명해 줍니다. 드디어 제가 원하던 설명부분이네요 !!

Jena API는 RDQL이라는 쿼리 기능을 제공한다고 합니다. RDQL은 RDF 와 스파클을 합친것 같은 언어로 보입니다. 


Model.listStatements()메소드가 있는데 모델에 사용되는 상태의 리스트를 보여주는 것입니다. Model.listSubjectsWithPropery(Property p, RDFNode o)는 모든 속성 p를 갖는 리소스를 반환한다고 합니다. 이것을 이용해 우리가 얻고자하는 property를 갖는 리소스를 검색할 수 있습니다. 이것을 이용해 쿼리를 할 수 있지만 상당히 큰 모델에서는 효과적이지 못합니다. 


다른 방법으로 Selector를 사용하는 방법이 있습니다. selector 인터페이스를 사용해서 원하는 값을 찾을 수 있습니다. selector는 기본적으로 3가지 인자값을 갖습니다.


Selector selector = new SimpleSelector(subject, predicate, object)


위와같이 상태의 3가지 값, subjec와 predicate와 object의 인자값을 가지고 selector를 선언할 수 있습니다. 



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
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.*;
 
import java.io.*;
 
 
public class Main extends Object {
    
    static final String inputFileName = "c:/vc-db-1.rdf";
    
    public static void main (String args[]) {
       
        Model model = ModelFactory.createDefaultModel();
       
        
        InputStream in = FileManager.get().open(inputFileName);
        if (in == null) {
            throw new IllegalArgumentException( "File: " + inputFileName + " not found");
        }
        
  
        model.read( in"");
        
      
        ResIterator iter = model.listResourcesWithProperty(VCARD.FN);
        
        if (iter.hasNext()) {
            System.out.println("The database contains vcards for:");
            while (iter.hasNext()) {
                System.out.println("  " + iter.nextResource()
                                              .getRequiredProperty(VCARD.FN)
                                              .getString() );
            }
        } else {
            System.out.println("No vcards were found in the database");
        }            
    }
}



위코드는 selector를 사용하지 않은 코드입니다. 일단 외부파일에서 vcard.FN속성을 갖는 값들을 전부 뽑는 코드인데요 지난 포스팅에서는 공식 메뉴얼에서 제공하는 파일을 못찾아서 사용하지않았는데 구글링으로 찾아서 이번엔 사용하게 됬습니다ㅎㅎ 파일 첨부할께요 


vc-db-1.rdf


해당 파일을 C드라이브에 이동시킨다음 위의 코드를 실행하면 다음과 같은 결과가 나옵니다. 


1
2
3
4
5
The database contains vcards for:
  Becky Smith
  Matt Jones
  Sarah Jones
  John Smith


vc-db-1의 내용은 잘 모르지만 사람이름만 4개가 나왔습니다. vc-db-1파일을 한번 볼까요?


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
<rdf:RDF
  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
  xmlns:vCard='http://www.w3.org/2001/vcard-rdf/3.0#'
   >
 
  <rdf:Description rdf:about="http://somewhere/JohnSmith/">
    <vCard:FN>John Smith</vCard:FN>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Smith</vCard:Family>
    <vCard:Given>John</vCard:Given>
    </vCard:N>
  </rdf:Description>
 
  <rdf:Description rdf:about="http://somewhere/RebeccaSmith/">
    <vCard:FN>Becky Smith</vCard:FN>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Smith</vCard:Family>
    <vCard:Given>Rebecca</vCard:Given>
    </vCard:N>
  </rdf:Description>
 
  <rdf:Description rdf:about="http://somewhere/SarahJones/">
    <vCard:FN>Sarah Jones</vCard:FN>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Jones</vCard:Family>
    <vCard:Given>Sarah</vCard:Given>
    </vCard:N>
  </rdf:Description>
 
  <rdf:Description rdf:about="http://somewhere/MattJones/">
    <vCard:FN>Matt Jones</vCard:FN>
    <vCard:N
    vCard:Family="Jones"
    vCard:Given="Matthew"/>
  </rdf:Description>
 
</rdf:RDF>


vc-db는 다음과 같이 되어있습니다. 여러개의 리소스가 있고 각 리소스마다 FN과 N의 속성을 가지고 있습니다. 각각 사람이름으로 구성되어 있고 코드에서는 FN속성의 값만 추출했기때문에 vCard:FN 으로 표시되어있는 줄의 값들만이 출력되는 것을 볼 수 있습니다.


다음은 selector의 사용입니다. 


1
2
3
4
5
StmtIterator iter = model.listStatements(
    new SimpleSelector(null, VCARD.FN, (RDFNode) null) {
        public boolean selects(Statement s)
            {return s.getString().endsWith("Smith");}
    });


위코드는 VCARD.FN에서 끝나는 문장이 "Smith"인 것만 찾아내는 코드입니다. 위의 코드보다 조금 복잡해 보이시나요? 저도 위에 설명만보고 selector 간단한데? 라고 생각했는데.. selector라는것 자체가 클래스로 사용되는군요. 위에 코드에서 new SimpleSelector()를 사용하고 뒤에 { } 괄호가 있습니다. 이것은 안드로이드 프로그래밍할때 onclicklistener를 상속받아 사용할때랑 비슷한 과정입니다. SimpleSelector()에서 오버라이드해서 사용하는 메소드인 selects는 조건이 맞는경우에만 검색되게 해주는 메소드라고 생각하시면 될 것같습니다. 즉 

public boolean selects(Statement s){ 조건 } 으로 사용하여 조건이 참인 것만 출력이 된다는 것이죠. 여기서 Statement s 라는 인자값은 SimpleSelector의 인자값으로 들어오는 Statement값일 겁니다. 말로만 설명했더니 어렵군요. 아래 예제를 봅시다.


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
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.*;
 
import java.io.*;
 
 
 
public class Main extends Object {
    
    static final String inputFileName = "c:/vc-db-1.rdf";
    
    public static void main (String args[]) {
   
        Model model = ModelFactory.createDefaultModel();
       
      
        InputStream in = FileManager.get().open(inputFileName);
        if (in == null) {
            throw new IllegalArgumentException( "File: " + inputFileName + " not found");
        }
        
        model.read( in"" );
        
 
        StmtIterator iter = model.listStatements(
            new 
                SimpleSelector(null, VCARD.FN, (RDFNode) null) {
                    @Override
                    public boolean selects(Statement s) {
                            return s.getString().endsWith("Smith");
                    }
                });
        if (iter.hasNext()) {
            System.out.println("The database contains vcards for:");
            while (iter.hasNext()) {
                System.out.println("  " + iter.nextStatement()
                                              .getString());
            }
        } else {
            System.out.println("No Smith's were found in the database");
        }            
    }
}


위에 예제는 이전에 사용했던 rdf파일인 vc-db-1.rdf파일을 불러와서 그안에있는 VCARD.FN속성들의 값중에 끝에말이 Smith로 끝나는 값들을 출력하는 과정입니다. 출력값을 먼저 보면 다음과 같습니다.


1
2
3
The database contains vcards for:
  Becky Smith
  John Smith


원래는 4개의 FN값을 가지고 있지만 끝에 Smith라는 단어가 포함되어야 한다는 조건이 붙어서 두개만 검색되는것을 볼 수 있습니다. 이전에 했던 listSubjectsWithProperty보다는 보다 상세한 검색이 가능한 것 같군요. 


또 한가지 사실을 Selector안에 selects 메소드의 활용입니다. 


1
2
3
4
5
6
7
8
9
10
StmtIterator iter = model.listStatements(
  new
      SimpleSelector(nullnull, (RDFNode) null) {
          public boolean selects(Statement s) {
              return (subject == null   || s.getSubject().equals(subject))
                  && (predicate == null || s.getPredicate().equals(predicate))
                  && (object == null    || s.getObject().equals(object))
          }
     }
 });



1
2
StmtIterator iter =
  model.listStatements(new SimpleSelector(subject, predicate, object)


위에 두가지 코드를 출력하면 같은 결과가 나오게 됩니다. 모든 상태를 출력하는 코드인데요. 첫번째의 경우에는 selects에서 비교 검색을 하므로 일일이 하나하나 검색한다고 합니다. 하지만 두번째 코드는 뭔가 더 기능적으로 전체를 출력한다고 하네요. 영어로 설명이 되있기는 한데 뭔가 잘 이해가 되지 않습니다. 아무래도 기능적으로 아래것이 더 효율적이라고 하네요. 물론 작은 모델에 한해서는 비슷한 성능을 보이지만 엄청 큰 모델에서 큰 차이가 있다고 합니다. 커피를 준비하고 시험해보라고 권하고 있네요 ^^...



Operations on Models


이번에 다룰 것은 모델연산자입니다. 약간 다르지만 같은 영역에대한 모델의 경우 합칠 수 있는 메소드가 있는 것 같습니다. 그림으로 설명하겠습니다. 






첫번째 그림에서는 JhonSmith에 대한 모델이 두개입니다. 하나는 FN과 N의 속성을 가지고 있고 다른 하나는 FN과 EMAIL이라는 속성을 가지고 있습니다. 두개를 병합하여 하나로 합친것이 아래 그림입니다. 솔직히 온톨로지 병합 기능은 온톨로지 툴에서 지원해주고 훨씬 편한 UI로 지원해주지만 그것들도 다 이렇게 자바로 만들어진것이기에 한번 봐두면 나쁘진 않을 것 같습니다. 쓰진 않을 태지만...

사용 코드는 간단합니다. model.read()를 통해 두개의 모델을 불러온 다음 새로운 모델을 선언하고 union메소드를 사용하면 됩니다. 


1
2
3
4
5
6
7
8
9
// read the RDF/XML files
model1.read(new InputStreamReader(in1), "");
model2.read(new InputStreamReader(in2), "");
 
// merge the Models
Model model = model1.union(model2);
 
// print the Model as RDF/XML
model.write(system.out"RDF/XML-ABBREV");




Containers

Container는 여러가지 객체를 모아서 대표하는 리소스의 특별한 종류라고 합니다. 바로 들어서는 뭔지 모르겠네요. 이런 컨테이너에는 3가지 종류가 있습니다.

  • a BAG is an unorderd collection
  • an ALT is an unordered collection intended to represent alternatives
  • a SEQ is an orderd collection
위에 3가지 컨테이너 종류가 있는데 정렬된 것과 비정렬된것으로 나뉘는가 봅니다. 

컨테이너는 리소스로 표현되고 rdf:type 속성을 갖고 그 값은 rdf:Bag, rdf:ALT, rdf:SEQ 중에 하나라고 합니다. 즉 컨테이너 리소스의 rdf:type 속성이 이 컨테이너가 3가지중에 어떤 것인지를 표현하는 것 같습니다. 

예제코드와 설명들을 읽어본바로는 컨테이너는 특정모델에서 원하는 값만 빼어내서 모델을 만들때 사용 될 것 같습니다. 예를들어 이전에 예제에서 "smith"라는 이름을 뒤에 갖는 사람들의 정보만 빼서 출력했었는데 출력만 하지말고 이것들로 모델을 만드는 것입니다. 다음과 같은 코드를 사용하면 Bag형태의 컨테이너가 만들어집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create a bag
Bag smiths = model.createBag();
 
// select all the resources with a VCARD.FN property
// whose value ends with "Smith"
StmtIterator iter = model.listStatements(
    new SimpleSelector(null, VCARD.FN, (RDFNode) null) {
        public boolean selects(Statement s) {
                return s.getString().endsWith("Smith");
        }
    });
// add the Smith's to the bag
while (iter.hasNext()) {
    smiths.add(iter.nextStatement().getSubject());
}


먼저 smiths라는 Bag을 만들고나서 selector 검색을 통해 "Smith"가 들어간 리소스들을 찾아낸다음 smiths라는 백에 추가하는거지요. 이렇게 추가된 bag을 출력하면 다음과 같이 나오게됩니다.


1
2
3
4
5
6
7
8
9
10
11
<rdf:RDF
  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
  xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
 >
...
  <rdf:Description rdf:nodeID="A3">
    <rdf:type rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
    <rdf:_1 rdf:resource='http://somewhere/JohnSmith/'/>
    <rdf:_2 rdf:resource='http://somewhere/RebeccaSmith/'/>
  </rdf:Description>
</rdf:RDF>


Bag형태이면서 속성이름들이 _1 _2를 갖게되는 것이 특징입니다. 여기까지가 컨테이너의 설명인데 좀더 자세한 활용은 더 공부해 봐야 알 것 같습니다.


More about Literals and Datatypes


RDF에서 사용되는 Literal(문자열)은 기본적인 String과는 다릅니다. 일단 Literal은 String과 다르게 태그를 가지고 있다고 합니다. "chat"이라는 영어태그를 가진 Literal과 "chat"이라는 프랑스태그를 가진 Literal은 구분할 수 있다고 하네요. RDF/XML의 고유 기능이라고 합니다.


다음 예제를 보면서 이해해보도록 합시다.


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 com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.vocabulary.*;
 
import java.io.PrintWriter;
 
 
public class Main extends Object {
    
      public static void main (String args[]) {
        // create an empty graph
        Model model = ModelFactory.createDefaultModel();
 
       // create the resource
       Resource r = model.createResource();                                     
 
      // add the property
      r.addProperty(RDFS.label, model.createLiteral("chat""en"))
       .addProperty(RDFS.label, model.createLiteral("chat""fr"))
       .addProperty(RDFS.label, model.createLiteral("<em>chat</em>"true));
      
      // write out the graph
      model.write(new PrintWriter(System.out));
      System.out.println();
      
      // create an empty graph
      model = ModelFactory.createDefaultModel();
 
       // create the resource
       r = model.createResource();                                     
 
      // add the property
      r.addProperty(RDFS.label, "11")
       .addLiteral(RDFS.label, 11);
      
      // write out the graph
      model.write( System.out"N-TRIPLE");
      }
}



위에 예제에서는 Literal에 태그를 입력하는 방법을 알려줍니다. 3번째의 <em> </em>은 뭔지 모르겠군요. 두번째 예제는 조금 잘못되었는데 addProperty를 이용해 "11"과 11을 추가할경우 둘다 같은것으로 인식해서 하나만 추가한다는 내용이 홈페이지에 나와있는데 버전업이 되면서 하나의 리소스에 property두개를 추가하는게 안되나 봅니다. 음.. 이부분에 대한 설명은 보류해야 할 것같습니다.





여기까지가 공식 홈페이지의  jena API 메뉴얼 입니다. 기대했던것보다 상당히 기본적인 것만 알려주는군요 ㅠ.. 실제로 온톨로지를 적용하고 활용하는 예제를 기대했는데 혼자해보거나 다른 곳을 찾아야 할 것같습니다. 

반응형

'Study > OWL,RDF' 카테고리의 다른 글

Jena Ontology API 예제소스 (1)  (0) 2014.02.11
Jena Ontology API  (0) 2014.02.10
Jena RDF api 활용하기 (2)  (1) 2014.02.05
Jena RDF api 활용하기  (0) 2014.02.04
Jena API  (0) 2014.02.03

댓글

Designed by JB FACTORY