2012. 8. 24. 10:15 IT

[purpose]
- RESTful API Documentation 

- Using Spring framework 

- autogenerated


[Review solutions]
 SPRINGDOCLET : javadoc style로 rest api user document 로는 부족함

RESTDOCLET : 목적에 부합
maven plugin, web application type
param description not support, rest error code not support
controller에 정의한 mapping 미인식

WSDOC : simple 충분하지않음 rest error, param description nor support

SWAGGER : spring 지원 불충분, 복잡...

[try - restdoclet]
- fail maven setting
- use library
- process : java source -> xml using xmldoc -> xml using jibx -> html using template
- preperation
spring restful application source & libraries
library : jibx, spring, log4j, commons, restdoc, tools.jar, ant

retdoclet lib : restdoclet-doclet-2.2.0.jar, restdoclet-plugin-2.2.0.jar, jibx-run-1.2.1.jar, from https://oss.sonatype.org/index.html#nexus-search;classname~RESTdoclet

spring lib : spring-web-3.0.5.RELEASE.jar, spring-context-3.0.5.RELEASE.jar, commons-lang-2.6.jar,

javadoc lib : tools.jar, 

ant lib : ant-launcher.jar, ant.jar

etc : commons-collections-3.2.jar,  log4j-1.2.15.jar, 

 

- setting
srcpath
version
excludes method
reportdir

demo sample.

import groovy.text.SimpleTemplateEngine

import org.apache.commons.collections.CollectionUtils

import org.jibx.runtime.JiBXException

import com.iggroup.oss.restdoclet.doclet.XmlDoclet

import com.iggroup.oss.restdoclet.doclet.type.*

import com.iggroup.oss.restdoclet.doclet.util.*

import com.iggroup.oss.restdoclet.plugin.io.*

import com.iggroup.oss.restdoclet.plugin.util.ServiceUtils

import com.sun.tools.javac.util.*

import com.sun.tools.javadoc.*

