'Framework/Another Lore'에 해당되는 글 27건

  1. 2009.08.14 언젠가...
  2. 2009.07.15 XUL 단상
  3. 2009.07.05 레이어의 속도
  4. 2009.06.25 read & write
  5. 2009.06.11 최근에 책을 읽다가..
  6. 2009.04.30 AL : Permission 1
  7. 2009.04.30 AL : 현재의 난제들
  8. 2009.04.28 AL : Workspace
  9. 2009.04.26 AL : Property Type Conversion
  10. 2009.04.25 AL : Reading
Framework/Another Lore2009. 8. 14. 15:49

AL을 처음 시작할때인 2007년에
AL의 최종 진화 모습은 DB를 사용하는게 아니라 DB 그 자체라고 얘기를 한적이 있는데..

결국 DB를 만들고 있다 -ㅅ-;

험난하다. -ㅅ-; 후. 

'Framework > Another Lore' 카테고리의 다른 글

XUL 단상  (0) 2009.07.15
레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin
Framework/Another Lore2009. 7. 15. 02:26

AL Client를 XUL(ThinLet)를 사용해서 약 한달간 만들었는데. 브라우저가 아닌 Java에서의 XUL은 2% 부족하다는 느낌이다. 

XUL(XML User-Interface Language)은 단순히 IDL 이므로 다양한 Client에서(물론 이전에 클라이언트가 XUL을 인지해야 한다. Firefox와는 달리 대표적인 브라우저인 ie는 XUL을 지원하지 않는다. ) 별도의 UI 로직를 고심할 필요가 없다는 것은 크나큰 장점이다. (https://developer.mozilla.org/Ko/The_Joy_of_XUL 모질라의 XUL Page)

그러나 XUL이 세상에 나온지 5년이 넘게 지났지만 생각보다 그 영향력은 매우 미비하다. 개인적으로는 XUL을 써보면서 lightweight Framework는 lightweight 하다라는 사실을 새삼스럽게 느꼈다. 

XUL의 Java OpenSource인 ThinLet(http://thinlet.sourceforge.net/component.html)는 매우 훌륭한 프레임워크였다. 무엇보다 놀란것은 그 간결한 구조였다. ThinLet은 XML을 Object Array Tree 형태로 관리하여 개별적인 컴포넌트의 paint를 구현한다는 것은 복잡한 머리속을 순식간에 관통하게 만드는 Simple 미학을 보여준다. Java의 Swing에 의존하지 않기 때문에 오히려 역으로 확장이 쉬운 아키텍쳐이다. 

XUL의 장점은 대표적으로 
    Based on existing standards, 
    Platform portability, 
    Separation of presentation from application logic, 
    Easy customization, localization, or branding 
등을 들 수 있다. 특히나  HTML 처럼 XUL은 플랫폼 중립적(platform-neutral)하기 때문에 비교적 단순하게 portable하고 cross platform application UI를 만들수 있다는 점에 매료되었다. 

문제가 됐던것은 바로 Lightweight 하다는 점이다. 어느정도 익숙해지고 난후 ThinLet은 Draggable한 Panel을 지원하지 않았기 때문에 ThinLet의 단순한 아키텍쳐에 감탄하면서 ThinBean 형태로 확장해서 만들었다. 얼마 지나지 않아서 다시 Draggable한 Table이 필요했고 역시 또 만들었다. 이 과정에서 ThinLet의 이벤트 처리 과정을 대폭 수정해야 했다. 왜냐하면 기본적인 Action Handler로는 너무 부족함이 많았고 결정적으로 Drag할수 있도록 Table의 Cell을 프로그램이 인지해야 했다. ThinLet에서 모든 객체는 paint된 pixel에 불과했기 때문에 Mouse Event가 발생한 곳의 객체를 인지하게 만드는 과정은 매우 복잡했고(zindex가 있음을 생각해보자) 어려웠다. 

나를 결국 손을 들게 만든건 Draggabl한 TreeTable이다. AL은 저장 구조상 TreeTable UI가 꼭 필요했다. 1주일이 넘게 삽질하면서 XUL에서 독립적인 Draggable한 TreeTable UI를 만들기는 아주아주 어렵다는 사실을 알았고 더 큰 문제는 설사 만든다고 하더라도 더 이상 그것은 XUL이 아니라는 사실을 깨닫게 됐다는 점이다. 

이른바 추상화의 함정에 빠진것이다. 

XUL은 일종의 새로운 추상층(Abstraction Layer)이다. 소프트웨어는 마치 양파와도 같이 겹겹이 층을 이루고 있는데, 각층은 그 아래충 위에 조심스럽지만 다소 불안정하게 쌓여있다. 새로운 층이 쌓일때마다 뭔가 복잡하고 지엽적인 것이 좀더 직관적이고 보편적인 것으로 대체되기 때문에 컴퓨터 공학에서는 추상을 여러개의 조그만 프로세스를 묶어 하나의 큰 프로세스로 만드는 과정이라고 정의한다. 

조엘의 말에 따르면 모든 추상화는 완벽하지 않고 함정이 있다고 하는데 예컨데 우리가 Java나 C#을 쓰지만 여전히 point에 대해서 알아야 한다는 것이다. VC++의 MFC 혹은 Delphi의 VCL만 쓰면서 Win32 API에 대해 전혀 알 필요가 없다면 정말 좋겠지만 조금만 더 깊이 들어가려 하면 바로 포인터와 관련된 지식을 만나게 되는 것과 같다. 사실 나는 기꺼이 Open된 ThinLet 코드를 볼수 있고, 가지고 있었기 때문에 사다리를 오르락 내리락 하며 문제를 수정했고 이러한 구멍에 대해서 충분히 이해를 하고 있었다.

그러나 결정적으로 수정에 수정을 거듭하다보니 수정된 프로그램은 XUL이라고 부르기에는 부끄러울 정도로 너덜너덜 해졌다. ThinLet의 마지막 버전은 2005년이고 코드를 작성하는 내내 왜 아무도 Draggabe한 Panel이나 Table을 만들지 않았을까가 궁금했었다. 그 의문은 그동안 겪었던 많은 문제들처럼 오랜동안 헤딩을 하고서야 깨달았는데, 쉽게 말하면 HTML에서 Draggable한 Table을 만들지 않는 이유와 같다. HTML처럼 XUL은 View를 하기 위한 Markup Language인데 Drag Action은 View가 아닌 영역에도 발을 걸칠수 밖에 없다. 즉 이전처럼 Model를 단지 View하기만 했던 구조와 달리 Drag등의 Action이 들어가는 순간 VIew는 Model을 인지하지 않으면 View를 할 수 없게 되었다. 그리고 Model과 Controller가 혼합되면서 Draggable한 TreeTable을 만든다고 하더라도 이게 그건 VIew역할을 하는 XUL이 아니고 오히려 더 복잡해진 정체를 알수 없는 혼합물이 되어버린 것이다. 

사실 프로그래밍 언어에 한해서는 이제 거의 이런 추상화의 문제로부터 벗어나 보인다. 우리는 수십년전의 어셈블리 개발자의 포트란 개발자에 대한 비난을 이젠 역사서에서나 읽을 수 있으니 말이다. 그러나 아직 개별적인 프레임워크에 있어서 그러한 함정은 너무나 많다. 

물론 이것이 XUL은 단지 VIew이기 때문에 XUL의 잘못이라고는 할 수 없다. 그러나 Helloworld를 만드는 것은 버튼 하나로 만들수 있지만 10만줄 짜리 프로그램을 만드는 데는 2배의 시간이 걸린다면 아마도 내가 잘못 선택을 한것이다. 


'Framework > Another Lore' 카테고리의 다른 글

언젠가...  (0) 2009.08.14
레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin
Framework/Another Lore2009. 7. 5. 20:31
앞서 소개한적이 있지만 

"전산학의 모든 문제는 또 다른 수준의 간접층으로 해결할 수 있다" 
라고 말한 휠러는 

"그러나 그러면 또 다른 문제가 생기는 것이 일반적이다" 라고 덧붙인 적이 있다. 

"또다른 문제"의 대표적인 예로는 공간과 시간적 부담과 코드의 가독성 등을 들 수 있다. 

이 포스팅에서는 이중에서 시간적 부담 즉 속도에 관해서이다. 


일반적으로 대부분의 프레임워크는 하나의 간접층(Layer)이며 위의 명제를 피할 수는 없다. 그러나 프레임워크를 쓴다고 항상 느려진다고 할 수 없는 이유가 있다. 

만약 A -> B 과정이 1초가 걸렸다면
A -> A' -> B 과정은 1초보다 작게 걸린다는 것은 논리적으로 불가능하다. 

그러나 시간은 연속성의 특징을 가지고 있다. 무슨말이냐면 프로그램에서의 시간 혹은 속도의 개념은 하나의 event개념이 아니다. 프로그램을 작성하면 무수히 많은 A -> B 과정을 작성하게 된다. 

이를테면 A가 Client Call이라고 하고 B를 Engine라고 가정했을때 (Engine에는 DB나 NS등의 여러가지를 대입할 수 있다.)

1. Client Call(Engine Library) -> Engine과
2. Client Call(Framework Call) -> Framework(Inner Use Engine Library) -> Engine 라는 경우를 상상해 보자. 

하나의 원자 동작을 보았을때 1은 2보다 항상 속도가 빠르다. 그러나 실제 프로그래밍에서는 2가 종종 더 빠른 이유는(잘 만들었을 경우를 가정했을때...) 연속성의 이유때문이다. 함수 하나 혹은 command 하나는 말 그대로의 원자 event로 이루어지지 않는다. 

그리고 이것때문에 이를테면 1의 동작에 0.1초가 걸린다고 하고 10번 반복한다고 했을때 1은 1초가 걸리지만 2는 1초 이하로 떨어뜨릴수 있다. 

좀더 IT적인 용어로 말하자면 Context Switching 비용 때문이다. 

예를 들어
Row row = Engine.executeCommand() ;
if(row.exist()) Engine.updateCommand() ;
else Engine.insertCommand();

와 같은 코드의 경우 Engine의 row의 exist 여부를 체크하여 2번의 Engine Call을 실행한다. 이 과정에서의 속도는 (0.1 + a(context switching cost)) * 2 + b(Caller cost) 가 소모하게 된다. 

만약 위의 과정을 
Framework.mergeCommand() 라고 바꿀수 있다면 0.1 * 2 + a(context switching cost) + b'(Maybe Lower Caller Cost) + c (framework inner handling cost) 로 수식이 바뀐다. 만약 여기서 a + b> b' + c라면 프레임워크를 사용함으로서 속도가 향상되었다고 말할 수 있다. 추가적으로 좀더 간결하게 프로그램을 작성할 수 있다. 여기서 간결함이란 문제는 이전글을 참조하자. 

3번의 동작이 연속으로 이루어진다면 2a + b> b' + c 정도만 해도 향상될 수 있다. 


두번째 속도가 빨라질 수 있는 이유로 
앞서 원자 event라고 가정했던 1의 과정조차 실제로는 바깥에서는 보이지 않는 내부적으로는 연계된 다른 과정을 거치게 된다.

앞서 원자 action event라고 했던 1이 사실은 
Client Call -> Inner Action 1 -> Inner Action 2 -> Engine Inner으로 이루어져 있다가 가정하자. 
각각의 비용은 a + b + c + d  라고 가정하자. 

만약 
Client Call -> Inner Action 1 -> NewFramework -> Inner Action 2 -> Engine Inner로 작성하였다면..
a + b + @(framework cost) + c + d 의 비용이기 때문에 역시 단순 논리로 봤을때는 빨라지지 않는다. 

그러나 프로그래밍이 다중 사용자 환경(One User 프로그램의 경우에도 Multi Thread로 작성된다면 마찬가지이다. )이고 Action 2가 공유될수 있는 Action이라고 한다면 얘기가 달라진다

Inner Action2을 사용자 혹은 여러 Thread가 공유할 수 있다면

N번의 Call이 발생했을때. 
N(a + b + c + d)의 비용이 N(a + b + @ + d) + c의 비용 수식으로 바꿀수 있다. 
이는 c의 cosr가 total cost에서 얼마의 비중인가에 따라 그 가치가 달라진다. 잘 알려진 예제로 Connection Pooling 같은 과정을 생각한다면 이해하기 쉽다. 

# 컨텍스트 스위치 context switch
멀티테스킹 OS는 서로 다른 처리를 수행하는 처리 단위로 프로세스/쓰레드를 짧은 시간에 전환함으로써 병렬처리를 실현하고 있다. 이때의 프로세스/쓰레드 전환처리를 컨텍스트 스위치라고 한다. 이렇게 컨텍스트 스위치할때 멀티쓰레드는 메모리 공간을 공유하기 때문에 메모리 공간의 전환 처리는 생략할 수 있다. 

메모리 공간을 전환하지 않게 되면 CPU 상의 메모리 캐시(정확히는 TLB Translation Lookaside Buffer : 메모리의 가상 주소를 물리 주소로 변환하는 처리를 고속화하기 위한 캐시로 CPU 내부의 구조다. 컨텍스트 스위치가 일어나면 TLB가 초기화되는데 이 영향에 의한 TBL 캐시 미스는 상대적으로 비용이 많이 든다.)를 그대로 유지할 수 있는 등의 큰 이점이 있으므로 멀티 프로세스에 비해 성능에 미치는 영향은 현저하다. 

이러한 이유로 위의 경우 자바와 예로 든 엔진(예컨데 DB)의 변환은 멀티 프로세스 관계일때 더 차이가 두드러진다. 





원래 주제와 좀 떨어지지만 휠러 얘기가 나왔으니 "발생하는 또다른 문제"중 가독성의 얘기를 해보자. 

여기서의 가독성은 동전의 양면과 같다. 사실 맨 처음 웹 프로그래밍을 한다고 했을때 스프링을 사용하는 것은 그다지 좋지 못하다.(라고 생각한다.) 스프링의 스펙은 그리 만만치 않다. 오히려 기본적인 서블릿 프로그래밍보다 더 많은 걸 알아야 한다. 스프링 프레임아웤의 유용성을 떠나 아이러니한것은 중급개발자의 반복작업을 줄여주기 위한 프레임워크가 오히려 초보개발자용 프레임워크로 둔갑한다는 것이다. 

어쨌거나 휠러가 지적한 것은 프레임워크의 겉핧기 사용등의 프레임워크의 내부에 대한 이해 없이 접근하면 작성하는 프로그래머도 또 그걸 읽는 프로그래머(그 사람이 해당 프레임워크에 대해 알고 있는지와 상관없이)도 고통이 된다는 사실을 말한 것이다. 

앞서 동전의 양면이라고 한것은 프레임워크에 대한 많은 학습없이 직관적으로 사용이 가능하고 기대대로 프레임워크가 작동한다면 오히려 가독성에 도움이 될 수 있다는 의미이다. 

그런면에서 프레임워크의 직관적 사용은 좋은 프레임워크를 결정하는데 핵심이 된다. 스프링 프레임워크가 직관적이지 못한것은 어찌보면 프레임워크가 커버하는 범위와 관계가 있을지 모른다. 스프링 프레임워크는 너무 많은 일을 하는 프레임워크이고 또 유연성을 위해 XML 형식의 configuration이 지나치게 많고 복잡해서 휠러의 또다른 문제를 벗어나지 못했다. 











'Framework > Another Lore' 카테고리의 다른 글

언젠가...  (0) 2009.08.14
XUL 단상  (0) 2009.07.15
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin
Framework/Another Lore2009. 6. 25. 10:47

습관은 관습을 낳고 관습은 편견을 낳는다. 

우리는 bean 객체를 많이 봐왔기 때문에 getMethod가 있으면 setMethod도 있어야 할 것이라고 생각한다. 물론 이것이 틀렸다고는 할수 없지만 가끔은 생각을 바꿔보는게 도움이 될 수 있다. 

예컨데 다음 상황을 보자. 

Node rootNode = session.getRootNode() ;
NodeType empType = getNodeType("employee") ;

Node newNode = rootNode.createChild(empType, "abcd") ;


이 상태에서 newNode를 repository에 저장하는게 좋을까? 아니다. 그러기엔 너무 일러보인다. 왜냐하면 아직 실제 값을 설정하지 않았기 때문이다. 

그 보다는 
newNode.setProperty("ename", "bleujin") ;
newNode.setProperty("empNo", 20) ;
와 같이 값을 설정하고 

newNode.save() 를 호출했을때 repository에 저장하는게 보다 효율적일 것이다. Transaction Handling이 가능하고 setProperty를 호출할때마다 DBCall을 하는것보단 훨씬 효율적이다. 


다만 위의 경우 문제가 있다. 

다음 상황을 상상해 보자. 

Node parent = rootNode.createChild(empType, "parent") ;
Node child = parent.createChild(empType, "bleujin") ;
child.setProperty("ename", "bleujin") ;
child.setProperty("empNo", 20) ;
child.save() ;

별문제가 없어보인다. 
그러나 위와 같이 현재 Session의 모든 PendingEvent를 저장하는 session.save() 를 호출하지 않고 child.save() ;를 호출했을 경우에 (물론 API 상으로는 아무런 문제가 없다.) parent가 저장되지 않은 상태에서 child가 먼저 저장될 수 있는가? 에 대한 의문이 든다. 더군다다 child만 save()하고 parent는 일부러 혹은 실수로 save를 하지 않거나 혹은 parent.save()를 호출했을 경우에 에러가 발생했을 경우를 상정해 보면 문제가 더욱 복잡해진다. 

API상으로는 문제가 없지만 child만 save가 되고 parent는 save 되지 않았을 경우는 정상적인 상태가 아니다. 그렇다고 맨 처음의 방식으로 돌아가는 것도 Transaction이 보장되는 것은 아니다. 각각의 호출마다 repository Call을 했을 경우 첫번째와 두번째 사이에 에러가 발생할 수 있다는 것은 마찬가지 이다. 

물론 사용자 실수야 라고 탓이라고 문제를 회피할 수도 있겠지만 이는 올바른 방법이 아니다. 순서에 의존하는 API는 (예컨데 a를 호출하고 난 다음에 b를 호출하고 그 다음에 c를 호출하고..) 사용하기 복잡하고 실수를 가져오는게 당연하다. 

이 문제를 해결하기 위해 AL은 존재와 무존재를 구분한다. 
예컨데 


interface INode {
    .......
    getterMethod() ;
    setterMethod() ;
    Node retrieve(...) ;
    Node save() ;
}

interface NodeTemplate extends INode {
     ....
}

interface Node extends INode {
    NodeTempalte createChild(NodeType nodeType, String name) ;
}

와 같이 인터페이스를 분리하면 문제는 명확해진다. 
ISP(Interface Segregation Principle)의 의미는 어렵지 않지만 책임의 한정이 어렵다. 여기서는 책임을 read책임과 write책임으로 분리한 것이다. 


NodeTemplate parent = rootNode.createChild(empType, "parent") ;
위 상태에서 NodeTemplate는 createChild Method를 호출할 수 없기때문에 

Node savedParent = parent.save() ; 를 호출한 이후 child를 만들거나 아니면 session.save()를 호출하여 pendingEvent를 같이 처리해야 한다. 


위 예제에서 좀더 나아가서 getMothod용 Node와 setMethod용 Node를 구분한다면 개선의 소지가 더 있다. 이를테면 Setter가 없는 Node를 통해 더 가벼운 Node를 만들 수 있게 된다. 그리고 Getter가 필요없는 Node를 통해 더 빨리 Node를 얻어올 수 있다. 




'Framework > Another Lore' 카테고리의 다른 글

XUL 단상  (0) 2009.07.15
레이어의 속도  (0) 2009.07.05
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
AL : 현재의 난제들  (0) 2009.04.30
Posted by bleujin
Framework/Another Lore2009. 6. 11. 20:19

AL 개발하면서... 코드가 막힐때면 책을 읽곤 하는데..
"드리밍 인 코드"에 소개된 오픈소스 "챈들러" 프로젝트의 백엔드 저장소와 AL이 상당히 비슷하다는 걸 알았다.

AL은 Node와 Node의 Link로 정보를 저장하는데.
챈들러는 <this> <has-relationship-with> <that> 형태로 저장하는 RDF(Resource Description Framework)를 사용하고 있었다. 챈들러라는 프로젝트는 2003년쯤에 우연히 관련자료를 찾다가 알게된 프로젝트인데 당시에는 개인정보관리 시스템 정도로  알려져 있었지만 오픈된 내용이 거의 없어서 곧 잊혀졌다. (고작해야 1시간 정도 살펴본것뿐이긴 하지만 이걸 기억하는건 당시 프렌즈라는 미국 드라마를 아주 좋아했기 때문이다. 물론 챈들러 프로젝트의 챈들러는 프렌즈의 챈들러가 아니다. )


RDF(이런 용어가 존재한다는 것도 처음 알았다)는 주로 학구적인 성격이 강하기 때문에 구현체가 많지 않지만.
Phthon 구현체인 ZODB는 아직 자세히 살펴보진 않았지만 AL의 현재의 모습과 아주 비슷하다 -ㅅ-..

혼자 하는 AL이니 머 수십명 이상이 하는 ZODB나 챈들러와는
세부적인 모습은 다르지만.. 목적지가 같다..이런...

본래 오픈소스에서 비슷한 걸 발견하면 기뻐해야 겠지만..
첫번째.. 거의 실패 혹은 잊혀진 프로젝트라는 것이고..
두번째.. 파이썬으로 되어 있어서 그들의 경험으로부터 배우기가 쉽지 않기 때문이다. 췟.




'Framework > Another Lore' 카테고리의 다른 글

레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
AL : Permission  (1) 2009.04.30
AL : 현재의 난제들  (0) 2009.04.30
AL : Workspace  (0) 2009.04.28
Posted by bleujin
Framework/Another Lore2009. 4. 30. 11:26

Permissions encompass the restrictions imposed by any access control restrictions that may be in effect upon the content of a repository, either implementation specific or JCR-defined

In repositories that support Access Control this will include the restrictions governed by privileges but may also include any additional policy-internal refinements with effects too fine-grained to be exposed through privilege discovery


Permissions are reported through
     boolean Session.hasPermission(String absPath, String actions)

which returns true if this Session has permission to perform all of the specified actions at the specified absPath and returns false otherwise. Similarly, void Session.checkPermission(String absPath, String actions) throws an AccessDeniedException if the this Session does not have permission to perform the specified actions and returns quietly if it does.


The actions parameter is a comma separated list of action strings, of which there are four, defined as follows:

  create: The permission to add a node at absPath.
  update: The permission to set (add or change) a property at absPath.
  delete: The permission to remove an item at absPath.
  read: The permission to retrieve (and read the value of, in the case of a property) an item at absPath.


The permission actions add_node, set_property and remove will only be relevant in a writable repository. In a read-only repository they will always return false. The information returned through these methods only reflects access controlrelated restrictions, not other kinds of restrictions such as node type constraints.

For example, even though hasPermission may indicate that a particular Session may add a property at /A/B/C, the node type of the node at /A/B may prevent the addition of a property called C.

SampleCode

package com.bleujin.lore.core.security;

import com.bleujin.lore.addon.security.Group;
import com.bleujin.lore.addon.security.IResource;
import com.bleujin.lore.addon.security.IUser;
import com.bleujin.lore.addon.security.Member;
import com.bleujin.lore.addon.security.UserAuthority;
import com.bleujin.lore.addon.security.UserAuthority.Range;
import com.bleujin.lore.addon.security.UserAuthority.Type;
import com.bleujin.lore.core.TestCaseParent;
import com.bleujin.lore.core.exception.ALRepositoryException;
import com.bleujin.lore.core.node.Node;

public class TestSecurity extends TestCaseParent {

  private Group adminGroup = new Group("admin");
  private Group normalGroup = new Group("normal");
  private Member bleujin = new Member("bleujin");

  private Node lvl1 = null;
  private Node lvl2 = null;

  private NodeResource lvl1Resource = null;
  private NodeResource lvl2Resource = null;
  private TransientSecurityFilter sf = null;
  private AuthoritySetting setting = AuthoritySetting.createDefault() ;

  public void setUp() throws Exception {
    super.setUp();

    lvl1 = createNode(objectType, "level1");
    lvl2 = createNode(lvl1, objectType, "level2");

    lvl1Resource = new NodeResource(lvl1);
    lvl2Resource = new NodeResource(lvl2);

    sf = new TransientSecurityFilter(setting);
  }

  private boolean isAllowed(IResource resource, IUser user, NodeAction actionthrows ALRepositoryException{
    return sf.isAllowed(resource, user, action;
  }
  
  public void testResourceInherit() throws Exception {
    setting.add(new UserAuthority(lvl1Resource, bleujin, setting.readAuthority(), Range.ONLY_THIS_RESOURCE, Type.GRANT));

    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    // test include Sub
    setting.add(new UserAuthority(lvl1Resource, bleujin, setting.readAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.GRANT))// inherit..
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    UserAuthority lvl2Revoke = new UserAuthority(lvl2Resource, bleujin, setting.readAuthority(), Range.ONLY_THIS_RESOURCE, Type.REVOKE);
    setting.add(lvl2Revoke);
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

  }

  public void testEqual() throws Exception {
    setting.add(new UserAuthority(lvl2Resource, bleujin, setting.readAuthority(), Range.ONLY_THIS_RESOURCE, Type.GRANT));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    setting.remove(new UserAuthority(lvl2Resource, bleujin, setting.readAuthority(), Range.ONLY_THIS_RESOURCE, Type.REVOKE));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));
  }

  public void testAuthorityInherit() throws Exception {
    setting.add(new UserAuthority(lvl1Resource, bleujin, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.GRANT));
    bleujin = new Member("bleujin");
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));

    setting.add(new UserAuthority(lvl2Resource, bleujin, setting.readAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.REVOKE));
    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("write")));
  }

  public void testUserInherit() throws Exception {
    setting.add(new UserAuthority(lvl1Resource, adminGroup, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.GRANT));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl1Resource, bleujin, NodeAction.create("manager")));
    assertEquals(false, isAllowed(lvl1Resource, bleujin, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));

    bleujin = new Member("bleujin");
    bleujin.partIn(adminGroup);

    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));
  }

  public void testMultiGroup() throws Exception {
    // adminGroup <- bleujin
    // normalGroup <- bleujin
    setting.add(new UserAuthority(lvl1Resource, adminGroup, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.GRANT));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    bleujin.partIn(adminGroup);
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    setting.add(new UserAuthority(lvl2Resource, normalGroup, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.REVOKE));
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));

    bleujin.partIn(normalGroup);
    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("write")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));

    normalGroup = new Group("normal");
    bleujin.dropOut(normalGroup);
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("write")));
  }

  public void testGroup() throws Exception {
    // adminGroup <- normarGroup <- bleujin
    setting.add(new UserAuthority(lvl1Resource, adminGroup, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.GRANT));
    setting.add(new UserAuthority(lvl2Resource, normalGroup, setting.managerAuthority(), Range.INCLUDE_SUB_RESOURCE, Type.REVOKE));
    assertEquals(true, isAllowed(lvl1Resource, adminGroup, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    bleujin.partIn(adminGroup);
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    normalGroup.partIn(adminGroup);
    assertEquals(true, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));

    bleujin.partIn(normalGroup);
    assertEquals(true, isAllowed(lvl1Resource, bleujin, NodeAction.create("read")));
    assertEquals(false, isAllowed(lvl2Resource, bleujin, NodeAction.create("read")));
  }
}