class MySample {

static def SERVICES_TEMPLATE = "src/main/resources/services.jsp";

static def SERVICE_TEMPLATE = "src/main/resources/service.jsp";

def reportDir;

def restsrcDir;

def packageNames = []

def appname = ""

def version = 1.0

static main(args) {

def me = new MySample();

me.restsrcDir = "../my-rest-web/src/main/java"

me.reportDir = "../my-rest-web/doc/restdoc"

me.packageNames = {"com.simple.rest"}

me.appname =  ["APPLICATION":"my-rest" ]

me.version = ["version":1.0, timestamp:new Date()]

me.setup()

me.makexmldoc()

me.build();

me.clean()

}

def ant = new AntBuilder()

def controllers = []// ArrayList<Controller> 

List<String> excludes = ["exceptionHandler"];

def outputDirectory = "my-rest";

def setup(){

clean();

ant.delete(dir:reportDir, quiet:true, verbose:false)

ant.mkdir(dir:reportDir);

}

def clean(){

ant.delete(dir:"com", quiet:true, verbose:false);

ant.delete(dir:"target/restdoc", quiet:true, verbose:false);

}

// gathering Controller.java files ( except Sample)

def getRestControllers(){

def list = []

new File(restsrcDir).eachFileRecurse {

if(it.name.endsWith("Controller.java")){

list.add(it)

}

}

return list;

}

def makexmldoc(){

Context context = new Context();

Options compOpts = Options.instance(context);

compOpts.put("-sourcepath", new File(restsrcDir).absolutePath);

compOpts.put("-classpath", getClassLibs());

ListBuffer<String> javaNames = new ListBuffer<String>();

for (File fileName : getRestControllers()) {

javaNames.append(fileName.getPath());

}

ListBuffer<String> subPackages = new ListBuffer<String>();

for (String packageName : packageNames) {

log.info("Adding sub-packages to documentation path: " + packageName);

subPackages.append(packageName);

}

new Messager(context,"application")

JavadocTool javadocTool = JavadocTool.make0(context);

def bQuiet = true

def rootDoc = javadocTool.getRootDocImpl(

                    "", "utf-8", new ModifierFilter(ModifierFilter.ALL_ACCESS),

                    javaNames.toList(), new ListBuffer<String[]>().toList(), false,

                    subPackages.toList(), new ListBuffer<String>().toList(),

                    false, false, bQuiet);

rootDoc.env.silent = true; // clear javadoc warning message

new XmlDoclet().start(rootDoc);

def myDir = "target/restdoc"

ant.mkdir(dir:myDir)

new ControllerUriAppender().add(rootDoc, myDir);

}

def build()   {

def myDir = "target/restdoc"

   javadocs(myDir);

   services(myDir);

}

def javadocs(rootDir){

new File(rootDir).eachFileRecurse {

if(it.name.endsWith(Controller.FILE_SUFFIX)){

final Controller cntrl = JiBXUtils.unmarshallController(it);

if (!controllers.contains(cntrl)) {

  controllers.add(cntrl);

}

}

}

}

def services(baseDirectory)  {

  List<Service> services = new ArrayList<Service>();

  def uriMethodMappings = [:]

  HashMap<String, Controller> uriControllerMappings =

 new HashMap<String, Controller>();

  HashMap<String, Collection<Uri>> multiUriMappings =

 new HashMap<String, Collection<Uri>>();

  for (Controller controller : controllers) {

 for (Method method : controller.getMethods()) {

if (excludeMethod(method)) { continue; }

Collection<Uri> uris = method.getUris();

if (!uris.isEmpty()) {

String multiUri = uris.join(", ")

multiUriMappings.put(multiUri, uris);

ArrayList<Method> methodList = uriMethodMappings.get(multiUri);

if (methodList == null) {

  methodList = new ArrayList<Method>();

  uriMethodMappings.put(multiUri, methodList);

}

methodList.add(method);

uriControllerMappings.put(multiUri, controller);

}

 }

  }

  int identifier = 1;

  for (String uri : uriControllerMappings.keySet()) {

 Controller controller = uriControllerMappings.get(uri);

 ArrayList<Method> matches = uriMethodMappings.get(uri);

 Service service =

new Service(identifier, multiUriMappings.get(uri), new Controller(

controller.getType(), controller.getJavadoc(), matches));

 services.add(service);

 service.assertValid();

 generateServiceHtml( appname, version, service)

 identifier++;

  }

  Services list = new Services();

  for (Service service : services) {

 org.apache.commons.collections.Predicate predicate =

new ControllerTypePredicate(service.getController().getType());

 if (CollectionUtils.exists(list.getControllers(), predicate)) {

ControllerSummary controller = (ControllerSummary) CollectionUtils.find(list.getControllers(), predicate);

controller.addService(service);

 } else {

ControllerSummary controller = new ControllerSummary(service.getController().getType(), service.getController().getJavadoc());

controller.addService(service);

list.addController(controller);

 }

  }

  generateServiceListHtml(  appname, version, list.getServices())

}

def generateServiceListHtml(  param, props, svcs){

def binding = ["param" : param,  "props":props, "services":svcs]

def toFile = reportDir+"/services.html"

generateHtml(toFile, binding, SERVICES_TEMPLATE);

}

def generateServiceHtml(  param, props, svc){

def binding = ["param" : param,  "props":props, "service":svc]

def toFile = reportDir+"/service-" + svc.identifier  + ".html"

generateHtml(toFile, binding, SERVICE_TEMPLATE);

}

def generateHtml(def toFile, def binding, def templateFile, encoding = "UTF-8"){

def reader = new File(templateFile).newReader();

def template = new SimpleTemplateEngine().createTemplate(reader).make(binding);

new File(toFile).write(template.toString(), encoding)

}

private boolean excludeMethod(Method method) {

return excludes.contains(method.getName().equalsIgnoreCase())

}

// for javadoc compile

def getClassLibs(){

return [

"../my-rest/WEB-INF/lib/commons-logging.jar",

"../my-rest/WEB-INF/lib/spring-XXXX-X.X.X.RELEASE.jar"

// ...  other libraries

].join(";")

}

}


ControllerUriAppender ( for controller's uri appending)

import static com.iggroup.oss.restdoclet.doclet.util.AnnotationUtils.*

import static com.iggroup.oss.restdoclet.doclet.util.JiBXUtils.marshallController

import groovy.util.slurpersupport.GPathResult

import groovy.xml.*

import org.springframework.web.bind.annotation.RequestMapping

import com.iggroup.oss.restdoclet.doclet.type.Controller

import com.sun.javadoc.*

class ControllerUriAppender {

def add(RootDoc rootDoc, outputpath){

for (ClassDoc classDoc : rootDoc.classes()) {

if (isAnnotated(classDoc, org.springframework.stereotype.Controller.class)) {

  AnnotationValue value = elementValue( classDoc, RequestMapping.class, "value")

  def rooturi  = value.toString().replaceAll("\"","");

  def xmlname = classDoc.qualifiedName().replace('.' as char, File.separatorChar) + Controller.FILE_SUFFIX

  def root = new XmlSlurper().parse(new File(xmlname));

  if(value!=null){

  root.method.each{ mtd ->

  def mtdname = mtd.name.toString()

  if(mtdname.endsWith("Null")||mtdname.endsWith("Error")){ // remove null filter mapping

  mtd.replaceNode{}

  }else{

  if(mtd.uri==""){

  mtd.javadoc + { 

  uri {

  uri(rooturi)

  deprecated(false)

  type("java.lang.String")

  }

  }

  }else{

  mtd.uri.uri = rooturi + mtd.uri.uri

  }

  //// add parameter comment

  def mtddoc = classDoc.methods().find {

  it.name().equals(mtdname) 

  }

  mtd."request-param".each{ rqparam ->

  if(rqparam.javadoc==""){

  def paramdoc = mtddoc.paramTags().find{ paramtag ->

  paramtag.parameterName().equals(rqparam.name)

  }

  rqparam.type + {

  javadoc(paramdoc?.parameterComment() )

  }

  }

  }

  // add response-param exception javadoc

  mtd."response-param".each{ rpparam ->

  if(rpparam.name.equals("Exception")){

  def exceptiondoc = mtddoc.throwsTags().find{ paramtag ->

  paramtag.exceptionName() .equals(rpparam.name.toString())

  }

  if(rpparam.javadoc==""){

  rpparam.type + {

  javadoc(exceptiondoc?.exceptionComment() )

  }

  }

  }

  }

  }

  }

  }

  def outputBuilder = new StreamingMarkupBuilder()

  outputBuilder.encoding ="UTF-8"

  def result = outputBuilder.bind{ 

  mkp.xmlDeclaration()

  mkp.yield root 

  }

  def ant = new AntBuilder()

  def myDir = outputpath + "/"+xmlname

  ant.touch(file:myDir, mkdirs:true, verbose:false)

  def fwriter = new FileWriter(myDir  );// .write(result);

  XmlUtil.serialize(result, fwriter)

  fwriter.close();

}

}

}

}

services.jsp ( template 1 )

<div class="projectDetails">

   <h1 class="mainHeading">

      <span class="title">RESTdoclet for:</span>

      <span class="application">

         <em><span class="application">${param["APPLICATION"]}</span></em>

      </span>

   </h1>

   <h3>

      <span class="version">Version: <em>${props.version}</em></span>

      <span class="timestamp">Creation: <em>${props.timestamp}</em></span>

   </h3>

</div>

<div class="services">

   <table class="topLevel">

      <thead>

      <tr>

         <th>URI</th>

         <th>Actions</th>

      </tr>

      </thead>

      <tbody><% services.each{ service -> %>

            <tr>

               <td class="uri"><%  service.uris.each{ urix -> %>

                     <div class="active">

                        <a href="service-${service.identifier}.html">${urix.uri}</a>

                     </div><% } %></td>

               <td>

                  <table class="methods">

                     <% service.methods.each{ method-> %>

                        <tr>

                           <td class="requestMethod">${method.requestMethod}</td class="javadoc">

                           <td class="javadoc">${method.javadoc}</td>

                        </tr><% } %>

                  </table>

               </td>

            </tr>

      <% }  %>

      </tbody>

   </table>

</div>

service.jsp ( template 2 )

<div class="projectDetails">

   <h1 class="mainHeading">

      <span class="title">REST for:</span>

      <em><a href="services.html">${param["APPLICATION"]}</a></em>

      <% service.uris.each{ urix -> %><span class="path"><em>${urix.uri}</em></span><% } %>

   </h1>

   <h3>

      <span class="version">Version: <em>${props.version}</em></span>

       <span class="timestamp">Creation: <em>${props.timestamp}</em></span>

   </h3>

</div>

<% service.controller.methods.each{ method-> %>

   <div class="method">

      <h3 class="httpMethod">${method.requestMethod}</h3>

      <p class="methodJsDoc">${method.javadoc}</p>

      <% if(method.pathParams.size()>0 || method.restParams.size() >0 || method.requestParams.size()>0 ){ %>

         <div class="input">

            <h3>Request input</h3>

            <table class="topLevel methodDetails">

               <thead><tr><th>Name</th><th>Description</th><th>Param Type</th></tr></thead>

               <tbody>

               <% method.pathParams.each{ parameter-> %><tr>

                     <td class="name">${parameter.type} ${parameter.name}</td>

                     <td class="javadoc">${parameter.javadoc}</td>

                     <td class="path">Path (Mandatory)</td>

                  </tr><% } %>

               <% method.restParams.each{ parameter-> %><tr>

                     <td class="name">${parameter.type} ${parameter.name}</td>

                     <td class="javadoc">${parameter.javadoc}</td><td class="path">REST</td>

                  </tr><% } %>

<% method.requestParams.each{ parameter-> %><tr>

                     <td class="name">${parameter.type} ${parameter.name}</td>

                     <td class="javadoc">${parameter.javadoc}</td>

                     <td class="path">Request<% if(parameter.required){ %>(Mandatory)<% }else{ %>(Optional)<% } %>

                        <% if(parameter.defaultValue){ %>(Default=${parameter.defaultValue})<% } %></td>

                  </tr><% } %>

               </tbody>

            </table>

         </div>

      <% } %>

<% if( method.responseParams.size()>0){ %>

         <div class="response">

            <h3>Response contents</h3>

            <table class="topLevel methodDetails">

               <thead><tr><th class="type">Response Type</th><th class="description">Description</th></tr></thead>

               <tbody>

               <% method.responseParams.each{ parameter-> %><tr>

                     <td class="type">${parameter.type}</td>

                     <td class="javadoc">${parameter.javadoc}</td>

                  </tr><% } %>

               </tbody>

            </table>

         </div>

      <% } %>

   </div>

<% } %>


ref) http://mestachs.wordpress.com/2012/08/06/rest-api-documentation/