'Framework > Another Lore' 카테고리의 다른 글

read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : 현재의 난제들  (0) 2009.04.30
AL : Workspace  (0) 2009.04.28
AL : Property Type Conversion  (0) 2009.04.26
Posted by bleujin
Framework/Another Lore2009. 4. 30. 10:48


Wokrspace의 Security의 문제
현재 AL Workspace의 보안 Role 모델은 Window의 탐색기로 시작했다. 
각각의 컴퓨터의 탐색기는 별도의 Root를 가지고 모든 파일을 TreePath로 접근할수 있다.

Storage가 물리적인 의미인 반면에 Workspace는 물리적이 아니라 논리적인 의미이다.

A컴퓨터를 GroupA와 GroupB사용자가 하나의 Storage로 각각 다른 Workspace를 사용할때(하나의 Storage를 2개 이상의 Workspace로 사용할때) GroupA와 GroupB는 별도의 TreePath를 가지며 각각의 Workspace에서 TreePath는 유니크하다. 여기까지는 문제가 없다.

단 Node의 UUID로 접근할때 이를 허용해야 하는가의 문제이다. 이를 허용하면 Workspace가 물리적으로 나뉘어 있을때도 접근을 허용해야 하는 문제가 있다. 허용하지 않으려면 매번 WorkspaceName을 검사해야 한다. 이 문제는 일반적인 규악이 없고 Workspace간의 SharableNode의 문제와 얽히기 때문에 머리에서 쥐가 난다.