posted by smplnote
2012. 8. 18. 12:00 IT
세상은 왜!

xacml (eXtensible Access Control Markup Language)
saml이 authentication(인증)에대한 것이라면 xacml은 authorization(ㅎㅓ가)

good
- it's standard. no lock in by OASIS
- no hard coded. externalize & xml
- policy based approach, attribute based approach
- fine-grained authorization



reference
http://en.m.wikipedia.org/wiki/XACML
http://blogs.kuppingercole.com/kuppinger/2009/10/22/xacml-why-it-is-so-important/
xacmlinfo.com/2011/10/30/whyxacml/
http://xacmlguide.blogspot.kr/2012/06/introduction-to-xacml-xacml-stands-for.html?m=1
validator http://www.webfarmr.eu/xacml-validator-schema-axiomatics/
posted by smplnote
2012. 8. 17. 20:35 IT

배경 : xacml 을 버튼에 대해서도 적용해달란다.

이미 적용된 상태에서 또다른 솔루션 (spring security 등등)을 적용하는 경우 관리 비용이 증가할 것을 우려하여, 한곳에서 관리하고 싶어했다. 


구현방향 : interceptor대신 validation을 처리할 controller 와 javascript function 을 제공. 


1. javascript로 object (button, image etc.. ) 의 id를  전달.

2. controller에 해당 object 에 대한 권한 여부를 확인

3. 결과에 따라 disable, enable 처리


단점 : 

- 버튼 권한까지 서버에게 물어봐야 하니???

- id가 중복될 경우 잘못 처리될 수 있음.


구현내용

1. javascript function

$.authorizeWebElement = function(objectid){

$.ajax({

type: "POST",

dataType: "json",

url:  "/authorize" ,

data : {"objectId":objectid},

success: function(data){

$(objectid)[data.message]();

}

});

};


2. controller

public @ResponseBody Map<String, Object> authorize(@RequestParam(value="objectId") String objectId, HttpServletRequest request){

ACManager acm = ACManager.getInstance(this.pdpPath); 

                List<String> roles = getRoles(request); // get user role from session or persistence data 

                Map map = new HashMap(); 

if(acm.evaluate(roles, objectId)){

map.put("message ", " show");

}else{

map.put("message ", " hide");

}

return map;

}


3. screen 

function xxx(){

    $.authorizeUI("#OnlyAdminAvailableButton");

}


cf) 주의사항 

반드시 elementId 는 유일해야한다. ...

posted by smplnote
2012. 8. 17. 19:41 IT

목적 : refresh 기능을 추가했더니 가끔 끄고 싶을때가 있더라..

그래서 버튼으로 추가구현. 


사용기술 : jquery , jqgrid 의 custom button

http://www.trirand.com/jqgridwiki/doku.php?id=wiki:custom_buttons

setTimeout http://www.w3schools.com/js/js_timing.asp



1. set global variable

var timeoutHnd

var LIST_ID = "#mylist";

var BTN_ID = "#reload";t

2. add refresh function

        // listid : list div element id ex) #mylist

        // btnid : button element id ex) #myrefreshbuttono

        // timeoutHnd : global variable for timeout handler 

       // change : boolean . if you want change button text, icon then true.

function gridRefresh(listId, btnId,timeoutHnd,change){

if(timeoutHnd){

clearTimeout(timeoutHnd);

timeoutHnd = null;

var on = $(btnId).attr("title")=="start refresh";

if(change&&on || !change&&!on){

timeoutHnd = setTimeout(function () { $(listId).trigger('reloadGrid'); },10000);

}

if(change){

changeRefreshButton(btnId);

}

return timeoutHnd;

};

function changeRefreshButton(btnId){

if($(btnId).attr("title")=="stop refresh"){

$(btnId).attr("title","start refresh");

$(btnId).find(".ui-icon").removeClass("ui-icon-cancel").addClass("ui-icon-refresh");

}else{

$(btnId).attr("title","stop refresh");

$(btnId).find(".ui-icon").removeClass("ui-icon-refresh").addClass("ui-icon-cancel");

}

}



3. set timer

loadComplete: function(){

if( $( LIST_ID ).jqGrid("getGridParam", "records")>0){ 

timeoutHnd = gridRefresh(LIST_ID, BTN_ID ,timeoutHnd, false);

$( BTN_ID ).show();

}else{ // 0건이면 refresh 안하고 버튼 숨김.

if(timeoutHnd){

clearTimeout(timeoutHnd);

timeoutHnd = null;

$( BTN_ID ).hide();

}

 },



3. add button  to navigator


}).navGrid("#pager")  

.navButtonAdd("#pager5",

  {title:"stop refresh",

   caption:"",

   buttonicon:"ui-icon-cancel", 

   id:"reload" , 

   onClickButton: function(){ gridRefresh(LIST_ID, BTN_ID  ,timeoutHnd,true);}

} );



posted by smplnote
2012. 8. 16. 09:08 IT

목적 : Access Control 표준이 XACML 이란다.


경과 : 

몇가지 xacml 관련 구현체들을 찾아보았는데,

상용이 아닌 것으로 가장 활발한 곳은 jboss xacml 구현체인 picketbox 였다. 

Picketbox는  Java Security Framework의 하나인데,  Oasis XACML v2.0 에 호환되는 엔진을 제공한다. 



절차 


1. xacml library download

from http://www.jboss.org/picketbox/downloads

jbossxacml-2.0.8.Final.jar


2. add library 

WEB-INF/lib 또는 기타 classpath 상에 해당 jar 파일을 배치한다.


3. register ACL Interceptor to spring mvc context 

> context/servlet-context.xml

<mvc:interceptors>        <mvc:mapping path="/*"/>                    

                     <bean class="com.MyACLInterceptor" >

                     <property name="config" value="config/policyConfig.xml"/><!-- pdp file classpath -->

                      </bean>

</mvc:interceptor>

...

</mvc:interceptors> 

4. write PDP (Policy Decision Point)  configuration file
Caching과 관련해서는 아래 Reference 를 참조

> config/policyConfig.xml

<ns:jbosspdp xmlns:ns="urn:jboss:xacml:2.0">

           <ns:Policies>

                     <ns:PolicySet>

                                <ns:Location>config/mypolicyset.xml</ns:Location>

                     </ns:PolicySet>

           </ns:Policies>

<ns:Locators>

<ns:Locator Name="org.jboss.security.xacml.locators.JBossPolicySetLocator"/>

</ns:Locators>

</ns:jbosspdp>

5. policy set 정의

> config/mypolicyset.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<PolicySet xmlns="urn:oasis:names:tc:xacml:2.0:policy:schema:os"  PolicySetId="policyset"

  PolicyCombiningAlgId="urn:oasis:names:tc:xacml:1.0:policy-combining-algorithm:first-applicable">

           <Target />

           <Policy xmlns="urn:oasis:names:tc:xacml:2.0:policy:schema:os"

                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                     xsi:schemaLocation="urn:oasis:names:tc:xacml:2.0:policy:schema:os

        access_control-xacml-2.0-policy-schema-os.xsd"

                     PolicyId="my-policy"

                     RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:first-applicable">

                     <Description>access control policy</Description>

                     <Target />

 <Rule RuleId="myruleid" Effect="Permit"><!— Permit / Deny  -->

           <Description>my rule</Description>

           <Target >

           <Subjects/>

           <Resources> 

                    <Resource> 

                              <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-regexp-match"> 

                                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">^/( accessuri1|accessuri2)</AttributeValue> 

                                        <ResourceAttributeDesignator 

                                        AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" DataType="http://www.w3.org/2001/XMLSchema#string"/> 

                                        </ResourceMatch> 

        </Resource> 

         </Resources> 

           </Target>

           <Condition>

                      <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-is-in">

                                <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">ACCESSIBLE_ROLE_NAME</AttributeValue>

                                <SubjectAttributeDesignator

                                          DataType="http://www.w3.org/2001/XMLSchema#string" AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role" />

                     </Apply>

           </Condition>

</Rule>

. . . 

<Rule RuleId="ImplicitDeny" Effect="Deny" />

</Policy>

</PolicySet>


-   policy-combining-algorithm은 경우에 따라 적절하게 사용할 것. ( first-applicable은 첫번째 걸리는 Rule을 따른다는 의미 )

- Rule의 Effect는 Permit 아니면 Deny 를 선택할수 있음

ResourceMatch  로 대상 Resource 를 정의할 수 있음

Condition 으로 Rule의 detail을 표현할 수 있음