프로그램에는 How의 코딩의 문제를 벗어나게 되면 일반적으로 2가지 문제가 있는데 하나는 다른 이해 관계자와 커뮤니케이션의 문제이고 두번째는 무엇(What)을 해야 하는가이다. 이런 종류는 방법이 없는게 문제가 아니라 방법이 너무 많아서 어느게 가장 좋은 방법인가를 지금은 알지 못한다에 문제가 있다.


이 문제를 bleujin식 난제 네이밍관례에 따라 "아들을 아들이라 부르지 못하고.." 문제로 정의하였다.




AL은 국제화
AL은 OR Mapping이 아닌 Repository에 가깝기 때문에 UTF인코딩을 넘어서는 국제화를 지원해야 한다.

이를테면 우리나라는 GMT+9의 Timezone에 있다. 날짜는 절대시간으로서의 인식과 상대시간으로의 인식이 있는데 오스트리아의 오늘 일몰 시간은 오후 07:13분입니다 라고 할때는 절대시간으로서 저장해야 하고 오스트리아의 오후 7시 13분이 우리나라 시간으로 몇시지? 라고 생각하면 상대시간으로 저장해야 한다.

그러나 시간은 말하지 않기 때문에 사용자가 Property에 날짜값을 설정했을때 어느 시간으로 저장해야 하는가에 대한 문제가 있다. 이를테면 호주에서 4월 30일 오후 9시 20분이라고 입력한 것과 우리나라에서 4월 30일 오후 9시 20분이라고 입력한것은 전혀 다르다.

그래서 Calendar Property는 UTC로 저장한다. 그런데 사용자의 접속시간 등 다른 시간들의 표현에 문제가 있다. 난 오전 10시에 로그인했는데 오전 1시에 접속한 걸로 나오면 아마 프로그래밍 버그를 의심할 것이다.

이런 날짜의 문제뿐 아니라 이전 국제화 글에서 지적한 바와 같이 숫자의 표현 등에도 난제는 많다. (통화는 PropertyType에 없다는 것이 좀 위안이 된다. -ㅅ-)  이 것을 "시간의 침묵" 이라고 부르기로 했다. -ㅅ-

'Framework > Another Lore' 카테고리의 다른 글

최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
AL : Workspace  (0) 2009.04.28
AL : Property Type Conversion  (0) 2009.04.26
AL : Reading  (0) 2009.04.25
Posted by bleujin
Framework/Another Lore2009. 4. 28. 11:59


A repository may support workspace management. A repository that supports this feature must the semantics of multiple workspaces  and support cross-workspace operations (copying across Workspaces and cloning and updating node, share node)

AL은 Worksapce에서 좀 다른 기능을 가지고 있다.

AL은 1개 이상의 Workspae로 구성되어 있고 AnotherLore.login(new Credential("bleujin"), "otherWorkspaceName") ; 로 통해 default가 아닌 다른 Worksapce에 접속할 수 있다.