cf) Policy 작성 BP. ( Ref. 1 - 2.7 )

- policy와 file 이름을 동일하게.

- 룰 조합 규칙은 가능한 심플하게. first-applicable 권고

- Permit, Deny 를 섞기말고 가능한 한쪽으로 모는것이 좋음. 


6. Interceptor 작성

MyACLInterceptor.java

public class MyACLInterceptor extends HandlerInterceptorAdapter {

private String config = "";

public  String getConfig(){

return this.config;

}

public void setConfig(String config){

this.config = config;

}

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response, Object handler) throws Exception {

if (!hasAccessRole(request)) {

throw new Exception("access not allowed.");

}

return true;

}

private boolean hasAccessRole(HttpServletRequest request) {
String  resourceId  = request.getRequestURI(); // URI
List<String> userRoles = getUserRoles(request); // User ROLES
ACLManager acm = ACLManager.getInstance(this.config); 
return acm.evaluate(userRoles, resourceId);
}
        List<String> getUserRoles(HttpServletRequest request){
              return "get user role from session or persistence data".....
        }
}


7. ACLManager 작성


public class ACLManager {

private static ACLManager instance;

private PolicyDecisionPoint pdp = null;

public boolean evaluate(List<String>userRoles, String resourceId) throws Exception{

RequestContext request = createXACMLRequest(userRoles, resourceId);

// try { request.marshall(System.out); } catch (IOException e) { }

ResponseContext response = this.pdp.evaluate(request);

return XACMLConstants.DECISION_PERMIT == response.getDecision();

}


public RequestContext createXACMLRequest(List<String>userRoles, String resourceId) throws Exception{

RequestContext requestCtx = RequestResponseContextFactory.createRequestCtx();

     SubjectType subject = new SubjectType();

     subject.getAttribute().add(RequestAttributeFactory.createStringAttributeType(XACMLConstants.ATTRIBUTEID_SUBJECT_ID, "myissuer", "mysubject"));

     for(String role : userRoles) {

        AttributeType attSubjectID = RequestAttributeFactory.createStringAttributeType(XACMLConstants.ATTRIBUTEID_ROLE, "myissuer", role);

        subject.getAttribute().add(attSubjectID);

     }

     ResourceType resourceType = new ResourceType();

     resourceType.getAttribute().add(RequestAttributeFactory.createStringAttributeType(XACMLConstants.ATTRIBUTEID_RESOURCE_ID, null, resourceId ));

     

     ActionType actionType = new ActionType();

     actionType.getAttribute().add(RequestAttributeFactory.createStringAttributeType("action-id", "myissuer", "read"));


     RequestType requestType = new RequestType();

     requestType.getSubject().add(subject);

     requestType.getResource().add(resourceType);

     requestType.setAction(actionType);

     requestCtx.setRequest(requestType);

return requestCtx;

}

private void setPDP(String pdpPath) throws Exception {

  InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(pdpPath);

  this.pdp = new JBossPDP(is); 

}

private ACLManager(String pdpPath) throws Exception{

setPDP(pdpPath);

}

public static synchronized ACLManager getInstance(String pdpPath) throws Exception{

if (instance == null) {

instance = new ACLManager(pdpPath);

}

return instance;

}

}



[References]

1) XACML Function List : http://fedora-commons.org/download/2.2/userdocs/server/security/XACMLPolicyGuide.htm 

XACML caching for Performance : https://community.jboss.org/wiki/XACMLCachingForPerformance


posted by smplnote
2012. 8. 6. 19:12 IT


var timeoutHnd;


...