이를테면 이런 경우를 생각하면 된다. AL을 이용하여 인사관리 시스템을 만들었다. 이제 재고관리 프로그램을 만들어야 할때 또 다른 AL을 설치해야 할까?


또 다른 AL이 필요치는 않다. 다만 새로운 Workspace를 정의해 두고 재고관리 프로그램은 이 newWorkspace를 사용하면 된다. 

 <global-store> 
  <reference-storage>default-dc</reference-storage>
 </global-store>


 <workspaces>
  <workspace name="default_workspace" default="true">
   <default-locale>ko</default-locale>
   <reference-storage>default-dc</reference-storage>
  </workspace>

  <workspace name="other_workspace">
   <default-locale>en</default-locale>
   <reference-storage>default-dc</reference-storage>
  </workspace>

  <workspace name="other2_workspace">
   <default-locale>en</default-locale>
   <reference-storage>default-mm</reference-storage>
  </workspace>

 </workspaces>
 
 
 <storage id="default-mm" type="memory">
 </storage>

 <storage id="default-dc" type="db.oracle">

 <database-controller>
  <controller-name>framework default database service</controller-name>
  <database-manager>
   <!-- Development server2 ,novision -->

   <description>Oracle database manager.</description>
   <configured-object>
    <class-name>com.bleujin.framework.db.manager.Oracle9iCacheDBManager</class-name>
    <constructor>
     <constructor-param>
      <description>jdbcURL</description>
      <type>java.lang.String</type>
      <value>jdbc:oracle:thin:@novision:1521:bleujin</value>
     </constructor-param>
     <constructor-param>
      <description>userId</description>
      <type>java.lang.String</type>
      <value>al</value>
     </constructor-param>
     <constructor-param>
      <description>password</description>
      <type>java.lang.String</type>
      <value>redf</value>
     </constructor-param>
     <constructor-param>
      <description>connectionLimit</description>
      <type>int</type>
      <value>10</value>
     </constructor-param>
    </constructor>
   </configured-object>
   
  </database-manager>
  <test-query>select 1</test-query>

 </database-controller>
 </storage>
 
위와 같이 설정되 있을 경우 AL은 1개의 GlobalStorage와 3개의 WorkspaceStorage를 가진다.
GlobalStorage는 주로 NodeType과 Sequecne등의 Global 객체 정보를 가지고 있으며 개개의 Workspace는 개별적인 Tree 형태의 Node 구조를 가지고 있다.

특징적인 점은 other_workspace와 other_workspace는 같은 DB를 참조하고 있다. Workspace는 Storage형태가 메모리이든(물론 이경우 매번 리셋된다.) DB이든 상관하지 않으며 다른 DB 종류일수도 있고 같은 DB이지만 Schema만 다를 수 있고 심지어는 같은 DB의 같은 Table에 여러개의 workspace 정보를 관리할 수도 있다.

이런 구조를 택한 이유는 Workspace로 관리하고자 하는 Node의 양이 상황에 따라 다르기 때문이다. 이를테면 A Workspace가 전체 노드의 50%이상이고 나머지 50% 이하를 9개의 Workspace가 관리한다면 A Workspace만 별도의 DB로 관리하고 나머지 9개를 하나의 Storage로 공유하여 사용할 수 있다.

AnotherLore.logn(Credential)로 접속을 하면 default=true인 Default Worksapce로 접속하고 일반적으로는 AnotherLore.login(Credintial, workspaceName)으로 별도의 Session을 할당받는다.