$(GRID_ID).jqGrid({

...

loadComplete: function(){

if(timeoutHnd){ // 기존 핸들은 clear

clearTimeout(timeoutHnd);

}

if( $(GRID_ID).jqGrid("getGridParam", "records")>0){ // 한건이라도 있으면.. 

timeoutHnd = setTimeout(function () { $(GRID_ID).trigger('reloadGrid'); },10000);

}

},

               ...


// has any issue?

posted by smplnote
2012. 7. 27. 19:20 IT


서버에서 로그를 지우고 싶은데,,, 쉘 스크립트를 짜고 싶지 않았다. 

그래서 그루비 쉘로... ;;



로그 파일을 대상으로 lastModified date가 오늘보다 1달 전인 것들을 

월별 디렉토리에 백업해주는 기능.  cron에 등록해놓으면 편할듯. 


cf) TimeCategory 기능 같은게 편하다... 



import groovy.time.TimeCategory;

def backup( def searchPath, def backupdate, def postfix = ~/.*log$/ ){

        def today = new Date();

        def loglist = new File(searchPath).listFiles().grep(postfix);

        def cnt = 0;

        loglist.each{ file ->

                def dt = new Date( file.lastModified())

                        if( backupdate > dt ){

                                def backupdir = searchPath + "/" + dt.format("yyyyMM")

                                new File(backupdir).mkdir();

                                "mv ${searchPath}/${file.name}  ${backupdir}".execute();

                                cnt++;

                       }

        }

        println " ${cnt} files moved "

}

def log_dir_list = ["/software/log", "/software/log/access" ];

def backupdate;

use(TimeCategory) {

        backupdate = new Date()  - 1.month

}

println "[ LOG Backup ] log < " + backupdate.format("yyyyMMdd") + " (lastModified date)"

log_dir_list.each{

        print "\t log backup target :  ${it}  -> "

        backup(it, backupdate);

}


posted by smplnote
2012. 7. 27. 11:29 IT

a mircro Groovy web framework 


https://github.com/bleedingwolf/Ratpack 

License : Apache License, Version 2.0


groovy 하면 RoR에 영감을 얻었다는 grails가 떠오르지만.. 마찬가지로  Sinatra 에 영향을 받은 Ratpack 이라는 경량 web framework이 있습니다. 


물론 아직 beta 수준이라서 어디 써먹기에는 참 애매하지만... 하여튼 잠깐의 틈을 내서  study. 


홈페이지에 간단하게 설치절차가 나와있지만,

eclipse 기반으로 작업할때 어떻게 하면 좋은지에 대한 경험을 기록하고자 합니다. 




- Requirements

jdk 1.5 이상?

groovy 1.7.1+

gradle

eclipse , groovy eclipse plugin (option)


- ratpack build 

1. 다운로드 받은 ratpack project를 새로운 eclipse groovy project로 생성하고나서 복사합니다.

2. gradle buildDistro 를 실행

cf) [gradle] Using Gradle from Ant (via gradlew) 글을 이용하여 ant build 파일을 이용해 돌릴 수 도 있습니다. 

3. build/libs 위치에 library 들이 모두 생성되었는지 확인되면 OK.


- make ratpack project 

1. eclipse에서 dynamic web project로 HelloRatpack 을 생성.

2. $webapp/WEB-INF/lib  에 위에서 생성한 build/libs 의 모든 jar 를 복사. 
cf) except servlet-*.jar , jetty-*.jar

3. web.xml 에 servlet 을 등록 

  <servlet>

    <servlet-name>Ratpack</servlet-name>

    <servlet-class>com.bleedingwolf.ratpack.RatpackServlet</servlet-class>
    <init-param>
      <param-name>app-script-filename</param-name>
      <param-value>hello.groovy</param-value><!--  WEB-INF/lib/xxx.groovy  -->
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>Ratpack</servlet-name>
    <url-pattern>*</url-pattern>
  </servlet-mapping>
이경우 app-script 파일의 위치는 WEB-INF/lib  으로 고정됨. 

따라서 원하는 곳으로 수정하려면... RatpackServlet 을 상속받아서 하나 만듭니다.
ex) 
class RatpackExtendServlet extends RatpackServlet{
void init() {
if(app == null) {
def appScriptName = getServletConfig().getInitParameter("app-script-filename")
 def fullScriptPath = getServletContext().getRealPath("WEB-INF/${appScriptName}")
app = new RatpackApp()
app.prepareScriptForExecutionOnApp( fullScriptPath )
}
mimetypesFileTypeMap.addMimeTypes(this.class.getResourceAsStream('mime.types').text)
}
  }
}
cf) mime.types 파일도 위 클래스와 같은 위치에 있어야 합니다. (없으면 에러 나죠.. )
cf) 새로 만들었으면 web.xml 의 servlet-class 이름도 바꿔주어야 합니다. 이제부터는 application groovy 파일을 WEB-INF 아래부터 원하는 경로로 배치하면 됩니다. ( WEB-INF 밑으로 잡은 이유는... 웹에서 직접 접근이 안되는 곳이니까요. )
    <servlet-class>com.yourcompany.yourpkgname.RatpackExtendServlet</servlet-class>
      <param-value>ratpack/hello.groovy</param-value><!--  WEB-INF/ratpack/hello.groovy  -->
4.  write your application 
ex) /WEB-INF/ratpack/hello.groovy
set 'port', 8282  // jetty 에서 쓸때만 적용됨. 
get("/") {
    def ua = headers['user-agent']
    "Hello, World! ${ua}"
}
get("/person/:personid") { // path parameter sample
"This is the page for person ${urlparams.personid}"
}
get("/person"){ // paramter sample
"this is your param ${params}"
}

hello.groovy  파일이 바로 ratapck application



5. 배포 및 테스트 (war로 만들어도 됨.)
ant task
<target name="ratpack-deploy">
<war destfile="dist/${ant.project.name}.war" webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}">
<exclude name="**/*test/**"/>
</fileset>
</war>
</target>

6. 확인.
앗 썰렁해. 

기타. 에러페이지는 다음과 같습니다.  ( def x = 1 /0 ; 을 실행하게 했음. ) 



- 평가
GOOD : 
쉽고 가볍다.  (Grails는 Spring Hibernate 같은 거인들 위에 올라타 앉아서 보기엔 쉽지만 까보면 복잡하다는 단점이 있다. 사실 그 모든기능이 필요하진 않을수도 있는데.)
간단한 REST SERVICE 만들때 편하겠다. 
앞으로 성장 가능성이 있다. 

BAD : 
URL 바인딩이 늘어날 경우 한개 파일에 들어가 복잡해진다. (  당연히 분리/모듈화가 진행될듯) 
아직 베타라서 장난감 수준이 아닌가 싶다. Sinatra 수준이 되려면 좀 걸리겠다. 
충분히 성장할 수 있을까? 글쎄... ratpack 자체는 아닐 수 있지만 경량화는 추세인듯. 

ANOTHER THINKING:
최근의 기술 경향은 유연하고 확장성 있는 범용 시스템을 제공하기보다는,
특정 도메인의 필요에 따라 적절한 기능과 성능을 제공하는 SMART한 시스템을 추구하는 경향이 보인다. 
Ratpack도 그렇고, 이벤트방식의 vert.x 도 그렇고... 
기술 트렌드에 대해 계속 관심을 가져야 겠다는 생각을.... 

참고자료 : 
Presentation : Ratpack ( Classy and Compact Groovy WebApps ) - JamesWilliams
동영상 : http://www.infoq.com/presentation/Ratpack 

posted by smplnote
2012. 7. 25. 17:21 IT

어느날 찾아온 ORA-28001 


XE를 쓰고 있으니 License 만료일리는 만무고..


패스워드 변경할때가 다 됐다는 얘기.



조치방법


sqlplus 같은걸로.. connect 하면 로그인 되면서 password 변경하라고 나온다.


그다음에.. 다시 원래 암호로 하고 싶으면... 


alter user XXXXUSER identified by 새암호. 


posted by smplnote
2012. 7. 23. 09:24 IT

[배경]  


- 문서따로 소스따로를 해결하고 싶었다. 

- 매번 변경이 생길때마다 둘다를 손보는건 번거롭다.

- 구글은 REST Java 에 대해 소스로부터 문서 자동 생성으로 방향을 잡았다고 들었다.


[전제사항]


- Spring MVC 를 쓰고 있다. (혹은 annotation에 기반한 개발을 하고 있다.. )

- 대상 클래스들이 classpath에 포함되어 있어야 한다. 



[처리흐름]


1. filter Controller class // 자바소스 중에서 Controller, RequestMapping Annotation을 가진 class들을 추출한다. 


2. filter RequestMapping Method // 추출된 class들에서 RequestMapping Annotation을 가진 method들을 추출한다.


3. get return type // 추출된 method에서 return type을 가져온다. 

    // method.getGenericReturnType()


4. get request mapping method // 사용하고 있다면 mapping method 값도 가져온다. (cf. REST)


5. filter RequestParam arguments // method 의 RequestParam 으로 정의한 parameter 정보도 추출한다. 


6. do pretty print // 예쁘게 출력한다.



[출력결과 샘플]


### XXXXX Specification Document

### Created Mon Jul 23 09:12:08 KST 2012 ###

============================

Resource : xxxxxxx


GET URI : <BASE_URL>/xxxxxxx/{xxxxxxid}

PAREMETERS : N/A

RETURN : Sample

RETURN DETAIL :

{

    "Sample": [

        "ID": "long",

        "Name": "String"

     ]

}



[회고]


1. java, Spring이 제공하는 Annotation 만으로는 문서를 만드는데 필요한 충분한 정보가 부족하더라.

ex. description, 예외처리정보 등... => 개발초기에 custom annotation 을 고려해야겠더라. 


2. 엑셀, 워드로 뽑아주려고 했더니 회사 문서보안시스템이 허용하지 않더라. 

혹시 누군가 필요로 한다면 MS가 추가한 xml의 축복을 누리시길. ( http://www.docx4java.org )


3. 재미나는 작업이었지만 결국 실제적인 도움을 주지는 못했다. (자동생성하기에는 담을 수 있는 내용이 부족했다.) => 다음엔 좀더 가치있게 되도록 사전에 고려를..


4. 반대로 Spec 문서에서 Skeleton 코드를 생성할 수도 있지 않을까... 

(가능은 하지만 당장은 One source, Multi use 를 하는게 더 정신건강에 이로울듯 싶다. ) 

posted by smplnote