기본적으로는 A workspace로 login해서 얻은 NodeA는
B Workspace의 NodeB를 Node.getIdentifier로 언제든 참조할 수 있지만 각각의 Workspace는 개별적인 SecurityFilter를 관리하기 때문에 특정 ShareNode만 노출시킬 수도 있다.









'Framework > Another Lore' 카테고리의 다른 글

AL : Permission  (1) 2009.04.30
AL : 현재의 난제들  (0) 2009.04.30
AL : Property Type Conversion  (0) 2009.04.26
AL : Reading  (0) 2009.04.25
AL : Link  (0) 2009.04.24
Posted by bleujin
Framework/Another Lore2009. 4. 26. 06:54

When the value of a property is read or written using a type different from that declared for the property, the repository attempts a type conversion according to the following rules. Note that even in cases where the type conversion is
defined in terms of standard JDK type conversion method, failure of conversion must only ever cause a ValueFormatException to be thrown and never any exception defined in the JDK API.



From STRING To

INPUTSTREAM: The string is encoded using UTF-8.
DATE: If the string is in the format(yyyyMMdd hh24miss) From DATE To, it is converted directly,
     otherwise a ValueFormatException is thrown.
DOUBLE: The string is converted using java.lang.Double.valueOf(String).
DECIMAL: The string is converted using the constructor java.math.BigDecimal(String).
LONG: The string is converted using java.lang.Long.valueOf(String).
BOOLEAN: The string is converted using java.lang.Boolean.valueOf(String).
OBJECT : The string(String)


From BINARY To

STRING: return binary.toXML().toString()
OBJECT : return binary (BinaryPropertyValue)
All Others: A ValueFormatException is thrown.



From DATE To

STRING: The date is converted to the following format:
   yyyyMMdd hh24miss(gregorian
INPUTSTREAM: The date is converted to a string, and this string is encoded in UTF-8.
DOUBLE: The date is converted to the number of milliseconds
     since 00:00 (UTC) 1 January 1970 (1970-01-01T00:00:00.000Z).
     If this number is out-of-range for a double, a ValueFormatException is thrown.
DECIMAL: The date is converted to the number of milliseconds
      since 00:00 (UTC) 1 January 1970(1970-01-01T00:00:00.000Z).
LONG: The date is converted to the number of milliseconds
      since 00:00 (UTC) 1 January 1970 (1970-01-01T00:00:00.000Z). 
      If this number is out-of-range for a long, a ValueFormatException is thrown.
OBJECT : The date(Calendar of Gregorian)
All Others: A ValueFormatException is thrown.


From DOUBLE To

STRING: The double is converted using java.lang.Double.toString().
INPUTSTREAM: The double is converted to a string, and this string is encoded in UTF-8.
DECIMAL: The double is converted using the constructor java.math.BigDecimal(double).
DATE: The double is coerced to a long using standard Java type coercion
      and interpreted as the number of milliseconds since 00:00 (UTC) 1 January 1970(1970-01-01T00:00:00.000Z).
      If the resulting value is out of range for a date, a ValueFormatException is thrown.
LONG: Standard Java type coercion is used.
OBJECT : The double (Double)
All Others: A ValueFormatException is thrown.


From DECIMAL To

STRING: The decimal is converted using java.math.BigDecimal.toString().
INPUTSTREAM: The decimal is converted to a string, and this string is encoded in UTF-8.
DOUBLE: The decimal is converted using java.math.BigDecimal.doubleValue().
DATE: The decimal is converted to a long and interpreted as the number of milliseconds
      since 00:00 (UTC) 1 January 1970 (1970-01-01T00:00:00.000Z).
      If the resulting value is out of range for a date, a ValueFormatException is thrown.
LONG: The decimal is converted using java.math.BigDecimal.longValue().
OBJECT : The decimal(BigDecimal)
All Others: A ValueFormatException is thrown.


From LONG To

STRING: The long is converted using java.lang.Long.toString().
INPUTSTREAM: The long is converted to a string, and this string is encoded in UTF-8.
DECIMAL: The double is converted using the method java.math.BigDecimal.valueOf(long).
DATE: The long is interpreted as the number of milliseconds
     since 00:00 (UTC) 1 January 1970 (1970-01-01T00:00:00.000Z).
     If the resulting value is out of range for a date, a ValueFormatException is thrown.
DOUBLE: Standard Java type coercion is used.
OBJECT : The long(Long)
All Others: A ValueFormatException is thrown.


From BOOLEAN To

STRING: The boolean is converted using java.lang.Boolean.toString().
INPUTSTREAM: The boolean is converted to a string, and this string is encoded in UTF-8.
OBJECT : The boolean(Boolean)
All Others: A ValueFormatException is thrown.



From NAME

STRING: The name is converted to qualified name(IDString)
INPUTSTREAM: The name is converted to a string, and then encoded using UTF-8.
OBJECT : The name(IDString)
All Others: A ValueFormatException is thrown.


From PATH

STRING: The name is converted to qualified name(IDString)
INPUTSTREAM: The name is converted to a string, and then encoded using UTF-8.
OBJECT : The node of path(Node)
All Others: A ValueFormatException is thrown.


From REFERENCE

STRING: The reference is converted to string
INPUTSTREAM: The name is converted to a string, and then encoded using UTF-8.
OBJECT : The node of identifier(Node)
All Others: A ValueFormatException is thrown.



From TEXT

STRING: The text is converted to string
INPUTSTREAM: The text is converted to a string, and then encoded using UTF-8.
OBJECT : The text is converted to stringBuffer
All Others: A ValueFormatException is thrown.



From VERTUAL_TEXT

STRING: The text is converted to string
INPUTSTREAM: The text is converted to a string, and then encoded using UTF-8.
OBJECT : The text is converted to translated string(translate template)
All Others: A ValueFormatException is thrown.





'Framework > Another Lore' 카테고리의 다른 글

AL : 현재의 난제들  (0) 2009.04.30
AL : Workspace  (0) 2009.04.28
AL : Reading  (0) 2009.04.25
AL : Link  (0) 2009.04.24
AL : Observation  (0) 2009.04.24
Posted by bleujin
Framework/Another Lore2009. 4. 25. 05:21

There are three types of read access which a session may have with respect to a particular node(or property), depending on that session's permissions: direct access, traversal access and search access.


Direct Access

Direct access to an item means being able to retrieve it by absolute and relative path and, in the case of nodes, by identifier. Let p(x) return the normalized absolute path of item x,  id(x) return the identifier of node x.

For any session S and node N, the statements below must be either all true or all false. If they are all true then S has direct access to N, if they are all false then S does not have direct access to N:

- S.getNodeByPath(p(N)) returns N.
- S.getNodeByUUID(id(x)) return N.
- S.existByPath(p(N)) returns true.
- If N is the root node of the workspace then S.getRootNode() returns N.

For any node N and property P, the statements below must be either all true or all false. p(P) eturn the identifier of property P. If they are all true then N has direct access to P, if they are all false then N does not have direct access to P:

- N.getProperty(p(P)) returns P.
- N.hasProperties(p(P)) return true.
- N.getString(p(P)) return P.getString()

Traversal Access

Traversal access to an node N means that it is returned when iterating over the link of a node.

For any given session S and node N, the statements below must be either both true or both false. If they are both true then S has traversal access to N, if they are both false then S does not have traversal access to N:

- S has access to M where M is the parent of N, and N appears among the node in the iterator returned by either M.getChildNode(int maxDepth) or  M.getChildNode() or M.getChildNode(Page page) or getNodes(LinkType.Child, Page.ALL)
- If N is the parent node of a node M then M.getParent() returns N.
- If N has property thern Node.hasProperties() returns true.
- If N is the member node of a node M, and N apperas among the node in the iterator returned by either M.getMemberNode(Page.ALL) or getNodes(LinkType.Member, Page.ALL)
- If N has link of a node M, and N apperas among the node in the iterator<Link> returned by either M.getLinks(Page.ALL) or M.getLinks(linkType, page). Link consist of fromNode and toNode with linkType

Export
Exporting a subgraph within a workspace can be done with
Exporter.exp()


Search Access

Search a subgraph within a workspace can be done with
Node.searchNode(String searchText)
currently the search engine is Lucene

Effect of Access Denial on Read

If a repository restricts the read access of a session, then the nodes and properties to which that session does not have read access must appear not to exist. For example, the iterator returned on N.getNodes will not include subnodes of N to which the session in question does not have read access. In other words, lack of read access to an item blocks access to both information about the content of that item and information about the existence of that item.

Node Information
This method provides access to the current Session.
    Session node.getSession()
These methods provide information about the location of an node within the workspace hierarchy:
    String node.getPath()
returns the name of the node
    String node.getName()
returns the ancestor of the node that is at the specified depth below the root node.
    Node node.getParent()
returns the name of the node
    String node.getExplain()
returns the more information of the node
    NodeBean node.getBean()
This method is used to determine the repository-level semantic identity of two node.
    boolean node.isSame(Node n2) or boolean node.isSameVersion(Node n2)
The method returns the identifier of a node.
    String Node.getIdentifier()

Iterators

Methods that return a set of Node or Property objects do so using a GenericIterator, subclasses of Iterator.


Reading Properties

The generic value getter for single value properties is
    Value Property.getValue().
returns one of the constants of Property.Type indicating the property type of the Value.
    Property.Type Value.getType()
The length of a value in a single-value property. is returned by
    long Property.getLength()

Property Type Conversion
When the value of a property is read or written using a type different from that declared for the property, the repository attempts a type conversion according to the [not defined] rules. Note that even in cases where the type conversion is
defined in terms of standard JDK type conversion method, failure of conversion must only ever cause a  ValueFormatException to be thrown and never any exception defined in the JDK API.

Namespace Mapping


'Framework > Another Lore' 카테고리의 다른 글

AL : Workspace  (0) 2009.04.28
AL : Property Type Conversion  (0) 2009.04.26
AL : Link  (0) 2009.04.24
AL : Observation  (0) 2009.04.24
AL : Versioning Model  (0) 2009.04.24
Posted by bleujin