├── .gitignore ├── README.md ├── fonts ├── OpenSans-Bold-webfont.eot ├── OpenSans-Bold-webfont.svg ├── OpenSans-Bold-webfont.ttf ├── OpenSans-Bold-webfont.woff ├── OpenSans-BoldItalic-webfont.eot ├── OpenSans-BoldItalic-webfont.svg ├── OpenSans-BoldItalic-webfont.ttf ├── OpenSans-BoldItalic-webfont.woff ├── OpenSans-Italic-webfont.eot ├── OpenSans-Italic-webfont.svg ├── OpenSans-Italic-webfont.ttf ├── OpenSans-Italic-webfont.woff ├── OpenSans-Light-webfont.eot ├── OpenSans-Light-webfont.svg ├── OpenSans-Light-webfont.ttf ├── OpenSans-Light-webfont.woff ├── OpenSans-LightItalic-webfont.eot ├── OpenSans-LightItalic-webfont.svg ├── OpenSans-LightItalic-webfont.ttf ├── OpenSans-LightItalic-webfont.woff ├── OpenSans-Regular-webfont.eot ├── OpenSans-Regular-webfont.svg ├── OpenSans-Regular-webfont.ttf ├── OpenSans-Regular-webfont.woff ├── OpenSans-Semibold-webfont.eot ├── OpenSans-Semibold-webfont.svg ├── OpenSans-Semibold-webfont.ttf ├── OpenSans-Semibold-webfont.woff ├── OpenSans-SemiboldItalic-webfont.eot ├── OpenSans-SemiboldItalic-webfont.svg ├── OpenSans-SemiboldItalic-webfont.ttf └── OpenSans-SemiboldItalic-webfont.woff ├── images ├── bullet.png ├── hr.gif └── nav-bg.gif ├── index.html ├── javascripts └── respond.js ├── params.json ├── pom.xml ├── src ├── assembly │ └── zip.xml ├── main │ ├── java │ │ └── org │ │ │ └── elasticsearch │ │ │ ├── action │ │ │ └── view │ │ │ │ ├── TransportViewAction.java │ │ │ │ ├── ViewAction.java │ │ │ │ ├── ViewRequest.java │ │ │ │ ├── ViewRequestBuilder.java │ │ │ │ └── ViewResponse.java │ │ │ ├── plugin │ │ │ └── view │ │ │ │ └── ViewPlugin.java │ │ │ ├── rest │ │ │ └── action │ │ │ │ └── view │ │ │ │ └── RestViewAction.java │ │ │ └── view │ │ │ ├── ViewContext.java │ │ │ ├── ViewEngineService.java │ │ │ ├── ViewModule.java │ │ │ ├── ViewResult.java │ │ │ ├── ViewService.java │ │ │ ├── binary │ │ │ └── BinaryViewEngineService.java │ │ │ ├── exception │ │ │ └── ElasticSearchViewNotFoundException.java │ │ │ └── mvel │ │ │ └── MvelViewEngineService.java │ └── resources │ │ └── es-plugin.properties └── test │ ├── java │ └── org │ │ └── elasticsearch │ │ └── test │ │ └── integration │ │ └── views │ │ └── ViewActionTests.java │ └── resources │ ├── org │ └── elasticsearch │ │ └── test │ │ └── integration │ │ └── views │ │ ├── config │ │ └── views │ │ │ ├── copyright.mv │ │ │ ├── list-of-products-with-brands.html │ │ │ ├── list-of-products-with-brands.html~ │ │ │ ├── list-of-products-with-brands.mv │ │ │ ├── list-of-products.html │ │ │ ├── list-of-products.html~ │ │ │ └── list-of-products.mv │ │ ├── data │ │ ├── brands.json │ │ └── products.json │ │ └── mappings │ │ ├── brand.json │ │ └── product.json │ └── test.sh └── stylesheets ├── ie.css ├── normalize.css ├── pygment_trac.css └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | elasticsearch-view-plugin.iml 4 | *.class 5 | .classpath 6 | .project 7 | .settings/ 8 | elasticsearch-view-plugin.iml 9 | 10 | 11 | # Package Files # 12 | *.jar 13 | *.war 14 | *.ear 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introducing the ElasticSearch View Plugin 2 | 3 | The ElasticSearch View Plugin provides a simple way to render ElasticSearch documents in HTML, XML or text. This plugin can also be used to generate web pages that show a list of documents based on oredefined queries. 4 | 5 | Full documentation available here: [http://tlrx.github.com/elasticsearch-view-plugin/](http://tlrx.github.com/elasticsearch-view-plugin/) 6 | 7 | 8 | -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Bold-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-BoldItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Italic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Copyright : Digitized data copyright 20102011 Google Corporation 7 | Foundry : Ascender Corporation 8 | Foundry URL : httpwwwascendercorpcom 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 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Light-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-LightItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Regular-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/images/bullet.png -------------------------------------------------------------------------------- /images/hr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/images/hr.gif -------------------------------------------------------------------------------- /images/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlrx/elasticsearch-view-plugin/1b92409d30886659f3aa1792842b1898784fd4d6/images/nav-bg.gif -------------------------------------------------------------------------------- /javascripts/respond.js: -------------------------------------------------------------------------------- 1 | if(typeof Object.create!=="function"){ 2 | Object.create=function(o){ 3 | function F(){ 4 | }; 5 | F.prototype=o; 6 | return new F(); 7 | }; 8 | } 9 | var ua={toString:function(){ 10 | return navigator.userAgent; 11 | },test:function(s){ 12 | return this.toString().toLowerCase().indexOf(s.toLowerCase())>-1; 13 | }}; 14 | ua.version=(ua.toString().toLowerCase().match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1]; 15 | ua.webkit=ua.test("webkit"); 16 | ua.gecko=ua.test("gecko")&&!ua.webkit; 17 | ua.opera=ua.test("opera"); 18 | ua.ie=ua.test("msie")&&!ua.opera; 19 | ua.ie6=ua.ie&&document.compatMode&&typeof document.documentElement.style.maxHeight==="undefined"; 20 | ua.ie7=ua.ie&&document.documentElement&&typeof document.documentElement.style.maxHeight!=="undefined"&&typeof XDomainRequest==="undefined"; 21 | ua.ie8=ua.ie&&typeof XDomainRequest!=="undefined"; 22 | var domReady=function(){ 23 | var _1=[]; 24 | var _2=function(){ 25 | if(!arguments.callee.done){ 26 | arguments.callee.done=true; 27 | for(var i=0;i<_1.length;i++){ 28 | _1[i](); 29 | } 30 | } 31 | }; 32 | if(document.addEventListener){ 33 | document.addEventListener("DOMContentLoaded",_2,false); 34 | } 35 | if(ua.ie){ 36 | (function(){ 37 | try{ 38 | document.documentElement.doScroll("left"); 39 | } 40 | catch(e){ 41 | setTimeout(arguments.callee,50); 42 | return; 43 | } 44 | _2(); 45 | })(); 46 | document.onreadystatechange=function(){ 47 | if(document.readyState==="complete"){ 48 | document.onreadystatechange=null; 49 | _2(); 50 | } 51 | }; 52 | } 53 | if(ua.webkit&&document.readyState){ 54 | (function(){ 55 | if(document.readyState!=="loading"){ 56 | _2(); 57 | }else{ 58 | setTimeout(arguments.callee,10); 59 | } 60 | })(); 61 | } 62 | window.onload=_2; 63 | return function(fn){ 64 | if(typeof fn==="function"){ 65 | _1[_1.length]=fn; 66 | } 67 | return fn; 68 | }; 69 | }(); 70 | var cssHelper=function(){ 71 | var _3={BLOCKS:/[^\s{][^{]*\{(?:[^{}]*\{[^{}]*\}[^{}]*|[^{}]*)*\}/g,BLOCKS_INSIDE:/[^\s{][^{]*\{[^{}]*\}/g,DECLARATIONS:/[a-zA-Z\-]+[^;]*:[^;]+;/g,RELATIVE_URLS:/url\(['"]?([^\/\)'"][^:\)'"]+)['"]?\)/g,REDUNDANT_COMPONENTS:/(?:\/\*([^*\\\\]|\*(?!\/))+\*\/|@import[^;]+;)/g,REDUNDANT_WHITESPACE:/\s*(,|:|;|\{|\})\s*/g,MORE_WHITESPACE:/\s{2,}/g,FINAL_SEMICOLONS:/;\}/g,NOT_WHITESPACE:/\S+/g}; 72 | var _4,_5=false; 73 | var _6=[]; 74 | var _7=function(fn){ 75 | if(typeof fn==="function"){ 76 | _6[_6.length]=fn; 77 | } 78 | }; 79 | var _8=function(){ 80 | for(var i=0;i<_6.length;i++){ 81 | _6[i](_4); 82 | } 83 | }; 84 | var _9={}; 85 | var _a=function(n,v){ 86 | if(_9[n]){ 87 | var _b=_9[n].listeners; 88 | if(_b){ 89 | for(var i=0;i<_b.length;i++){ 90 | _b[i](v); 91 | } 92 | } 93 | } 94 | }; 95 | var _c=function(_d,_e,_f){ 96 | if(ua.ie&&!window.XMLHttpRequest){ 97 | window.XMLHttpRequest=function(){ 98 | return new ActiveXObject("Microsoft.XMLHTTP"); 99 | }; 100 | } 101 | if(!XMLHttpRequest){ 102 | return ""; 103 | } 104 | var r=new XMLHttpRequest(); 105 | try{ 106 | r.open("get",_d,true); 107 | r.setRequestHeader("X_REQUESTED_WITH","XMLHttpRequest"); 108 | } 109 | catch(e){ 110 | _f(); 111 | return; 112 | } 113 | var _10=false; 114 | setTimeout(function(){ 115 | _10=true; 116 | },5000); 117 | document.documentElement.style.cursor="progress"; 118 | r.onreadystatechange=function(){ 119 | if(r.readyState===4&&!_10){ 120 | if(!r.status&&location.protocol==="file:"||(r.status>=200&&r.status<300)||r.status===304||navigator.userAgent.indexOf("Safari")>-1&&typeof r.status==="undefined"){ 121 | _e(r.responseText); 122 | }else{ 123 | _f(); 124 | } 125 | document.documentElement.style.cursor=""; 126 | r=null; 127 | } 128 | }; 129 | r.send(""); 130 | }; 131 | var _11=function(_12){ 132 | _12=_12.replace(_3.REDUNDANT_COMPONENTS,""); 133 | _12=_12.replace(_3.REDUNDANT_WHITESPACE,"$1"); 134 | _12=_12.replace(_3.MORE_WHITESPACE," "); 135 | _12=_12.replace(_3.FINAL_SEMICOLONS,"}"); 136 | return _12; 137 | }; 138 | var _13={mediaQueryList:function(s){ 139 | var o={}; 140 | var idx=s.indexOf("{"); 141 | var lt=s.substring(0,idx); 142 | s=s.substring(idx+1,s.length-1); 143 | var mqs=[],rs=[]; 144 | var qts=lt.toLowerCase().substring(7).split(","); 145 | for(var i=0;i-1&&_23.href&&_23.href.length!==0&&!_23.disabled){ 315 | _1f[_1f.length]=_23; 316 | } 317 | } 318 | if(_1f.length>0){ 319 | var c=0; 320 | var _24=function(){ 321 | c++; 322 | if(c===_1f.length){ 323 | _20(); 324 | } 325 | }; 326 | var _25=function(_26){ 327 | var _27=_26.href; 328 | _c(_27,function(_28){ 329 | _28=_11(_28).replace(_3.RELATIVE_URLS,"url("+_27.substring(0,_27.lastIndexOf("/"))+"/$1)"); 330 | _26.cssHelperText=_28; 331 | _24(); 332 | },_24); 333 | }; 334 | for(i=0;i<_1f.length;i++){ 335 | _25(_1f[i]); 336 | } 337 | }else{ 338 | _20(); 339 | } 340 | }; 341 | var _29={mediaQueryLists:"array",rules:"array",selectors:"object",declarations:"array",properties:"object"}; 342 | var _2a={mediaQueryLists:null,rules:null,selectors:null,declarations:null,properties:null}; 343 | var _2b=function(_2c,v){ 344 | if(_2a[_2c]!==null){ 345 | if(_29[_2c]==="array"){ 346 | return (_2a[_2c]=_2a[_2c].concat(v)); 347 | }else{ 348 | var c=_2a[_2c]; 349 | for(var n in v){ 350 | if(v.hasOwnProperty(n)){ 351 | if(!c[n]){ 352 | c[n]=v[n]; 353 | }else{ 354 | c[n]=c[n].concat(v[n]); 355 | } 356 | } 357 | } 358 | return c; 359 | } 360 | } 361 | }; 362 | var _2d=function(_2e){ 363 | _2a[_2e]=(_29[_2e]==="array")?[]:{}; 364 | for(var i=0;i<_4.length;i++){ 365 | _2b(_2e,_4[i].cssHelperParsed[_2e]); 366 | } 367 | return _2a[_2e]; 368 | }; 369 | domReady(function(){ 370 | var els=document.body.getElementsByTagName("*"); 371 | for(var i=0;i=_44)||(max&&_46<_44)||(!min&&!max&&_46===_44)); 554 | }else{ 555 | return false; 556 | } 557 | }else{ 558 | return _46>0; 559 | } 560 | }else{ 561 | if("device-height"===_41.substring(l-13,l)){ 562 | _47=screen.height; 563 | if(_42!==null){ 564 | if(_43==="length"){ 565 | return ((min&&_47>=_44)||(max&&_47<_44)||(!min&&!max&&_47===_44)); 566 | }else{ 567 | return false; 568 | } 569 | }else{ 570 | return _47>0; 571 | } 572 | }else{ 573 | if("width"===_41.substring(l-5,l)){ 574 | _46=document.documentElement.clientWidth||document.body.clientWidth; 575 | if(_42!==null){ 576 | if(_43==="length"){ 577 | return ((min&&_46>=_44)||(max&&_46<_44)||(!min&&!max&&_46===_44)); 578 | }else{ 579 | return false; 580 | } 581 | }else{ 582 | return _46>0; 583 | } 584 | }else{ 585 | if("height"===_41.substring(l-6,l)){ 586 | _47=document.documentElement.clientHeight||document.body.clientHeight; 587 | if(_42!==null){ 588 | if(_43==="length"){ 589 | return ((min&&_47>=_44)||(max&&_47<_44)||(!min&&!max&&_47===_44)); 590 | }else{ 591 | return false; 592 | } 593 | }else{ 594 | return _47>0; 595 | } 596 | }else{ 597 | if("device-aspect-ratio"===_41.substring(l-19,l)){ 598 | return _43==="aspect-ratio"&&screen.width*_44[1]===screen.height*_44[0]; 599 | }else{ 600 | if("color-index"===_41.substring(l-11,l)){ 601 | var _48=Math.pow(2,screen.colorDepth); 602 | if(_42!==null){ 603 | if(_43==="absolute"){ 604 | return ((min&&_48>=_44)||(max&&_48<_44)||(!min&&!max&&_48===_44)); 605 | }else{ 606 | return false; 607 | } 608 | }else{ 609 | return _48>0; 610 | } 611 | }else{ 612 | if("color"===_41.substring(l-5,l)){ 613 | var _49=screen.colorDepth; 614 | if(_42!==null){ 615 | if(_43==="absolute"){ 616 | return ((min&&_49>=_44)||(max&&_49<_44)||(!min&&!max&&_49===_44)); 617 | }else{ 618 | return false; 619 | } 620 | }else{ 621 | return _49>0; 622 | } 623 | }else{ 624 | if("resolution"===_41.substring(l-10,l)){ 625 | var res; 626 | if(_45==="dpcm"){ 627 | res=_3d("1cm"); 628 | }else{ 629 | res=_3d("1in"); 630 | } 631 | if(_42!==null){ 632 | if(_43==="resolution"){ 633 | return ((min&&res>=_44)||(max&&res<_44)||(!min&&!max&&res===_44)); 634 | }else{ 635 | return false; 636 | } 637 | }else{ 638 | return res>0; 639 | } 640 | }else{ 641 | return false; 642 | } 643 | } 644 | } 645 | } 646 | } 647 | } 648 | } 649 | } 650 | }; 651 | var _4a=function(mq){ 652 | var _4b=mq.getValid(); 653 | var _4c=mq.getExpressions(); 654 | var l=_4c.length; 655 | if(l>0){ 656 | for(var i=0;i0){ 675 | s[c++]=","; 676 | } 677 | s[c++]=n; 678 | } 679 | } 680 | if(s.length>0){ 681 | _39[_39.length]=cssHelper.addStyle("@media "+s.join("")+"{"+mql.getCssText()+"}",false); 682 | } 683 | }; 684 | var _4e=function(_4f){ 685 | for(var i=0;i<_4f.length;i++){ 686 | _4d(_4f[i]); 687 | } 688 | if(ua.ie){ 689 | document.documentElement.style.display="block"; 690 | setTimeout(function(){ 691 | document.documentElement.style.display=""; 692 | },0); 693 | setTimeout(function(){ 694 | cssHelper.broadcast("cssMediaQueriesTested"); 695 | },100); 696 | }else{ 697 | cssHelper.broadcast("cssMediaQueriesTested"); 698 | } 699 | }; 700 | var _50=function(){ 701 | for(var i=0;i<_39.length;i++){ 702 | cssHelper.removeStyle(_39[i]); 703 | } 704 | _39=[]; 705 | cssHelper.mediaQueryLists(_4e); 706 | }; 707 | var _51=0; 708 | var _52=function(){ 709 | var _53=cssHelper.getViewportWidth(); 710 | var _54=cssHelper.getViewportHeight(); 711 | if(ua.ie){ 712 | var el=document.createElement("div"); 713 | el.style.position="absolute"; 714 | el.style.top="-9999em"; 715 | el.style.overflow="scroll"; 716 | document.body.appendChild(el); 717 | _51=el.offsetWidth-el.clientWidth; 718 | document.body.removeChild(el); 719 | } 720 | var _55; 721 | var _56=function(){ 722 | var vpw=cssHelper.getViewportWidth(); 723 | var vph=cssHelper.getViewportHeight(); 724 | if(Math.abs(vpw-_53)>_51||Math.abs(vph-_54)>_51){ 725 | _53=vpw; 726 | _54=vph; 727 | clearTimeout(_55); 728 | _55=setTimeout(function(){ 729 | if(!_3a()){ 730 | _50(); 731 | }else{ 732 | cssHelper.broadcast("cssMediaQueriesTested"); 733 | } 734 | },500); 735 | } 736 | }; 737 | window.onresize=function(){ 738 | var x=window.onresize||function(){ 739 | }; 740 | return function(){ 741 | x(); 742 | _56(); 743 | }; 744 | }(); 745 | }; 746 | var _57=document.documentElement; 747 | _57.style.marginLeft="-32767px"; 748 | setTimeout(function(){ 749 | _57.style.marginTop=""; 750 | },20000); 751 | return function(){ 752 | if(!_3a()){ 753 | cssHelper.addListener("newStyleParsed",function(el){ 754 | _4e(el.cssHelperParsed.mediaQueryLists); 755 | }); 756 | cssHelper.addListener("cssMediaQueriesTested",function(){ 757 | if(ua.ie){ 758 | _57.style.width="1px"; 759 | } 760 | setTimeout(function(){ 761 | _57.style.width=""; 762 | _57.style.marginLeft=""; 763 | },0); 764 | cssHelper.removeListener("cssMediaQueriesTested",arguments.callee); 765 | }); 766 | _3c(); 767 | _50(); 768 | }else{ 769 | _57.style.marginLeft=""; 770 | } 771 | _52(); 772 | }; 773 | }()); 774 | try{ 775 | document.execCommand("BackgroundImageCache",false,true); 776 | } 777 | catch(e){ 778 | } 779 | 780 | -------------------------------------------------------------------------------- /params.json: -------------------------------------------------------------------------------- 1 | {"tagline":"","note":"Don't delete this file! It's used internally to help with page regeneration.","name":"Elasticsearch-view-plugin","google":"","body":"# Introducing the ElasticSearch View Plugin\r\n\r\nThe ElasticSearch View Plugin provides a simple way to render ElasticSearch documents in HTML, XML or text. This plugin can also be used to generate web pages that show a list of documents based on oredefined queries.\r\n\r\nElasticsearch provides a fast and simple way to retrieve a document with the [GET API](http://www.elasticsearch.org/guide/reference/api/get.html):\r\n```\r\ncurl -XGET 'http://localhost:9200/catalog/product/1'\r\n```\r\n\r\nUntil now, this API only allows to get the document in JSON format:\r\n```\r\n{\r\n \"_index\": \"catalog\",\r\n \"_type\": \"product\",\r\n \"_id\": \"1\",\r\n \"_version\": 1,\r\n \"exists\": true,\r\n \"_source\": {\r\n \"name\": \"1969 Harley Davidson Ultimate Chopper\",\r\n \"type\": \"Motorcycles\",\r\n \"brand\": \"Harley Davidson\",\r\n ...\r\n }\r\n}\r\n```\r\n\r\nAlthough this format is really useful, it is not directly presentable to a final user. The JSON format is more dedicated\r\nto be used by third party applications located at client or server side. These applications are in charge of\r\nparsing the JSON content, extracting meaningful data and rendering them in a more graphical way, for instance within a\r\nHTML page. With ElasticSearch, anyone who wants to have a graphical rendering of these documents shall install, configure\r\nand maintain such an application, which can become quite complex and require regular redelivery every time the graphic\r\ncontent of a document is modified\r\n\r\nThe ElasticSearch View Plugin can be used when you don't want to develop a dedicated application or when you wish to\r\ncontrol not only the document searches but also the way the document are displayed.\r\n\r\nThis plugin allows to create views using different templating engines (for now [MVEL](http://mvel.codehaus.org/MVEL+2.0+Basic+Templating)\r\nand [Mustache](http://mustache.github.com/) can be used) in order to generate a HTML (or XML, or anything\r\nwhich is text) display of your document and access to it threw an URL.\r\n\r\nFor example, the plugin can be used to generate a HTML page that displays our product:\r\nhttp://localhost:9200/_view/catalog/product/1\r\n\r\n![HTML view of document #1](tlrx.github.com/elasticsearch-view-plugin/samples/render_html.png)\r\n\r\nThe plugin can also be used to create several formats of views for a same type of document, if necessary with the help of\r\npredefined scripts. It can also be used to generate a specific view to show the results of predefined search queries:\r\n\r\nhttp://localhost:9200/_view/web/pages/home\r\n![HTML view of document #1](https://raw.github.com/tlrx/elasticsearch-view-plugin/gh-pages/samples/render_html_list_brand.png)\r\n\r\nIn this article, we explain how to install and configure the ElasticSearch View Plugin in order to generate HTML and XML\r\nviews of documents indexed in ElasticSearch.\r\n\r\n\r\n## Installing the plugin\r\n\r\nThe plugin can be installed as any other ElasticSearch's plugins:\r\n\r\n```\r\nbin/plugin -install tlrx/elasticsearch-view-plugin/0.0.1\r\n\r\n```\r\n\r\nThe current version of the plugin is compatible with [ElasticSearch 0.20.1](http://www.elasticsearch.org/download/2012/12/07/0.20.1.html).\r\n\r\n\r\n## Creating views for existing documents\r\n\r\nLet's imagine that we have a `catalog` index and few documents of `product` type:\r\n```\r\ncurl -XPUT 'http://localhost:9200/catalog/product/1' -d '\r\n{\r\n \"name\": \"1969 Harley Davidson Ultimate Chopper\",\r\n \"type\": \"Motorcycles\",\r\n \"brand\": \"Harley Davidson\",\r\n \"code\": \"S10_1678\",\r\n \"since\": \"1969\",\r\n \"price\": 48.34,\r\n \"description\": \"This replica features working kickstand, front suspension, gear-shift lever, footbrake lever, drive chain, wheels and steering.\",\r\n \"scale\": \"10\"\r\n}'\r\n\r\n```\r\n\r\nElasticSearch View Plugin uses the mapping's [meta data](http://www.elasticsearch.org/guide/reference/mapping/meta.html)\r\nto store all the views that are associated with a specific document type. Each view has a unique name, a scripting language,\r\na content and eventually a content type.\r\n\r\nBe careful, as the [Update API](http://www.elasticsearch.org/guide/reference/api/update.html), the `_source` field need\r\nto be enabled for this feature to work.\r\n\r\nFirst, we can create a basic view using the [MVEL templating](http://mvel.codehaus.org/MVEL+2.0+Basic+Templating) language:\r\n```\r\ncurl -XPUT 'http://localhost:9200/catalog/product/_mapping' -d '\r\n{\r\n \"product\": {\r\n \"_meta\": {\r\n \"views\": {\r\n \"default\": {\r\n \"view_lang\": \"mvel\",\r\n \"view\": \"Rendering the document #@{_id} in version @{_version} of type @{_type} from index @{_index}\"\r\n }\r\n }\r\n }\r\n }\r\n}'\r\n```\r\n\r\nThe previous command creates a view called `default`. The property `view_lang` can be used to specify the templating\r\nengine to use (default is `mvel`) whereas the `view` property holds the content of the view. When needed, a specific `content_type`\r\ncan be set. Note that the view named `default` will be used by default to render the documents of type `product`.\r\n\r\nIn MVEL, the coordinates of the document are available with `@{_id}`, `@{_type}` and `@{_index}` instructions. The original\r\n`_source` of the document can be accessed with `@{_source._x_}` where _x_ is a document property name.\r\n\r\nNow the view is created, opening the URL `http://localhost:9200/_view/catalog/product/1` in a web browser will trigger\r\nthe rendering of document with id 1. The result looks like:\r\n\r\n![Default view for product #1](https://raw.github.com/tlrx/elasticsearch-view-plugin/gh-pages/samples/render_default.png)\r\n\r\nSimple, no?\r\n\r\n\r\n### Using multiple views\r\n\r\nIn most use cases, a unique view is not sufficient. That's why the plugins allows to define many views for the same type of document,\r\n allowing differents renderings of the same document:\r\n\r\n```\r\ncurl -XPUT 'http://localhost:9200/catalog/product/_mapping' -d '\r\n{\r\n \"product\": {\r\n \"_meta\": {\r\n \"views\": {\r\n \"default\": {\r\n \"view_lang\": \"mvel\",\r\n \"view\": \"Rendering the document #@{_id} in version @{_version} of type @{_type} from index @{_index}\"\r\n },\r\n \"xml\": {\r\n \"view_lang\": \"mvel\",\r\n \"content_type\": \"text/xml\",\r\n \"view\": \"@{_source.name}@{_source.brand}\"\r\n }\r\n }\r\n }\r\n }\r\n}'\r\n```\r\n\r\n\r\nThis way the URL `http://localhost:9200/_view/catalog/product/1/xml` can be used to access to the XML view of document 1:\r\n\r\n![XML view for product #1](https://raw.github.com/tlrx/elasticsearch-view-plugin/gh-pages/samples/render_xml.png)\r\n\r\n\r\n### Rendering binary fields\r\n\r\nIf the document contains a [binary field](http://www.elasticsearch.org/guide/reference/mapping/core-types.html), the `binary`\r\nview language can be used to get an octet stream corresponding to the field value.\r\n\r\nTo illustrate that, we can add a new picture field to document 1 (the full JSON content is available on [gist](https://gist.github.com/4337853)):\r\n```\r\ncurl -XPUT 'http://localhost:9200/catalog/product/1' -d '\r\n{\r\n \"name\": \"1969 Harley Davidson Ultimate Chopper\",\r\n \"type\": \"Motorcycles\",\r\n \"brand\": \"Harley Davidson\",\r\n \"code\": \"S10_1678\",\r\n \"since\": \"1969\",\r\n \"price\": 48.34,\r\n \"description\": \"This replica features working kickstand, front suspension, gear-shift lever, footbrake lever, drive chain, wheels and steering.\",\r\n \"scale\": \"10\",\r\n \"picture\": \"/9j/4AAQSkZJRgABAQAAAQABAAD//gA7...\"\r\n}'\r\n\r\n```\r\n\r\nThe picture field contains a base64 encoded image of the Harley Davidson's logo.\r\n\r\nWe can now define two more views:\r\n* **logo**: which renders the picture as binary content\r\n* **full**: which renders the document as HTML block\r\n\r\n```\r\ncurl -XPUT 'http://localhost:9200/catalog/product/_mapping' -d '\r\n{\r\n \"product\": {\r\n \"_meta\": {\r\n \"views\": {\r\n \"default\": {\r\n \"view_lang\": \"mvel\",\r\n \"view\": \"Rendering the document #@{_id} in version @{_version} of type @{_type} from index @{_index}\"\r\n },\r\n \"xml\": {\r\n \"view_lang\": \"mvel\",\r\n \"content_type\": \"text/xml\",\r\n \"view\": \"@{_source.name}@{_source.brand}\"\r\n },\r\n \"logo\": {\r\n \"view_lang\": \"binary\",\r\n \"view\": \"_source.picture\"\r\n },\r\n \"full\": {\r\n \"view_lang\": \"mvel\",\r\n \"view\": \"

Detail of @{_source.name.toUpperCase()}

Year: @{_source.since}, price: @{_source.price}€

@{_source.description}

© Copyright @{_source.brand}

\"\r\n }\r\n }\r\n }\r\n }\r\n}'\r\n```\r\n\r\nThe URL `http://localhost:9200/_view/catalog/product/1/logo` can be used to get the picture of the product, whereas\r\n `http://localhost:9200/_view/catalog/product/1/full` renders the full HTML view:\r\n\r\n![HTML view of document #1](https://raw.github.com/tlrx/elasticsearch-view-plugin/gh-pages/samples/render_html.png)\r\n\r\n\r\n## Using preloaded templates\r\n\r\nSimilar to the [scripting module](http://www.elasticsearch.org/guide/reference/modules/scripting.html), the ElasticSearch\r\nView Plugin supports predefined templates scripts.\r\n\r\nThe scripts must be placed under the `config/views` directory and then referencing them by the script name. The way to\r\nreference a script differs according to the view language.\r\n\r\nFor example, we can create the file `config/views/copyright.mv` with the following content:\r\n```\r\n

© Copyright @{_source.brand}

\r\n```\r\n\r\nThe `.mv` extension indicates that the file contains a template written in MVEL.\r\n\r\nAfter a cluster restart, we will be able to update the `full` view in order to use the preloaded template script (note the\r\n@includeNamed{} instruction):\r\n```\r\n...\r\n \"full\": {\r\n \"view_lang\": \"mvel\",\r\n \"view\": \"

Detail of @{_source.name.toUpperCase()}

Year: @{_source.since}, price: @{_source.price}€

@{_source.description}

@includeNamed{\\\"copyright\\\"}
\"\r\n }\r\n...\r\n ```\r\n\r\nPreloaded templates are great candidates for code o text that are used in mulitple views.\r\n\r\n## Creating complete views from queries\r\n\r\nThe plugin allows to create custom views from query hits. Everytime such a view is requested, a set of predefined queries\r\nare executed and the results are used to create the view. Such views are stored in ElasticSearch as standard documents.\r\n\r\nThis kind of view is really powerful and are a simple way to create complete web pages.\r\n\r\nFirst, let's create a more complex template called `list-of-products` and stored in the file `config/views/list-of-products.mv`:\r\n```html\r\n\r\n\r\n \r\n \r\n @{title}\r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n Catalog\r\n
\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n

List of products with scale 1:10

\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n @foreach{item : _queries.products_with_size_1_10}\r\n \r\n \r\n \r\n \r\n \r\n \r\n @end{}\r\n \r\n
CodeNameBrandYear
@{item._source.code}@{item._source.name}@{item._source.brand}@{item._source.since}
\r\n
\r\n \r\n\r\n```\r\n\r\nNext, we can create a view:\r\n```\r\ncurl -XPUT \"http://localhost:9200/catalog/list-of-products-by-size/1:10\" -d \"{\r\n \\\"views\\\":{\r\n \\\"default\\\":{\r\n \\\"view_lang\\\": \\\"mvel\\\",\r\n \\\"queries\\\": {\r\n \\\"products_with_size_1_10\\\": {\r\n \\\"indices\\\": \\\"catalog\\\",\r\n \\\"types\\\": [\\\"product\\\"],\r\n \\\"query\\\" : {\r\n \\\"constant_score\\\" : {\r\n \\\"filter\\\" : {\r\n \\\"term\\\" : { \\\"scale\\\" : \\\"10\\\"}\r\n }\r\n }\r\n }\r\n }\r\n },\r\n \\\"view\\\" : \\\"@includeNamed{'list-of-products'; title='List of products'}\\\"\r\n }\r\n }\r\n}\"\r\n```\r\n\r\nNote that the view is indexed in `catalog` index with the `list-of-products-by-size` document type and id `1:10`. It defines\r\na view called `default` (but could have another name) and uses the `list-of-products.mv` template to render a list\r\nof products.\r\n\r\nThe list of products is defined by the `products_with_size_1_10` query in the `queries` field of the view. This query\r\nselects 10 products that have a scale of 1:10.\r\n\r\nIf you look closely at the previous template, you can see the following code:\r\n```\r\n@foreach{item : _queries.products_with_size_1_10}\r\n \r\n @{item._source.code}\r\n @{item._source.name}\r\n @{item._source.brand}\r\n @{item._source.since}\r\n \r\n@end{}\r\n```\r\n\r\nThis code uses a MVEL templating syntax `@foreach{}...@end{}` that iterates over the hits provided by the\r\n`products_with_size_1_10` query in order to construct a dynamic table of products that will be rendered in the\r\nfinal HTML page. Of course, multiple queries can be used in the same view.\r\n\r\nThe result is available at `http://localhost:9200/_view/catalog/list-of-products-by-size/1:10` and looks like:\r\n\r\n![List of products with scale 1:10](https://raw.github.com/tlrx/elasticsearch-view-plugin/gh-pages/samples/render_html_list.png)\r\n\r\n\r\n## Going further...\r\n\r\n### Using Mustache\r\n\r\nThe plugin [elasticsearch-view-mustache-plugin](https://github.com/tlrx/elasticsearch-view-mustache-plugin) adds\r\n[Mustache](http://mustache.github.com/) as templating language for views.\r\n\r\nMustache is a great templating engine that supports template encapsulation. To defined views with Mustache template engine,\r\nuse `\"view_lang\": \"mustache\"`.\r\n\r\nSome sample usage of this plugin can be found in the Github project.\r\n\r\n### Rewriting URLs with Apache2\r\n\r\nApache2 server with mod_proxy and mod_rewrite can be used to redirect ElasticSearch Views Plugin URLs to better looking URLs.\r\n\r\nThe goal is to have nicer URLs like `http://www.domain.com/catalog/list-of-products-by-size/1:10` that points\r\nto internal `http://localhost:9200/_view/catalog/list-of-products-by-size/1:10`.\r\n\r\nHere is a basic sample of such URL rewriting:\r\n```\r\nRewriteEngine on\r\nRewriteRule ^catalog/(.*)$ http://localhost:9200/_view/catalog/$1 [P,L]\r\n```\r\n\r\n\r\n\r\nWe hope that this plugin will be as useful for you as it is for us, and we welcome your feedback and comments about this new plugin.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"} -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.github.tlrx 5 | elasticsearch-view-plugin 6 | 0.0.3-SNAPSHOT 7 | jar 8 | 9 | elasticsearch-view-plugin 10 | http://maven.apache.org 11 | 12 | 13 | 14 | org.sonatype.oss 15 | oss-parent 16 | 7 17 | 18 | 19 | 20 | 0.20.5 21 | 2.1.3.Final 22 | UTF-8 23 | 24 | 25 | 26 | 27 | The Apache Software License, Version 2.0 28 | http://www.apache.org/licenses/LICENSE-2.0.txt 29 | 30 | 31 | 32 | 33 | 34 | tlrx 35 | Tanguy Leroux 36 | tlrx.dev@gmail.com 37 | 38 | 39 | 40 | 41 | 42 | org.elasticsearch 43 | elasticsearch 44 | ${elasticsearch.version} 45 | compile 46 | 47 | 48 | org.mvel 49 | mvel2 50 | ${mvel2.version} 51 | 52 | 53 | 54 | junit 55 | junit 56 | 4.10 57 | test 58 | 59 | 60 | com.github.tlrx 61 | elasticsearch-test 62 | 0.0.7 63 | test 64 | 65 | 66 | org.elasticsearch 67 | elasticsearch 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | git:git@github.com:tlrx/elasticsearch-view-plugin.git 76 | scm:git:git@github.com:tlrx/elasticsearch-view-plugin.git 77 | scm:git:git@github.com:tlrx/elasticsearch-view-plugin.git 78 | 79 | 80 | 81 | GitHub 82 | https://github.com/tlrx/elasticsearch-view-plugin/issues/ 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-compiler-plugin 90 | 2.3.2 91 | 92 | 1.6 93 | 1.6 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-javadoc-plugin 100 | 2.8 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-source-plugin 106 | 2.1.2 107 | 108 | 109 | attach-sources 110 | 111 | jar 112 | 113 | 114 | 115 | 116 | 117 | 118 | org.apache.maven.plugins 119 | maven-jar-plugin 120 | 2.3.2 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-scm-plugin 126 | 1.5 127 | 128 | 129 | 130 | maven-assembly-plugin 131 | 2.3 132 | 133 | ${project.artifactId}-${project.version} 134 | 135 | /src/assembly/zip.xml 136 | 137 | 138 | 139 | 140 | package 141 | 142 | single 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | sonatype-nexus-staging 154 | Nexus Staging Repository 155 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/assembly/zip.xml: -------------------------------------------------------------------------------- 1 | 2 | zip 3 | 4 | zip 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | org.mvel:mvel2 13 | 14 | 15 | 16 | 17 | 18 | 19 | target/${project.artifactId}-${project.version}.jar 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/view/TransportViewAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.view; 20 | 21 | import org.elasticsearch.ElasticSearchException; 22 | import org.elasticsearch.ElasticSearchIllegalArgumentException; 23 | import org.elasticsearch.ElasticSearchParseException; 24 | import org.elasticsearch.action.search.SearchRequest; 25 | import org.elasticsearch.action.search.SearchResponse; 26 | import org.elasticsearch.action.search.TransportSearchAction; 27 | import org.elasticsearch.action.support.single.shard.TransportShardSingleOperationAction; 28 | import org.elasticsearch.cluster.ClusterService; 29 | import org.elasticsearch.cluster.ClusterState; 30 | import org.elasticsearch.cluster.block.ClusterBlockException; 31 | import org.elasticsearch.cluster.block.ClusterBlockLevel; 32 | import org.elasticsearch.cluster.metadata.MappingMetaData; 33 | import org.elasticsearch.cluster.routing.ShardIterator; 34 | import org.elasticsearch.common.Strings; 35 | import org.elasticsearch.common.collect.ImmutableMap; 36 | import org.elasticsearch.common.inject.Inject; 37 | import org.elasticsearch.common.settings.Settings; 38 | import org.elasticsearch.index.get.GetResult; 39 | import org.elasticsearch.index.service.IndexService; 40 | import org.elasticsearch.index.shard.service.IndexShard; 41 | import org.elasticsearch.indices.IndicesService; 42 | import org.elasticsearch.search.builder.SearchSourceBuilder; 43 | import org.elasticsearch.search.sort.SortOrder; 44 | import org.elasticsearch.threadpool.ThreadPool; 45 | import org.elasticsearch.transport.TransportService; 46 | import org.elasticsearch.view.exception.ElasticSearchViewNotFoundException; 47 | import org.elasticsearch.view.ViewContext; 48 | import org.elasticsearch.view.ViewResult; 49 | import org.elasticsearch.view.ViewService; 50 | 51 | import java.io.IOException; 52 | import java.util.HashMap; 53 | import java.util.List; 54 | import java.util.Map; 55 | 56 | 57 | public class TransportViewAction extends TransportShardSingleOperationAction { 58 | 59 | private final ViewService viewService; 60 | private final IndicesService indicesService; 61 | private final TransportSearchAction searchAction; 62 | 63 | @Inject 64 | public TransportViewAction(Settings settings, ThreadPool threadPool, 65 | ClusterService clusterService, 66 | TransportService transportService, 67 | IndicesService indicesService, 68 | ViewService viewService, 69 | TransportSearchAction searchAction) { 70 | super(settings, threadPool, clusterService, transportService); 71 | this.indicesService = indicesService; 72 | this.viewService = viewService; 73 | this.searchAction = searchAction; 74 | } 75 | 76 | @Override 77 | protected String executor() { 78 | return ThreadPool.Names.GENERIC; 79 | } 80 | 81 | @Override 82 | protected ViewRequest newRequest() { 83 | return new ViewRequest(); 84 | } 85 | 86 | @Override 87 | protected ViewResponse newResponse() { 88 | return new ViewResponse(); 89 | } 90 | 91 | @Override 92 | protected String transportAction() { 93 | return ViewAction.NAME; 94 | } 95 | 96 | @Override 97 | protected ClusterBlockException checkGlobalBlock(ClusterState state, ViewRequest request) { 98 | return state.blocks().globalBlockedException(ClusterBlockLevel.READ); 99 | } 100 | 101 | @Override 102 | protected ClusterBlockException checkRequestBlock(ClusterState state, ViewRequest request) { 103 | return state.blocks().indexBlockedException(ClusterBlockLevel.READ, request.index()); 104 | } 105 | 106 | @Override 107 | protected ShardIterator shards(ClusterState state, ViewRequest request) { 108 | return clusterService.operationRouting() 109 | .getShards(clusterService.state(), request.index(), request.type(), request.id(), null, null); 110 | } 111 | 112 | @Override 113 | protected ViewResponse shardOperation(ViewRequest request, int shardId) throws ElasticSearchException { 114 | 115 | // Get the doc first 116 | IndexService indexService = indicesService.indexService(request.index()); 117 | IndexShard indexShard = indexService.shardSafe(shardId); 118 | GetResult getResult = indexShard.getService().get(request.type(), request.id(), null, false); 119 | 120 | if (!getResult.exists()) { 121 | throw new ElasticSearchIllegalArgumentException("Document not found, cannot render view"); 122 | } 123 | 124 | // Try to get a view stored at document level 125 | ViewContext viewContext = extract(getResult.sourceAsMap(), request.format()); 126 | 127 | if (viewContext == null) { 128 | // Then, get the view stored in the mapping _meta field 129 | MappingMetaData mappingMetaData = clusterService.state().metaData().index(request.index()).mapping(request.type()); 130 | if (mappingMetaData != null) { 131 | try { 132 | Map mapping = mappingMetaData.sourceAsMap(); 133 | viewContext = extract(mapping, request.format()); 134 | } catch (IOException e) { 135 | throw new ElasticSearchParseException("Failed to parse mapping content to map", e); 136 | } 137 | } 138 | } 139 | 140 | if (viewContext == null) { 141 | throw new ElasticSearchViewNotFoundException("No view [" + request.format() + "] found for document type [" + request.type() + "]"); 142 | } 143 | 144 | // Set some org.elasticsearch.test.integration.views.mappings.data required for view rendering 145 | viewContext.index(getResult.index()) 146 | .type(getResult.type()) 147 | .id(getResult.id()) 148 | .version(getResult.version()) 149 | .source(getResult.sourceAsMap()); 150 | 151 | // Ok, let's render it with a ViewEngineService 152 | ViewResult result = viewService.render(viewContext); 153 | 154 | return new ViewResponse(result.contentType(), result.content()); 155 | } 156 | 157 | private ViewContext extract(Map sourceAsMap, String format) { 158 | if (sourceAsMap != null) { 159 | for (String key : sourceAsMap.keySet()) { 160 | Object views = null; 161 | 162 | // When searching in a mapping 163 | if ("_meta".equals(key)) { 164 | Object meta = sourceAsMap.get(key); 165 | if (meta instanceof Map) { 166 | views = ((Map) meta).get("views"); 167 | } 168 | } 169 | 170 | // When searching in the document content 171 | if ("views".equals(key)) { 172 | views = sourceAsMap.get(key); 173 | } 174 | 175 | if ((views != null) && (views instanceof Map)) { 176 | Map mapViews = (Map) views; 177 | Object candidate = null; 178 | 179 | // Try to load a specific view 180 | if (format != null) { 181 | candidate = mapViews.get(format); 182 | } else if (!mapViews.isEmpty()) { 183 | // Try to load the "default" view 184 | Object defaultView = mapViews.get(ViewRequest.DEFAULT_VIEW); 185 | if (defaultView != null) { 186 | candidate = defaultView; 187 | } 188 | } 189 | if ((candidate != null) && (candidate instanceof Map)) { 190 | Map mapCandidate = (Map) candidate; 191 | 192 | // ViewContext holds the org.elasticsearch.test.integration.views.mappings.data for view rendering 193 | ViewContext viewContext = new ViewContext((String) mapCandidate.get("view_lang"), (String) mapCandidate.get("view"), (String) mapCandidate.get("content_type")); 194 | 195 | Object queries = mapCandidate.get("queries"); 196 | Map mapQueries = null; 197 | 198 | if (queries != null) { 199 | if (queries instanceof List) { 200 | List listQueries = (List) queries; 201 | mapQueries = new HashMap(listQueries.size()); 202 | for (Object query : listQueries) { 203 | if (query instanceof Map) { 204 | Map q = (Map) query; 205 | for (Object queryName : q.keySet()) { 206 | if (queryName instanceof String) { 207 | mapQueries.put((String) queryName, q.get(queryName)); 208 | } 209 | } 210 | } 211 | } 212 | } else if (queries instanceof Map) { 213 | mapQueries = (Map) queries; 214 | } 215 | } 216 | 217 | if (mapQueries != null) { 218 | for (String queryName : mapQueries.keySet()) { 219 | try { 220 | Map mapQuery = (Map) mapQueries.get(queryName); 221 | 222 | String[] indices = null; 223 | if (mapQuery.get("indices") instanceof List) { 224 | indices = (String[]) ((List) mapQuery.get("indices")).toArray(new String[0]); 225 | } else if (mapQuery.get("indices") instanceof String) { 226 | indices = new String[]{((String) mapQuery.get("indices"))}; 227 | } 228 | 229 | String[] types = null; 230 | if (mapQuery.get("types") instanceof List) { 231 | types = (String[]) ((List) mapQuery.get("types")).toArray(new String[0]); 232 | } else if (mapQuery.get("types") instanceof String) { 233 | types = new String[]{((String) mapQuery.get("types"))}; 234 | } 235 | 236 | SearchSourceBuilder searchSourceBuilder = null; 237 | if (mapQuery.get("sort") instanceof List) { 238 | if (searchSourceBuilder == null) { 239 | searchSourceBuilder = new SearchSourceBuilder(); 240 | } 241 | for (Object sort : (List) mapQuery.get("sort")) { 242 | if (sort instanceof String) { 243 | searchSourceBuilder.sort((String) sort); 244 | } else if (sort instanceof Map) { 245 | for (Object field : ((Map) sort).keySet()) { 246 | String sortField = (String) field; 247 | String reverse = (String) ((Map) sort).get(field); 248 | if ("asc".equals(reverse)) { 249 | searchSourceBuilder.sort(sortField, SortOrder.ASC); 250 | } else if ("desc".equals(reverse)) { 251 | searchSourceBuilder.sort(sortField, SortOrder.DESC); 252 | } 253 | } 254 | } 255 | } 256 | } 257 | 258 | if (mapQuery.get("fields") instanceof List) { 259 | if (searchSourceBuilder == null) { 260 | searchSourceBuilder = new SearchSourceBuilder(); 261 | } 262 | for (Object field : (List) mapQuery.get("fields")) { 263 | if (field instanceof String) { 264 | searchSourceBuilder.field((String) field); 265 | } 266 | } 267 | } 268 | 269 | SearchRequest searchRequest = new SearchRequest(); 270 | if (indices != null) { 271 | searchRequest.indices(indices); 272 | } 273 | if (types != null) { 274 | searchRequest.types(types); 275 | } 276 | 277 | ImmutableMap.Builder builder = ImmutableMap.builder(); 278 | builder.put("query", (Map) mapQuery.get("query")); 279 | searchRequest.source(builder.build()); 280 | 281 | if (searchSourceBuilder != null) { 282 | searchRequest.extraSource(searchSourceBuilder); 283 | } 284 | 285 | SearchResponse searchResponse = searchAction.execute(searchRequest).get(); 286 | viewContext.queriesAndHits(queryName, searchResponse.hits()); 287 | 288 | } catch (Exception e) { 289 | viewContext.queriesAndHits(queryName, null); 290 | } 291 | } 292 | } 293 | return viewContext; 294 | } 295 | } 296 | } 297 | } 298 | return null; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/view/ViewAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.view; 20 | 21 | import org.elasticsearch.action.Action; 22 | import org.elasticsearch.client.Client; 23 | 24 | public class ViewAction extends Action { 25 | 26 | public static final ViewAction INSTANCE = new ViewAction(); 27 | public static final String NAME = "view"; 28 | 29 | private ViewAction() { 30 | super(NAME); 31 | } 32 | 33 | @Override 34 | public ViewResponse newResponse() { 35 | return new ViewResponse(); 36 | } 37 | 38 | @Override 39 | public ViewRequestBuilder newRequestBuilder(Client client) { 40 | return new ViewRequestBuilder(client); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/view/ViewRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.view; 20 | 21 | import org.elasticsearch.action.support.single.shard.SingleShardOperationRequest; 22 | import org.elasticsearch.common.Nullable; 23 | import org.elasticsearch.common.Required; 24 | 25 | public class ViewRequest extends SingleShardOperationRequest { 26 | 27 | private String type; 28 | private String id; 29 | private String format; 30 | public static final String DEFAULT_VIEW = "default"; 31 | 32 | ViewRequest() { 33 | } 34 | 35 | /** 36 | * Constructs a new view request against the specified index with the type and id. 37 | * 38 | * @param index The index to get the document from 39 | * @param type The type of the document 40 | * @param id The id of the document 41 | */ 42 | public ViewRequest(String index, String type, String id) { 43 | super(index); 44 | this.type = type; 45 | this.id = id; 46 | } 47 | 48 | /** 49 | * Sets the type of the document to fetch. 50 | */ 51 | public ViewRequest type(@Nullable String type) { 52 | if (type == null) { 53 | type = "_all"; 54 | } 55 | this.type = type; 56 | return this; 57 | } 58 | 59 | /** 60 | * Sets the id of the document to fetch. 61 | */ 62 | @Required 63 | public ViewRequest id(String id) { 64 | this.id = id; 65 | return this; 66 | } 67 | 68 | /** 69 | * Sets the format of the document 70 | */ 71 | public ViewRequest format(String format) { 72 | this.format = format; 73 | return this; 74 | } 75 | 76 | public String type() { 77 | return type; 78 | } 79 | 80 | public String id() { 81 | return id; 82 | } 83 | 84 | public String format() { 85 | return format; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/view/ViewRequestBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.view; 20 | 21 | import org.elasticsearch.action.ActionListener; 22 | import org.elasticsearch.action.support.single.shard.SingleShardOperationRequestBuilder; 23 | import org.elasticsearch.client.Client; 24 | import org.elasticsearch.client.internal.InternalClient; 25 | 26 | public class ViewRequestBuilder extends SingleShardOperationRequestBuilder { 27 | 28 | public ViewRequestBuilder(Client client) { 29 | super((InternalClient) client, new ViewRequest()); 30 | } 31 | 32 | public ViewRequestBuilder(Client client, String index, String type, String id) { 33 | super((InternalClient) client, new ViewRequest(index, type, id)); 34 | } 35 | 36 | /** 37 | * Sets the type of the document to fetch. If set to null, will use just the id to fetch the 38 | * first document matching it. 39 | */ 40 | public ViewRequestBuilder setType(String type) { 41 | request.type(type); 42 | return this; 43 | } 44 | 45 | /** 46 | * Sets the id of the document to fetch. 47 | */ 48 | public ViewRequestBuilder setId(String id) { 49 | request.id(id); 50 | return this; 51 | } 52 | 53 | @Override 54 | protected void doExecute(ActionListener listener) { 55 | ((Client) client).execute(ViewAction.INSTANCE, request, listener); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/action/view/ViewResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.action.view; 20 | 21 | import org.elasticsearch.action.ActionResponse; 22 | 23 | public class ViewResponse extends ActionResponse { 24 | 25 | public byte[] content; 26 | public String contentType = ""; 27 | 28 | ViewResponse() { 29 | } 30 | 31 | public ViewResponse(String contentType, byte[] content) { 32 | this.content = content; 33 | this.contentType = contentType; 34 | } 35 | 36 | public byte[] content() { 37 | return this.content; 38 | } 39 | 40 | public String contentType() { 41 | return this.contentType; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/plugin/view/ViewPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.plugin.view; 20 | 21 | import org.elasticsearch.action.ActionModule; 22 | import org.elasticsearch.action.view.TransportViewAction; 23 | import org.elasticsearch.action.view.ViewAction; 24 | import org.elasticsearch.common.collect.Lists; 25 | import org.elasticsearch.common.inject.Module; 26 | import org.elasticsearch.plugins.AbstractPlugin; 27 | import org.elasticsearch.rest.RestModule; 28 | import org.elasticsearch.rest.action.view.RestViewAction; 29 | import org.elasticsearch.view.ViewModule; 30 | 31 | import java.util.Collection; 32 | import java.util.List; 33 | 34 | 35 | public class ViewPlugin extends AbstractPlugin { 36 | 37 | public String description() { 38 | return "Elasticsearch View Plugin"; 39 | } 40 | 41 | public String name() { 42 | return "view-plugin"; 43 | } 44 | 45 | @Override 46 | public void processModule(Module module) { 47 | if (module instanceof RestModule) { 48 | ((RestModule) module).addRestAction(RestViewAction.class); 49 | } 50 | if (module instanceof ActionModule) { 51 | ((ActionModule) module).registerAction(ViewAction.INSTANCE, TransportViewAction.class); 52 | } 53 | } 54 | 55 | @Override 56 | public Collection> modules() { 57 | List> modules = Lists.newArrayList(); 58 | modules.add(ViewModule.class); 59 | return modules; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/rest/action/view/RestViewAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.rest.action.view; 20 | 21 | import org.elasticsearch.action.ActionListener; 22 | import org.elasticsearch.action.view.ViewAction; 23 | import org.elasticsearch.action.view.ViewRequest; 24 | import org.elasticsearch.action.view.ViewResponse; 25 | import org.elasticsearch.client.Client; 26 | import org.elasticsearch.common.inject.Inject; 27 | import org.elasticsearch.common.settings.Settings; 28 | import org.elasticsearch.rest.*; 29 | import org.elasticsearch.view.exception.ElasticSearchViewNotFoundException; 30 | 31 | import java.io.IOException; 32 | 33 | import static org.elasticsearch.rest.RestRequest.Method.GET; 34 | import static org.elasticsearch.rest.RestStatus.NOT_FOUND; 35 | 36 | public class RestViewAction extends BaseRestHandler { 37 | 38 | @Inject 39 | public RestViewAction(Settings settings, Client client, RestController controller) { 40 | super(settings, client); 41 | controller.registerHandler(GET, "/_view/{index}/{type}/{id}", this); 42 | controller.registerHandler(GET, "/_view/{index}/{type}/{id}/{format}", this); 43 | } 44 | 45 | public void handleRequest(final RestRequest request, final RestChannel channel) { 46 | ViewRequest viewRequest = new ViewRequest(request.param("index"), request.param("type"), request.param("id")); 47 | if (request.hasParam("format")) { 48 | viewRequest.format(request.param("format")); 49 | } 50 | 51 | // we just send a response, no need to fork 52 | viewRequest.listenerThreaded(false); 53 | // we don't spawn, then fork if local 54 | viewRequest.operationThreaded(true); 55 | 56 | client.execute(ViewAction.INSTANCE, viewRequest, new ActionListener() { 57 | 58 | public void onResponse(ViewResponse response) { 59 | try { 60 | channel.sendResponse(new BytesRestResponse(response.content(), response.contentType())); 61 | } catch (Exception e) { 62 | onFailure(e); 63 | } 64 | } 65 | 66 | public void onFailure(Throwable e) { 67 | try { 68 | if (e instanceof ElasticSearchViewNotFoundException) { 69 | channel.sendResponse(new XContentThrowableRestResponse(request, NOT_FOUND, e)); 70 | } else { 71 | channel.sendResponse(new XContentThrowableRestResponse(request, e)); 72 | } 73 | } catch (IOException e1) { 74 | logger.error("Failed to send failure response", e1); 75 | } 76 | } 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/ViewContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view; 20 | 21 | import org.elasticsearch.common.collect.ImmutableMap; 22 | import org.elasticsearch.common.util.concurrent.ConcurrentCollections; 23 | import org.elasticsearch.index.get.GetField; 24 | import org.elasticsearch.search.SearchHit; 25 | import org.elasticsearch.search.SearchHitField; 26 | import org.elasticsearch.search.SearchHits; 27 | 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | public class ViewContext { 34 | 35 | private String lang; 36 | private String contentType; 37 | private String view; 38 | private String index; 39 | private String type; 40 | private String id; 41 | private Long version; 42 | private Map source; 43 | private Map fields; 44 | private Map queries; 45 | 46 | public ViewContext(String lang, String view, String contentType) { 47 | this.lang = lang; 48 | this.view = view; 49 | this.contentType = contentType; 50 | } 51 | 52 | public String lang() { 53 | return lang; 54 | } 55 | 56 | public String contentType() { 57 | return contentType; 58 | } 59 | 60 | public String view() { 61 | return view; 62 | } 63 | 64 | public Map queries() { 65 | return queries; 66 | } 67 | 68 | public ViewContext index(String index) { 69 | this.index = index; 70 | return this; 71 | } 72 | 73 | public ViewContext type(String type) { 74 | this.type = type; 75 | return this; 76 | } 77 | 78 | public ViewContext id(String id) { 79 | this.id = id; 80 | return this; 81 | } 82 | 83 | public ViewContext version(Long version) { 84 | this.version = version; 85 | return this; 86 | } 87 | 88 | public ViewContext source(Map source) { 89 | this.source = source; 90 | return this; 91 | } 92 | 93 | public ViewContext queriesAndHits(String queryName, SearchHits hits) { 94 | if (this.queries == null) { 95 | this.queries = ConcurrentCollections.newConcurrentMap(); 96 | } 97 | this.queries.put(queryName, hits); 98 | return this; 99 | } 100 | 101 | public Map varsAsMap() { 102 | Map builder = new HashMap(); 103 | if (this.index != null) { 104 | builder.put("_index", this.index); 105 | } 106 | if (this.type != null) { 107 | builder.put("_type", this.type); 108 | } 109 | if (this.id != null) { 110 | builder.put("_id", this.id); 111 | } 112 | if (this.version != null) { 113 | builder.put("_version", this.version.toString()); 114 | } 115 | if (this.source != null) { 116 | ImmutableMap.Builder sourceAsMap = ImmutableMap.builder(); 117 | sourceAsMap.putAll(this.source); 118 | builder.put("_source", sourceAsMap.build()); 119 | } 120 | 121 | if (this.queries() != null) { 122 | ImmutableMap.Builder queryHitsAsMap = ImmutableMap.builder(); 123 | for (String query : this.queries().keySet()) { 124 | 125 | SearchHits searchHits = this.queries().get(query); 126 | List> hits = new ArrayList>(searchHits.hits().length); 127 | for (SearchHit hit : searchHits.hits()) { 128 | ImmutableMap.Builder hitProperties = ImmutableMap.builder(); 129 | hitProperties.put("_index", hit.index()); 130 | hitProperties.put("_type", hit.type()); 131 | hitProperties.put("_id", hit.id()); 132 | hitProperties.put("_version", hit.version()); 133 | 134 | Map sourceAsMap = hit.sourceAsMap(); 135 | if(sourceAsMap != null){ 136 | hitProperties.put("_source", hit.sourceAsMap()); 137 | } 138 | 139 | Map fields = hit.fields(); 140 | if (fields != null) { 141 | Map fieldsMap = new HashMap(); 142 | for (SearchHitField field : hit.fields().values()) { 143 | fieldsMap.put(field.name(), field.value()); 144 | } 145 | hitProperties.put("fields", fieldsMap); 146 | } 147 | hits.add(hitProperties.build()); 148 | } 149 | 150 | queryHitsAsMap.put(query, hits); 151 | } 152 | builder.put("_queries", queryHitsAsMap.build()); 153 | } 154 | 155 | return builder; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/ViewEngineService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view; 20 | 21 | import org.elasticsearch.common.Nullable; 22 | 23 | import java.util.Map; 24 | 25 | public interface ViewEngineService { 26 | 27 | String[] types(); 28 | 29 | String[] extensions(); 30 | 31 | String contentType(); 32 | 33 | void load(String name, String view); 34 | 35 | byte[] render(String view, @Nullable Mapvars); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/ViewModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view; 20 | 21 | import org.elasticsearch.common.collect.Lists; 22 | import org.elasticsearch.common.inject.AbstractModule; 23 | import org.elasticsearch.common.inject.multibindings.Multibinder; 24 | import org.elasticsearch.view.binary.BinaryViewEngineService; 25 | import org.elasticsearch.view.mvel.MvelViewEngineService; 26 | 27 | import java.util.List; 28 | 29 | public class ViewModule extends AbstractModule { 30 | 31 | private final List> viewEngines = Lists.newArrayList(); 32 | 33 | public void addViewsEngine(Class viewEngine) { 34 | viewEngines.add(viewEngine); 35 | } 36 | 37 | @Override 38 | protected void configure() { 39 | Multibinder multibinder = Multibinder.newSetBinder(binder(), ViewEngineService.class); 40 | multibinder.addBinding().to(MvelViewEngineService.class); 41 | multibinder.addBinding().to(BinaryViewEngineService.class); 42 | for (Class viewEngine : viewEngines) { 43 | multibinder.addBinding().to(viewEngine); 44 | } 45 | 46 | bind(ViewService.class).asEagerSingleton(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/ViewResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view; 20 | 21 | public class ViewResult { 22 | 23 | private String contentType; 24 | 25 | private byte[] content; 26 | 27 | public ViewResult(String contentType, byte[] content) { 28 | this.contentType = contentType; 29 | this.content = content; 30 | } 31 | 32 | public byte[] content() { 33 | return content; 34 | } 35 | 36 | public String contentType() { 37 | return contentType; 38 | } 39 | 40 | public ViewResult content(byte[] content) { 41 | this.content = content; 42 | return this; 43 | } 44 | 45 | public ViewResult contentType(String contentType) { 46 | this.contentType = contentType; 47 | return this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/ViewService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view; 20 | 21 | import org.elasticsearch.common.collect.ImmutableMap; 22 | import org.elasticsearch.common.collect.ImmutableSet; 23 | import org.elasticsearch.common.component.AbstractComponent; 24 | import org.elasticsearch.common.inject.Inject; 25 | import org.elasticsearch.common.io.Streams; 26 | import org.elasticsearch.common.settings.Settings; 27 | import org.elasticsearch.common.util.concurrent.ConcurrentCollections; 28 | import org.elasticsearch.env.Environment; 29 | import org.elasticsearch.script.CompiledScript; 30 | import org.elasticsearch.view.binary.BinaryViewEngineService; 31 | import org.elasticsearch.view.mvel.MvelViewEngineService; 32 | 33 | import java.io.File; 34 | import java.io.FileInputStream; 35 | import java.io.InputStreamReader; 36 | import java.util.Set; 37 | import java.util.concurrent.ConcurrentMap; 38 | 39 | public class ViewService extends AbstractComponent { 40 | 41 | private final String defaultViewLang; 42 | 43 | private final ImmutableMap viewEngines; 44 | 45 | private final ConcurrentMap staticCache = ConcurrentCollections.newConcurrentMap(); 46 | 47 | public ViewService(Settings settings) { 48 | this(settings, new Environment(), ImmutableSet.builder() 49 | .add(new MvelViewEngineService(settings)) 50 | .add(new BinaryViewEngineService(settings)) 51 | .build()); 52 | } 53 | 54 | @Inject 55 | public ViewService(Settings settings, Environment environment, Set viewEngines) { 56 | super(settings); 57 | 58 | this.defaultViewLang = componentSettings.get("default_view_lang", "mvel"); 59 | 60 | ImmutableMap.Builder builder = ImmutableMap.builder(); 61 | for (ViewEngineService viewEngine : viewEngines) { 62 | for (String type : viewEngine.types()) { 63 | builder.put(type, viewEngine); 64 | } 65 | } 66 | this.viewEngines = builder.build(); 67 | 68 | // compile static scripts 69 | File viewsFile = new File(environment.configFile(), "views"); 70 | if (viewsFile.exists()) { 71 | processViewsDirectory("", viewsFile); 72 | } 73 | } 74 | 75 | private void processViewsDirectory(String prefix, File dir) { 76 | for (File file : dir.listFiles()) { 77 | if (file.isDirectory()) { 78 | processViewsDirectory(prefix + file.getName() + "_", file); 79 | } else { 80 | int extIndex = file.getName().lastIndexOf('.'); 81 | if (extIndex != -1) { 82 | String ext = file.getName().substring(extIndex + 1); 83 | String viewName = prefix + file.getName().substring(0, extIndex); 84 | boolean found = false; 85 | for (ViewEngineService viewEngine : viewEngines.values()) { 86 | for (String s : viewEngine.extensions()) { 87 | if (s.equals(ext)) { 88 | found = true; 89 | try { 90 | String view = Streams.copyToString(new InputStreamReader(new FileInputStream(file), "UTF-8")); 91 | viewEngine.load(viewName, view); 92 | } catch (Exception e) { 93 | logger.warn("failed to load/compile view [{}]", e, viewName); 94 | } 95 | break; 96 | } 97 | } 98 | if (found) { 99 | break; 100 | } 101 | } 102 | if (!found) { 103 | logger.warn("no view engine found for [{}]", ext); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | public ViewResult render(ViewContext context) { 111 | ViewEngineService viewEngineService = viewEngines.get(context.lang() == null ? defaultViewLang : context.lang()); 112 | return new ViewResult(context.contentType() == null ? viewEngineService.contentType() : context.contentType(), viewEngineService.render(context.view(), context.varsAsMap())); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/binary/BinaryViewEngineService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view.binary; 20 | 21 | import org.elasticsearch.common.Base64; 22 | import org.elasticsearch.common.Nullable; 23 | import org.elasticsearch.common.component.AbstractComponent; 24 | import org.elasticsearch.common.inject.Inject; 25 | import org.elasticsearch.common.settings.Settings; 26 | import org.elasticsearch.view.ViewEngineService; 27 | 28 | import java.io.IOException; 29 | import java.util.Map; 30 | 31 | public class BinaryViewEngineService extends AbstractComponent implements ViewEngineService { 32 | 33 | @Inject 34 | public BinaryViewEngineService(Settings settings) { 35 | super(settings); 36 | } 37 | 38 | @Override 39 | public String[] types() { 40 | return new String[]{"binary"}; 41 | } 42 | 43 | @Override 44 | public String[] extensions() { 45 | return new String[]{}; 46 | } 47 | 48 | @Override 49 | public String contentType() { 50 | return "application/octet-stream"; 51 | } 52 | 53 | @Override 54 | public void load(String name, String view) { 55 | // Nothing to load 56 | } 57 | 58 | @Override 59 | public byte[] render(String view, @Nullable Map vars) { 60 | byte[] result = new byte[0]; 61 | if (vars != null) { 62 | Object binary = getBinaryField(view, vars); 63 | if (binary != null) { 64 | if (binary instanceof String) { 65 | try { 66 | result = Base64.decode((String) binary); 67 | } catch (IOException e) { 68 | //todo gérer exception 69 | } 70 | } 71 | } 72 | } 73 | return result; 74 | } 75 | 76 | private Object getBinaryField(String name, Object scope) { 77 | int indexDot = name.indexOf("."); 78 | if (indexDot == -1) { 79 | if (scope instanceof Map) { 80 | Object binary = ((Map) scope).get(name); 81 | return binary; 82 | } 83 | } else { 84 | String accessor = name.substring(0, indexDot); 85 | if (scope instanceof Map) { 86 | scope = ((Map) scope).get(accessor); 87 | return getBinaryField(name.substring(indexDot + 1), scope); 88 | } 89 | } 90 | return null; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/exception/ElasticSearchViewNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view.exception; 20 | 21 | 22 | import org.elasticsearch.ElasticSearchException; 23 | 24 | public class ElasticSearchViewNotFoundException extends ElasticSearchException { 25 | public ElasticSearchViewNotFoundException(String msg) { 26 | super(msg); 27 | } 28 | 29 | public ElasticSearchViewNotFoundException(String msg, Throwable cause) { 30 | super(msg, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/elasticsearch/view/mvel/MvelViewEngineService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.view.mvel; 20 | 21 | import org.elasticsearch.common.Nullable; 22 | import org.elasticsearch.common.component.AbstractComponent; 23 | import org.elasticsearch.common.inject.Inject; 24 | import org.elasticsearch.common.settings.Settings; 25 | import org.elasticsearch.common.util.concurrent.ConcurrentCollections; 26 | import org.elasticsearch.view.ViewEngineService; 27 | import org.mvel2.templates.CompiledTemplate; 28 | import org.mvel2.templates.TemplateCompiler; 29 | import org.mvel2.templates.TemplateRegistry; 30 | import org.mvel2.templates.TemplateRuntime; 31 | 32 | import java.util.Iterator; 33 | import java.util.Map; 34 | import java.util.Set; 35 | import java.util.concurrent.ConcurrentMap; 36 | 37 | public class MvelViewEngineService extends AbstractComponent implements ViewEngineService { 38 | 39 | private final StaticCacheTemplateRegistry registry; 40 | 41 | @Inject 42 | public MvelViewEngineService(Settings settings) { 43 | super(settings); 44 | registry = new StaticCacheTemplateRegistry(); 45 | } 46 | 47 | @Override 48 | public String[] types() { 49 | return new String[]{"mvel"}; 50 | } 51 | 52 | @Override 53 | public String[] extensions() { 54 | return new String[]{"mv"}; 55 | } 56 | 57 | @Override 58 | public String contentType() { 59 | return "text/html;charset=utf8"; 60 | } 61 | 62 | @Override 63 | public void load(String name, String view) { 64 | // compile the template 65 | CompiledTemplate compiled = TemplateCompiler.compileTemplate(view); 66 | registry.addNamedTemplate(name, compiled); 67 | } 68 | 69 | @Override 70 | public byte[] render(String view, @Nullable Map vars) { 71 | String output = (String) TemplateRuntime.eval(view, vars, registry); 72 | return output.getBytes(); 73 | } 74 | 75 | private class StaticCacheTemplateRegistry implements TemplateRegistry { 76 | 77 | private final ConcurrentMap staticCache = ConcurrentCollections.newConcurrentMap(); 78 | 79 | @Override 80 | public Iterator iterator() { 81 | return staticCache.values().iterator(); 82 | } 83 | 84 | @Override 85 | public Set getNames() { 86 | return staticCache.keySet(); 87 | } 88 | 89 | @Override 90 | public boolean contains(String name) { 91 | return staticCache.containsKey(name); 92 | } 93 | 94 | @Override 95 | public void addNamedTemplate(String name, CompiledTemplate compiledTemplate) { 96 | staticCache.put(name, compiledTemplate); 97 | } 98 | 99 | @Override 100 | public CompiledTemplate getNamedTemplate(String name) { 101 | return staticCache.get(name); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/resources/es-plugin.properties: -------------------------------------------------------------------------------- 1 | plugin=org.elasticsearch.plugin.view.ViewPlugin -------------------------------------------------------------------------------- /src/test/java/org/elasticsearch/test/integration/views/ViewActionTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elastic Search and Shay Banon under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. Elastic Search licenses this 6 | * file to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package org.elasticsearch.test.integration.views; 20 | 21 | 22 | import com.github.tlrx.elasticsearch.test.EsSetup; 23 | import org.elasticsearch.action.view.ViewAction; 24 | import org.elasticsearch.action.view.ViewRequest; 25 | import org.elasticsearch.action.view.ViewResponse; 26 | import org.elasticsearch.common.settings.ImmutableSettings; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import static com.github.tlrx.elasticsearch.test.EsSetup.*; 32 | import static junit.framework.Assert.*; 33 | import static junit.framework.Assert.assertEquals; 34 | 35 | public class ViewActionTests { 36 | 37 | EsSetup esSetup; 38 | 39 | @Before 40 | public void setUp() { 41 | 42 | // Instantiates a local node & client with few templates in config dir 43 | esSetup = new EsSetup(ImmutableSettings 44 | .settingsBuilder() 45 | .put("path.conf", "./target/test-classes/org/elasticsearch/test/integration/views/config/") 46 | .build()); 47 | 48 | // Clean all and create test org.elasticsearch.test.integration.views.mappings.data 49 | esSetup.execute( 50 | 51 | deleteAll(), 52 | 53 | createIndex("catalog") 54 | .withMapping("product", fromClassPath("org/elasticsearch/test/integration/views/mappings/product.json")) 55 | .withData(fromClassPath("org/elasticsearch/test/integration/views/data/products.json")), 56 | 57 | createIndex("manufacturers") 58 | .withMapping("brand", fromClassPath("org/elasticsearch/test/integration/views/mappings/brand.json")) 59 | .withData(fromClassPath("org/elasticsearch/test/integration/views/data/brands.json")) 60 | ); 61 | } 62 | 63 | @Test 64 | public void testDefaultView() throws Exception { 65 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "product", "1")).get(); 66 | assertEquals("Rendering the document #1 in version 1 of type product from index catalog", new String(response.content(), "UTF-8")); 67 | } 68 | 69 | @Test 70 | public void testFullView() throws Exception { 71 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "product", "2").format("full")).get(); 72 | assertEquals("

Detail of 1952 ALPINE RENAULT 1300

Year: 1952, price: 98.58€

Turnable front wheels; steering function; detailed interior; detailed engine; opening hood; opening trunk; opening doors; and detailed chassis.

© Copyright Renault

", new String(response.content(), "UTF-8")); 73 | } 74 | 75 | @Test 76 | public void testBinaryView() throws Exception { 77 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "product", "1").format("logo")).get(); 78 | assertNotNull(response.content()); 79 | assertEquals(0, response.content().length); 80 | 81 | response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "product", "2").format("logo")).get(); 82 | assertNotNull(response.content()); 83 | assertEquals(61752, response.content().length); 84 | } 85 | 86 | @Test 87 | public void testUndefinedView() throws Exception { 88 | try { 89 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "product", "1").format("undefined")).get(); 90 | fail("Exception expected!"); 91 | } catch (Exception e) { 92 | assertEquals("org.elasticsearch.view.exception.ElasticSearchViewNotFoundException: No view [undefined] found for document type [product]", e.getMessage()); 93 | } 94 | } 95 | 96 | @Test 97 | public void testCustomView() throws Exception { 98 | 99 | // Index a custom view 100 | esSetup.execute( 101 | index("catalog", "list-of-products-by-size", "1:10") 102 | .withSource("{\n" + 103 | " \"views\":{\n" + 104 | " \"default\":{\n" + 105 | " \"view_lang\": \"mvel\",\n" + 106 | " \"queries\": {\n" + 107 | " \"products_with_size_1_10\": {\n" + 108 | " \"indices\": \"catalog\",\n" + 109 | " \"types\": [\"product\"],\n" + 110 | " \"query\" : {\n" + 111 | " \"constant_score\" : {\n" + 112 | " \"filter\" : {\n" + 113 | " \"term\" : { \"scale\" : \"1:10\"}\n" + 114 | " }\n" + 115 | " }\n" + 116 | " }\n" + 117 | " }\n" + 118 | " },\n" + 119 | " \"view\" : \"@includeNamed{'list-of-products'; title='List of products'}\"\n" + 120 | " }\n" + 121 | " }\n" + 122 | "}") 123 | ); 124 | 125 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("catalog", "list-of-products-by-size", "1:10")).get(); 126 | assertEquals(fromClassPath("org/elasticsearch/test/integration/views/config/views/list-of-products.html").toString(), new String(response.content(), "UTF-8")); 127 | } 128 | 129 | @Test 130 | public void testCustomViewWithMultipleQueries() throws Exception { 131 | 132 | // Index a custom view 133 | esSetup.execute( 134 | index("web", "pages", "home") 135 | .withSource("{\n" + 136 | " \"views\": {\n" + 137 | " \"default\": {\n" + 138 | " \"view_lang\": \"mvel\",\n" + 139 | " \"queries\": [\n" + 140 | " {\n" + 141 | " \"products_with_price_lower_than_50\": {\n" + 142 | " \"indices\": \"catalog\",\n" + 143 | " \"types\": [\"product\"],\n" + 144 | " \"query\" : {\n" + 145 | " \"constant_score\" : {\n" + 146 | " \"filter\" : {\n" + 147 | " \"range\" : { \"price\" : { \"to\": 50, \"include_upper\": true } }\n" + 148 | " }\n" + 149 | " }\n" + 150 | " }\n" + 151 | " }\n" + 152 | " },\n" + 153 | " {\n" + 154 | " \"brands\": {\n" + 155 | " \"indices\": \"manufacturers\",\n" + 156 | " \"types\": [\"brand\"],\n" + 157 | " \"query\" : {\n" + 158 | " \"match_all\" : {}\n" + 159 | " },\n" + 160 | " \"sort\" : [\n" + 161 | " { \"name\" : \"asc\" },\n"+ 162 | " { \"country\" : \"asc\" }\n"+ 163 | " ],\n"+ 164 | " \"fields\" : [ \"name\" ]\n"+ 165 | " }\n" + 166 | " }\n" + 167 | " ],\n" + 168 | " \"view\" : \"@includeNamed{'list-of-products-with-brands'; title='Welcome'}\"\n" + 169 | " }\n" + 170 | " }\n" + 171 | "}") 172 | ); 173 | 174 | ViewResponse response = esSetup.client().execute(ViewAction.INSTANCE, new ViewRequest("web", "pages", "home")).get(); 175 | assertEquals(fromClassPath("org/elasticsearch/test/integration/views/config/views/list-of-products-with-brands.html").toString(), new String(response.content(), "UTF-8")); 176 | } 177 | 178 | 179 | @After 180 | public void tearDown() throws Exception { 181 | esSetup.terminate(); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/copyright.mv: -------------------------------------------------------------------------------- 1 | © Copyright @{_source.brand} -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products-with-brands.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 43 | 44 |
45 | 46 |
47 | 48 |
49 | 68 |
69 | 70 |
71 |

Top products

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
CodeNameBrandYear
S10_16781969 Harley Davidson Ultimate ChopperHarley Davidson1969
92 |
93 |
94 | 95 |
96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products-with-brands.html~: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Welcome 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 44 | 45 |
46 | 47 |
48 | 49 |
50 | 69 |
70 | 71 |
72 |

Top products

73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
CodeNameBrandYear
S10_16781969 Harley Davidson Ultimate ChopperHarley Davidson1969
93 |
94 |
95 | 96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products-with-brands.mv: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @{title} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 43 | 44 |
45 | 46 |
47 | 48 |
49 | 56 |
57 | 58 |
59 |

Top products

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | @foreach{item : _queries.products_with_price_lower_than_50} 71 | 72 | 73 | 74 | 75 | 76 | 77 | @end{} 78 | 79 |
CodeNameBrandYear
@{item._source.code}@{item._source.name}@{item._source.brand}@{item._source.since}
80 |
81 |
82 | 83 |
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List of products 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 43 | 44 |
45 |

List of products with scale 1:10

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
CodeNameBrandYear
S10_16781969 Harley Davidson Ultimate ChopperHarley Davidson1969
S10_19491952 Alpine Renault 1300Renault1952
73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products.html~: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List of products 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 44 | 45 |
46 |

List of products with scale 1:10

47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
CodeNameBrandYear
S10_16781969 Harley Davidson Ultimate ChopperHarley Davidson1969
S10_19491952 Alpine Renault 1300Renault1952
74 |
75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/config/views/list-of-products.mv: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @{title} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 43 | 44 |
45 |

List of products with scale 1:10

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | @foreach{item : _queries.products_with_size_1_10} 57 | 58 | 59 | 60 | 61 | 62 | 63 | @end{} 64 | 65 |
CodeNameBrandYear
@{item._source.code}@{item._source.name}@{item._source.brand}@{item._source.since}
66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/data/brands.json: -------------------------------------------------------------------------------- 1 | { "index" : { "_index" : "manufacturers", "_type" : "brand", "_id" : "1" } } 2 | { "name" : "Renault", "country": "FR"} 3 | { "index" : { "_index" : "manufacturers", "_type" : "brand", "_id" : "2" } } 4 | { "name" : "Honda", "country": "JP"} 5 | { "index" : { "_index" : "manufacturers", "_type" : "brand", "_id" : "3" } } 6 | { "name" : "Ducati", "country": "IT"} 7 | { "index" : { "_index" : "manufacturers", "_type" : "brand", "_id" : "4" } } 8 | { "name" : "Triumph", "country": "UK"} 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/mappings/brand.json: -------------------------------------------------------------------------------- 1 | { 2 | "brand": { 3 | "_source" : {"enabled" : false}, 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "store":"yes" 8 | }, 9 | "country": { 10 | "type": "string", 11 | "index": "not_analyzed" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/resources/org/elasticsearch/test/integration/views/mappings/product.json: -------------------------------------------------------------------------------- 1 | { 2 | "product": { 3 | "properties": { 4 | "name": { 5 | "type": "string" 6 | }, 7 | "type": { 8 | "type": "string", 9 | "index": "not_analyzed" 10 | }, 11 | "brand": { 12 | "type": "string" 13 | }, 14 | "code": { 15 | "type": "string", 16 | "index": "not_analyzed" 17 | }, 18 | "description": { 19 | "type": "string" 20 | }, 21 | "price": { 22 | "type": "double" 23 | }, 24 | "picture": { 25 | "type": "binary" 26 | }, 27 | "since": { 28 | "type": "date", 29 | "format": "YYYY", 30 | "ignore_malformed": "true" 31 | }, 32 | "scale": { 33 | "type": "string", 34 | "null_value": "unknown", 35 | "index": "not_analyzed" 36 | } 37 | }, 38 | "_meta": { 39 | "views": { 40 | "default": { 41 | "view": "Rendering the document #@{_id} in version @{_version} of type @{_type} from index @{_index}" 42 | }, 43 | "full": { 44 | "view_lang": "mvel", 45 | "view": "

Detail of @{_source.name.toUpperCase()}

Year: @{_source.since}, price: @{_source.price}€

@{_source.description}

@includeNamed{\"copyright\"}

" 46 | }, 47 | "logo": { 48 | "view_lang": "binary", 49 | "view": "_source.picture" 50 | } 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/resources/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ES_HOST=localhost:9200 4 | 5 | echo Delete all 6 | curl -XDELETE "http://$ES_HOST/" 7 | 8 | echo 9 | echo Creating index catalog 10 | curl -XPOST "http://$ES_HOST/catalog" 11 | 12 | echo 13 | echo Updating product mapping 14 | curl -XPUT "http://$ES_HOST/catalog/product/_mapping" --data-binary @org/elasticsearch/test/integration/views/mappings/product.json 15 | 16 | echo 17 | echo Bulk indexing products 18 | curl -XPOST "http://$ES_HOST/catalog/product/_bulk" --data-binary @org/elasticsearch/test/integration/views/data/products.json 19 | 20 | echo 21 | echo Creating index manufacturers 22 | curl -XPOST "http://$ES_HOST/manufacturers" 23 | 24 | echo 25 | echo Updating brand mapping 26 | curl -XPUT "http://$ES_HOST/manufacturers/brand/_mapping" --data-binary @org/elasticsearch/test/integration/views/mappings/brand.json 27 | 28 | echo 29 | echo Bulk indexing brands 30 | curl -XPOST "http://$ES_HOST/manufacturers/brand/_bulk" --data-binary @org/elasticsearch/test/integration/views/data/brands.json 31 | 32 | echo 33 | echo Indexing list-of-products-by-size view 34 | curl -XPUT "http://$ES_HOST/catalog/list-of-products-by-size/1:10" -d "{ 35 | \"views\":{ 36 | \"default\":{ 37 | \"view_lang\": \"mvel\", 38 | \"queries\": { 39 | \"products_with_size_1_10\": { 40 | \"indices\": \"catalog\", 41 | \"types\": [\"product\"], 42 | \"query\" : { 43 | \"constant_score\" : { 44 | \"filter\" : { 45 | \"term\" : { \"scale\" : \"1:10\"} 46 | } 47 | } 48 | } 49 | } 50 | }, 51 | \"view\" : \"@includeNamed{'list-of-products'; title='List of products'}\" 52 | } 53 | } 54 | }" 55 | 56 | 57 | echo 58 | echo Indexing manufacturers view 59 | curl -XPUT "http://$ES_HOST/web/pages/home" -d "{ 60 | \"views\": { 61 | \"default\": { 62 | \"view_lang\": \"mvel\", 63 | \"queries\": [ 64 | { 65 | \"products_with_price_lower_than_50\": { 66 | \"indices\": \"catalog\", 67 | \"types\": [\"product\"], 68 | \"query\" : { 69 | \"constant_score\" : { 70 | \"filter\" : { 71 | \"range\" : { \"price\" : { \"to\": 50, \"include_upper\": true } } 72 | } 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | \"brands\": { 79 | \"indices\": \"manufacturers\", 80 | \"types\": [\"brand\"], 81 | \"query\" : { 82 | \"match_all\" : {} 83 | }, 84 | \"sort\" : [ 85 | { \"name\" : \"asc\" }, 86 | { \"country\" : \"asc\" } 87 | ], 88 | \"fields\" : [ \"name\" ] 89 | } 90 | } 91 | ], 92 | \"view\" : \"@includeNamed{'list-of-products-with-brands'; title='Welcome'}\" 93 | } 94 | } 95 | }" 96 | 97 | 98 | echo 99 | echo Refresh all 100 | curl -XPOST "http://$ES_HOST/_refresh" 101 | 102 | 103 | echo 104 | echo ----- Views ------- 105 | curl -XGET "http://$ES_HOST/_view/catalog/product/1" 106 | 107 | echo 108 | curl -XGET "http://$ES_HOST/_view/catalog/product/2/full" 109 | 110 | echo 111 | curl -XGET "http://$ES_HOST/_view/catalog/product/1/logo" -o /dev/null -w "Size: %{size_download}\r\n" 112 | 113 | echo 114 | curl -XGET "http://$ES_HOST/_view/catalog/product/2/logo" -o /dev/null -w "Size: %{size_download}\r\n" 115 | 116 | echo 117 | curl -XGET "http://$ES_HOST/_view/catalog/list-of-products-by-size/1:10" 118 | 119 | echo 120 | curl -XGET "http://$ES_HOST/_view/web/pages/home" 121 | 122 | -------------------------------------------------------------------------------- /stylesheets/ie.css: -------------------------------------------------------------------------------- 1 | nav { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ 2 | /* ============================================================================= 3 | HTML5 display definitions 4 | ========================================================================== */ 5 | /* 6 | * Corrects block display not defined in IE6/7/8/9 & FF3 7 | */ 8 | article, 9 | aside, 10 | details, 11 | figcaption, 12 | figure, 13 | footer, 14 | header, 15 | hgroup, 16 | nav, 17 | section, 18 | summary { 19 | display: block; 20 | } 21 | 22 | /* 23 | * Corrects inline-block display not defined in IE6/7/8/9 & FF3 24 | */ 25 | audio, 26 | canvas, 27 | video { 28 | display: inline-block; 29 | *display: inline; 30 | *zoom: 1; 31 | } 32 | 33 | /* 34 | * Prevents modern browsers from displaying 'audio' without controls 35 | */ 36 | audio:not([controls]) { 37 | display: none; 38 | } 39 | 40 | /* 41 | * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 42 | * Known issue: no IE6 support 43 | */ 44 | [hidden] { 45 | display: none; 46 | } 47 | 48 | /* ============================================================================= 49 | Base 50 | ========================================================================== */ 51 | /* 52 | * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units 53 | * http://clagnut.com/blog/348/#c790 54 | * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom 55 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 56 | */ 57 | html { 58 | font-size: 100%; 59 | /* 1 */ 60 | -webkit-text-size-adjust: 100%; 61 | /* 2 */ 62 | -ms-text-size-adjust: 100%; 63 | /* 2 */ 64 | } 65 | 66 | /* 67 | * Addresses font-family inconsistency between 'textarea' and other form elements. 68 | */ 69 | html, 70 | button, 71 | input, 72 | select, 73 | textarea { 74 | font-family: sans-serif; 75 | } 76 | 77 | /* 78 | * Addresses margins handled incorrectly in IE6/7 79 | */ 80 | body { 81 | margin: 0; 82 | } 83 | 84 | /* ============================================================================= 85 | Links 86 | ========================================================================== */ 87 | /* 88 | * Addresses outline displayed oddly in Chrome 89 | */ 90 | a:focus { 91 | outline: thin dotted; 92 | } 93 | 94 | /* 95 | * Improves readability when focused and also mouse hovered in all browsers 96 | * people.opera.com/patrickl/experiments/keyboard/test 97 | */ 98 | a:hover, 99 | a:active { 100 | outline: 0; 101 | } 102 | 103 | /* ============================================================================= 104 | Typography 105 | ========================================================================== */ 106 | /* 107 | * Addresses font sizes and margins set differently in IE6/7 108 | * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 109 | */ 110 | h1 { 111 | font-size: 2em; 112 | margin: 0.67em 0; 113 | } 114 | 115 | h2 { 116 | font-size: 1.5em; 117 | margin: 0.83em 0; 118 | } 119 | 120 | h3 { 121 | font-size: 1.17em; 122 | margin: 1em 0; 123 | } 124 | 125 | h4 { 126 | font-size: 1em; 127 | margin: 1.33em 0; 128 | } 129 | 130 | h5 { 131 | font-size: 0.83em; 132 | margin: 1.67em 0; 133 | } 134 | 135 | h6 { 136 | font-size: 0.75em; 137 | margin: 2.33em 0; 138 | } 139 | 140 | /* 141 | * Addresses styling not present in IE7/8/9, S5, Chrome 142 | */ 143 | abbr[title] { 144 | border-bottom: 1px dotted; 145 | } 146 | 147 | /* 148 | * Addresses style set to 'bolder' in FF3+, S4/5, Chrome 149 | */ 150 | b, 151 | strong { 152 | font-weight: bold; 153 | } 154 | 155 | blockquote { 156 | margin: 1em 40px; 157 | } 158 | 159 | /* 160 | * Addresses styling not present in S5, Chrome 161 | */ 162 | dfn { 163 | font-style: italic; 164 | } 165 | 166 | /* 167 | * Addresses styling not present in IE6/7/8/9 168 | */ 169 | mark { 170 | background: #ff0; 171 | color: #000; 172 | } 173 | 174 | /* 175 | * Addresses margins set differently in IE6/7 176 | */ 177 | p, 178 | pre { 179 | margin: 1em 0; 180 | } 181 | 182 | /* 183 | * Corrects font family set oddly in IE6, S4/5, Chrome 184 | * en.wikipedia.org/wiki/User:Davidgothberg/Test59 185 | */ 186 | pre, 187 | code, 188 | kbd, 189 | samp { 190 | font-family: monospace, serif; 191 | _font-family: 'courier new', monospace; 192 | font-size: 1em; 193 | } 194 | 195 | /* 196 | * 1. Addresses CSS quotes not supported in IE6/7 197 | * 2. Addresses quote property not supported in S4 198 | */ 199 | /* 1 */ 200 | q { 201 | quotes: none; 202 | } 203 | 204 | /* 2 */ 205 | q:before, 206 | q:after { 207 | content: ''; 208 | content: none; 209 | } 210 | 211 | small { 212 | font-size: 75%; 213 | } 214 | 215 | /* 216 | * Prevents sub and sup affecting line-height in all browsers 217 | * gist.github.com/413930 218 | */ 219 | sub, 220 | sup { 221 | font-size: 75%; 222 | line-height: 0; 223 | position: relative; 224 | vertical-align: baseline; 225 | } 226 | 227 | sup { 228 | top: -0.5em; 229 | } 230 | 231 | sub { 232 | bottom: -0.25em; 233 | } 234 | 235 | /* ============================================================================= 236 | Lists 237 | ========================================================================== */ 238 | /* 239 | * Addresses margins set differently in IE6/7 240 | */ 241 | dl, 242 | menu, 243 | ol, 244 | ul { 245 | margin: 1em 0; 246 | } 247 | 248 | dd { 249 | margin: 0 0 0 40px; 250 | } 251 | 252 | /* 253 | * Addresses paddings set differently in IE6/7 254 | */ 255 | menu, 256 | ol, 257 | ul { 258 | padding: 0 0 0 40px; 259 | } 260 | 261 | /* 262 | * Corrects list images handled incorrectly in IE7 263 | */ 264 | nav ul, 265 | nav ol { 266 | list-style: none; 267 | list-style-image: none; 268 | } 269 | 270 | /* ============================================================================= 271 | Embedded content 272 | ========================================================================== */ 273 | /* 274 | * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 275 | * 2. Improves image quality when scaled in IE7 276 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 277 | */ 278 | img { 279 | border: 0; 280 | /* 1 */ 281 | -ms-interpolation-mode: bicubic; 282 | /* 2 */ 283 | } 284 | 285 | /* 286 | * Corrects overflow displayed oddly in IE9 287 | */ 288 | svg:not(:root) { 289 | overflow: hidden; 290 | } 291 | 292 | /* ============================================================================= 293 | Figures 294 | ========================================================================== */ 295 | /* 296 | * Addresses margin not present in IE6/7/8/9, S5, O11 297 | */ 298 | figure { 299 | margin: 0; 300 | } 301 | 302 | /* ============================================================================= 303 | Forms 304 | ========================================================================== */ 305 | /* 306 | * Corrects margin displayed oddly in IE6/7 307 | */ 308 | form { 309 | margin: 0; 310 | } 311 | 312 | /* 313 | * Define consistent border, margin, and padding 314 | */ 315 | fieldset { 316 | border: 1px solid #c0c0c0; 317 | margin: 0 2px; 318 | padding: 0.35em 0.625em 0.75em; 319 | } 320 | 321 | /* 322 | * 1. Corrects color not being inherited in IE6/7/8/9 323 | * 2. Corrects text not wrapping in FF3 324 | * 3. Corrects alignment displayed oddly in IE6/7 325 | */ 326 | legend { 327 | border: 0; 328 | /* 1 */ 329 | padding: 0; 330 | white-space: normal; 331 | /* 2 */ 332 | *margin-left: -7px; 333 | /* 3 */ 334 | } 335 | 336 | /* 337 | * 1. Corrects font size not being inherited in all browsers 338 | * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome 339 | * 3. Improves appearance and consistency in all browsers 340 | */ 341 | button, 342 | input, 343 | select, 344 | textarea { 345 | font-size: 100%; 346 | /* 1 */ 347 | margin: 0; 348 | /* 2 */ 349 | vertical-align: baseline; 350 | /* 3 */ 351 | *vertical-align: middle; 352 | /* 3 */ 353 | } 354 | 355 | /* 356 | * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet 357 | */ 358 | button, 359 | input { 360 | line-height: normal; 361 | /* 1 */ 362 | } 363 | 364 | /* 365 | * 1. Improves usability and consistency of cursor style between image-type 'input' and others 366 | * 2. Corrects inability to style clickable 'input' types in iOS 367 | * 3. Removes inner spacing in IE7 without affecting normal text inputs 368 | * Known issue: inner spacing remains in IE6 369 | */ 370 | button, 371 | input[type="button"], 372 | input[type="reset"], 373 | input[type="submit"] { 374 | cursor: pointer; 375 | /* 1 */ 376 | -webkit-appearance: button; 377 | /* 2 */ 378 | *overflow: visible; 379 | /* 3 */ 380 | } 381 | 382 | /* 383 | * Re-set default cursor for disabled elements 384 | */ 385 | button[disabled], 386 | input[disabled] { 387 | cursor: default; 388 | } 389 | 390 | /* 391 | * 1. Addresses box sizing set to content-box in IE8/9 392 | * 2. Removes excess padding in IE8/9 393 | * 3. Removes excess padding in IE7 394 | Known issue: excess padding remains in IE6 395 | */ 396 | input[type="checkbox"], 397 | input[type="radio"] { 398 | box-sizing: border-box; 399 | /* 1 */ 400 | padding: 0; 401 | /* 2 */ 402 | *height: 13px; 403 | /* 3 */ 404 | *width: 13px; 405 | /* 3 */ 406 | } 407 | 408 | /* 409 | * 1. Addresses appearance set to searchfield in S5, Chrome 410 | * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) 411 | */ 412 | input[type="search"] { 413 | -webkit-appearance: textfield; 414 | /* 1 */ 415 | -moz-box-sizing: content-box; 416 | -webkit-box-sizing: content-box; 417 | /* 2 */ 418 | box-sizing: content-box; 419 | } 420 | 421 | /* 422 | * Removes inner padding and search cancel button in S5, Chrome on OS X 423 | */ 424 | input[type="search"]::-webkit-search-decoration, 425 | input[type="search"]::-webkit-search-cancel-button { 426 | -webkit-appearance: none; 427 | } 428 | 429 | /* 430 | * Removes inner padding and border in FF3+ 431 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 432 | */ 433 | button::-moz-focus-inner, 434 | input::-moz-focus-inner { 435 | border: 0; 436 | padding: 0; 437 | } 438 | 439 | /* 440 | * 1. Removes default vertical scrollbar in IE6/7/8/9 441 | * 2. Improves readability and alignment in all browsers 442 | */ 443 | textarea { 444 | overflow: auto; 445 | /* 1 */ 446 | vertical-align: top; 447 | /* 2 */ 448 | } 449 | 450 | /* ============================================================================= 451 | Tables 452 | ========================================================================== */ 453 | /* 454 | * Remove most spacing between table cells 455 | */ 456 | table { 457 | border-collapse: collapse; 458 | border-spacing: 0; 459 | } 460 | -------------------------------------------------------------------------------- /stylesheets/pygment_trac.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #404040 } 2 | .highlight { color: #d0d0d0 } 3 | .highlight .c { color: #999999; font-style: italic } /* Comment */ 4 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 5 | .highlight .g { color: #d0d0d0 } /* Generic */ 6 | .highlight .k { color: #6ab825; font-weight: normal } /* Keyword */ 7 | .highlight .l { color: #d0d0d0 } /* Literal */ 8 | .highlight .n { color: #d0d0d0 } /* Name */ 9 | .highlight .o { color: #d0d0d0 } /* Operator */ 10 | .highlight .x { color: #d0d0d0 } /* Other */ 11 | .highlight .p { color: #d0d0d0 } /* Punctuation */ 12 | .highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ 13 | .highlight .cp { color: #cd2828; font-weight: normal } /* Comment.Preproc */ 14 | .highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ 15 | .highlight .cs { color: #e50808; font-weight: normal; background-color: #520000 } /* Comment.Special */ 16 | .highlight .gd { color: #d22323 } /* Generic.Deleted */ 17 | .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ 18 | .highlight .gr { color: #d22323 } /* Generic.Error */ 19 | .highlight .gh { color: #ffffff; font-weight: normal } /* Generic.Heading */ 20 | .highlight .gi { color: #589819 } /* Generic.Inserted */ 21 | .highlight .go { color: #cccccc } /* Generic.Output */ 22 | .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ 23 | .highlight .gs { color: #d0d0d0; font-weight: normal } /* Generic.Strong */ 24 | .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ 25 | .highlight .gt { color: #d22323 } /* Generic.Traceback */ 26 | .highlight .kc { color: #6ab825; font-weight: normal } /* Keyword.Constant */ 27 | .highlight .kd { color: #6ab825; font-weight: normal } /* Keyword.Declaration */ 28 | .highlight .kn { color: #6ab825; font-weight: normal } /* Keyword.Namespace */ 29 | .highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ 30 | .highlight .kr { color: #6ab825; font-weight: normal } /* Keyword.Reserved */ 31 | .highlight .kt { color: #6ab825; font-weight: normal } /* Keyword.Type */ 32 | .highlight .ld { color: #d0d0d0 } /* Literal.Date */ 33 | .highlight .m { color: #3677a9 } /* Literal.Number */ 34 | .highlight .s { color: #9dd5f1 } /* Literal.String */ 35 | .highlight .na { color: #bbbbbb } /* Name.Attribute */ 36 | .highlight .nb { color: #24909d } /* Name.Builtin */ 37 | .highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ 38 | .highlight .no { color: #40ffff } /* Name.Constant */ 39 | .highlight .nd { color: #ffa500 } /* Name.Decorator */ 40 | .highlight .ni { color: #d0d0d0 } /* Name.Entity */ 41 | .highlight .ne { color: #bbbbbb } /* Name.Exception */ 42 | .highlight .nf { color: #447fcf } /* Name.Function */ 43 | .highlight .nl { color: #d0d0d0 } /* Name.Label */ 44 | .highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ 45 | .highlight .nx { color: #d0d0d0 } /* Name.Other */ 46 | .highlight .py { color: #d0d0d0 } /* Name.Property */ 47 | .highlight .nt { color: #6ab825;} /* Name.Tag */ 48 | .highlight .nv { color: #40ffff } /* Name.Variable */ 49 | .highlight .ow { color: #6ab825; font-weight: normal } /* Operator.Word */ 50 | .highlight .w { color: #666666 } /* Text.Whitespace */ 51 | .highlight .mf { color: #3677a9 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ 55 | .highlight .sb { color: #9dd5f1 } /* Literal.String.Backtick */ 56 | .highlight .sc { color: #9dd5f1 } /* Literal.String.Char */ 57 | .highlight .sd { color: #9dd5f1 } /* Literal.String.Doc */ 58 | .highlight .s2 { color: #9dd5f1 } /* Literal.String.Double */ 59 | .highlight .se { color: #9dd5f1 } /* Literal.String.Escape */ 60 | .highlight .sh { color: #9dd5f1 } /* Literal.String.Heredoc */ 61 | .highlight .si { color: #9dd5f1 } /* Literal.String.Interpol */ 62 | .highlight .sx { color: #ffa500 } /* Literal.String.Other */ 63 | .highlight .sr { color: #9dd5f1 } /* Literal.String.Regex */ 64 | .highlight .s1 { color: #9dd5f1 } /* Literal.String.Single */ 65 | .highlight .ss { color: #9dd5f1 } /* Literal.String.Symbol */ 66 | .highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ 67 | .highlight .vc { color: #40ffff } /* Name.Variable.Class */ 68 | .highlight .vg { color: #40ffff } /* Name.Variable.Global */ 69 | .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ 70 | .highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /stylesheets/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'OpenSansLight'; 3 | src: url("../fonts/OpenSans-Light-webfont.eot"); 4 | src: url("../fonts/OpenSans-Light-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Light-webfont.woff") format("woff"), url("../fonts/OpenSans-Light-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Light-webfont.svg#OpenSansLight") format("svg"); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'OpenSansLightItalic'; 11 | src: url("../fonts/OpenSans-LightItalic-webfont.eot"); 12 | src: url("../fonts/OpenSans-LightItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-LightItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-LightItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-LightItalic-webfont.svg#OpenSansLightItalic") format("svg"); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'OpenSansRegular'; 19 | src: url("../fonts/OpenSans-Regular-webfont.eot"); 20 | src: url("../fonts/OpenSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Regular-webfont.woff") format("woff"), url("../fonts/OpenSans-Regular-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular") format("svg"); 21 | font-weight: normal; 22 | font-style: normal; 23 | -webkit-font-smoothing: antialiased; 24 | } 25 | 26 | @font-face { 27 | font-family: 'OpenSansItalic'; 28 | src: url("../fonts/OpenSans-Italic-webfont.eot"); 29 | src: url("../fonts/OpenSans-Italic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Italic-webfont.woff") format("woff"), url("../fonts/OpenSans-Italic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic") format("svg"); 30 | font-weight: normal; 31 | font-style: normal; 32 | -webkit-font-smoothing: antialiased; 33 | } 34 | 35 | @font-face { 36 | font-family: 'OpenSansSemibold'; 37 | src: url("../fonts/OpenSans-Semibold-webfont.eot"); 38 | src: url("../fonts/OpenSans-Semibold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Semibold-webfont.woff") format("woff"), url("../fonts/OpenSans-Semibold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold") format("svg"); 39 | font-weight: normal; 40 | font-style: normal; 41 | -webkit-font-smoothing: antialiased; 42 | } 43 | 44 | @font-face { 45 | font-family: 'OpenSansSemiboldItalic'; 46 | src: url("../fonts/OpenSans-SemiboldItalic-webfont.eot"); 47 | src: url("../fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-SemiboldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-SemiboldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic") format("svg"); 48 | font-weight: normal; 49 | font-style: normal; 50 | -webkit-font-smoothing: antialiased; 51 | } 52 | 53 | @font-face { 54 | font-family: 'OpenSansBold'; 55 | src: url("../fonts/OpenSans-Bold-webfont.eot"); 56 | src: url("../fonts/OpenSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Bold-webfont.woff") format("woff"), url("../fonts/OpenSans-Bold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Bold-webfont.svg#OpenSansBold") format("svg"); 57 | font-weight: normal; 58 | font-style: normal; 59 | -webkit-font-smoothing: antialiased; 60 | } 61 | 62 | @font-face { 63 | font-family: 'OpenSansBoldItalic'; 64 | src: url("../fonts/OpenSans-BoldItalic-webfont.eot"); 65 | src: url("../fonts/OpenSans-BoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-BoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-BoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic") format("svg"); 66 | font-weight: normal; 67 | font-style: normal; 68 | -webkit-font-smoothing: antialiased; 69 | } 70 | 71 | /* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ 72 | /* ============================================================================= 73 | HTML5 display definitions 74 | ========================================================================== */ 75 | /* 76 | * Corrects block display not defined in IE6/7/8/9 & FF3 77 | */ 78 | article, 79 | aside, 80 | details, 81 | figcaption, 82 | figure, 83 | footer, 84 | header, 85 | hgroup, 86 | nav, 87 | section, 88 | summary { 89 | display: block; 90 | } 91 | 92 | /* 93 | * Corrects inline-block display not defined in IE6/7/8/9 & FF3 94 | */ 95 | audio, 96 | canvas, 97 | video { 98 | display: inline-block; 99 | *display: inline; 100 | *zoom: 1; 101 | } 102 | 103 | /* 104 | * Prevents modern browsers from displaying 'audio' without controls 105 | */ 106 | audio:not([controls]) { 107 | display: none; 108 | } 109 | 110 | /* 111 | * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 112 | * Known issue: no IE6 support 113 | */ 114 | [hidden] { 115 | display: none; 116 | } 117 | 118 | /* ============================================================================= 119 | Base 120 | ========================================================================== */ 121 | /* 122 | * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units 123 | * http://clagnut.com/blog/348/#c790 124 | * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom 125 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 126 | */ 127 | html { 128 | font-size: 100%; 129 | /* 1 */ 130 | -webkit-text-size-adjust: 100%; 131 | /* 2 */ 132 | -ms-text-size-adjust: 100%; 133 | /* 2 */ 134 | } 135 | 136 | /* 137 | * Addresses font-family inconsistency between 'textarea' and other form elements. 138 | */ 139 | html, 140 | button, 141 | input, 142 | select, 143 | textarea { 144 | font-family: sans-serif; 145 | } 146 | 147 | /* 148 | * Addresses margins handled incorrectly in IE6/7 149 | */ 150 | body { 151 | margin: 0; 152 | } 153 | 154 | /* ============================================================================= 155 | Links 156 | ========================================================================== */ 157 | /* 158 | * Addresses outline displayed oddly in Chrome 159 | */ 160 | a:focus { 161 | outline: thin dotted; 162 | } 163 | 164 | /* 165 | * Improves readability when focused and also mouse hovered in all browsers 166 | * people.opera.com/patrickl/experiments/keyboard/test 167 | */ 168 | a:hover, 169 | a:active { 170 | outline: 0; 171 | } 172 | 173 | /* ============================================================================= 174 | Typography 175 | ========================================================================== */ 176 | /* 177 | * Addresses font sizes and margins set differently in IE6/7 178 | * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 179 | */ 180 | h1 { 181 | font-size: 2em; 182 | margin: 0.67em 0; 183 | } 184 | 185 | h2 { 186 | font-size: 1.5em; 187 | margin: 0.83em 0; 188 | } 189 | 190 | h3 { 191 | font-size: 1.17em; 192 | margin: 1em 0; 193 | } 194 | 195 | h4 { 196 | font-size: 1em; 197 | margin: 1.33em 0; 198 | } 199 | 200 | h5 { 201 | font-size: 0.83em; 202 | margin: 1.67em 0; 203 | } 204 | 205 | h6 { 206 | font-size: 0.75em; 207 | margin: 2.33em 0; 208 | } 209 | 210 | /* 211 | * Addresses styling not present in IE7/8/9, S5, Chrome 212 | */ 213 | abbr[title] { 214 | border-bottom: 1px dotted; 215 | } 216 | 217 | /* 218 | * Addresses style set to 'bolder' in FF3+, S4/5, Chrome 219 | */ 220 | b, 221 | strong { 222 | font-weight: bold; 223 | } 224 | 225 | blockquote { 226 | margin: 1em 40px; 227 | } 228 | 229 | /* 230 | * Addresses styling not present in S5, Chrome 231 | */ 232 | dfn { 233 | font-style: italic; 234 | } 235 | 236 | /* 237 | * Addresses styling not present in IE6/7/8/9 238 | */ 239 | mark { 240 | background: #ff0; 241 | color: #000; 242 | } 243 | 244 | /* 245 | * Addresses margins set differently in IE6/7 246 | */ 247 | p, 248 | pre { 249 | margin: 1em 0; 250 | } 251 | 252 | /* 253 | * Corrects font family set oddly in IE6, S4/5, Chrome 254 | * en.wikipedia.org/wiki/User:Davidgothberg/Test59 255 | */ 256 | pre, 257 | code, 258 | kbd, 259 | samp { 260 | font-family: monospace, serif; 261 | _font-family: 'courier new', monospace; 262 | font-size: 1em; 263 | } 264 | 265 | /* 266 | * 1. Addresses CSS quotes not supported in IE6/7 267 | * 2. Addresses quote property not supported in S4 268 | */ 269 | /* 1 */ 270 | q { 271 | quotes: none; 272 | } 273 | 274 | /* 2 */ 275 | q:before, 276 | q:after { 277 | content: ''; 278 | content: none; 279 | } 280 | 281 | small { 282 | font-size: 75%; 283 | } 284 | 285 | /* 286 | * Prevents sub and sup affecting line-height in all browsers 287 | * gist.github.com/413930 288 | */ 289 | sub, 290 | sup { 291 | font-size: 75%; 292 | line-height: 0; 293 | position: relative; 294 | vertical-align: baseline; 295 | } 296 | 297 | sup { 298 | top: -0.5em; 299 | } 300 | 301 | sub { 302 | bottom: -0.25em; 303 | } 304 | 305 | /* ============================================================================= 306 | Lists 307 | ========================================================================== */ 308 | /* 309 | * Addresses margins set differently in IE6/7 310 | */ 311 | dl, 312 | menu, 313 | ol, 314 | ul { 315 | margin: 1em 0; 316 | } 317 | 318 | dd { 319 | margin: 0 0 0 40px; 320 | } 321 | 322 | /* 323 | * Addresses paddings set differently in IE6/7 324 | */ 325 | menu, 326 | ol, 327 | ul { 328 | padding: 0 0 0 40px; 329 | } 330 | 331 | /* 332 | * Corrects list images handled incorrectly in IE7 333 | */ 334 | nav ul, 335 | nav ol { 336 | list-style: none; 337 | list-style-image: none; 338 | } 339 | 340 | /* ============================================================================= 341 | Embedded content 342 | ========================================================================== */ 343 | /* 344 | * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 345 | * 2. Improves image quality when scaled in IE7 346 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 347 | */ 348 | img { 349 | border: 0; 350 | /* 1 */ 351 | -ms-interpolation-mode: bicubic; 352 | /* 2 */ 353 | } 354 | 355 | /* 356 | * Corrects overflow displayed oddly in IE9 357 | */ 358 | svg:not(:root) { 359 | overflow: hidden; 360 | } 361 | 362 | /* ============================================================================= 363 | Figures 364 | ========================================================================== */ 365 | /* 366 | * Addresses margin not present in IE6/7/8/9, S5, O11 367 | */ 368 | figure { 369 | margin: 0; 370 | } 371 | 372 | /* ============================================================================= 373 | Forms 374 | ========================================================================== */ 375 | /* 376 | * Corrects margin displayed oddly in IE6/7 377 | */ 378 | form { 379 | margin: 0; 380 | } 381 | 382 | /* 383 | * Define consistent border, margin, and padding 384 | */ 385 | fieldset { 386 | border: 1px solid #c0c0c0; 387 | margin: 0 2px; 388 | padding: 0.35em 0.625em 0.75em; 389 | } 390 | 391 | /* 392 | * 1. Corrects color not being inherited in IE6/7/8/9 393 | * 2. Corrects text not wrapping in FF3 394 | * 3. Corrects alignment displayed oddly in IE6/7 395 | */ 396 | legend { 397 | border: 0; 398 | /* 1 */ 399 | padding: 0; 400 | white-space: normal; 401 | /* 2 */ 402 | *margin-left: -7px; 403 | /* 3 */ 404 | } 405 | 406 | /* 407 | * 1. Corrects font size not being inherited in all browsers 408 | * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome 409 | * 3. Improves appearance and consistency in all browsers 410 | */ 411 | button, 412 | input, 413 | select, 414 | textarea { 415 | font-size: 100%; 416 | /* 1 */ 417 | margin: 0; 418 | /* 2 */ 419 | vertical-align: baseline; 420 | /* 3 */ 421 | *vertical-align: middle; 422 | /* 3 */ 423 | } 424 | 425 | /* 426 | * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet 427 | */ 428 | button, 429 | input { 430 | line-height: normal; 431 | /* 1 */ 432 | } 433 | 434 | /* 435 | * 1. Improves usability and consistency of cursor style between image-type 'input' and others 436 | * 2. Corrects inability to style clickable 'input' types in iOS 437 | * 3. Removes inner spacing in IE7 without affecting normal text inputs 438 | * Known issue: inner spacing remains in IE6 439 | */ 440 | button, 441 | input[type="button"], 442 | input[type="reset"], 443 | input[type="submit"] { 444 | cursor: pointer; 445 | /* 1 */ 446 | -webkit-appearance: button; 447 | /* 2 */ 448 | *overflow: visible; 449 | /* 3 */ 450 | } 451 | 452 | /* 453 | * Re-set default cursor for disabled elements 454 | */ 455 | button[disabled], 456 | input[disabled] { 457 | cursor: default; 458 | } 459 | 460 | /* 461 | * 1. Addresses box sizing set to content-box in IE8/9 462 | * 2. Removes excess padding in IE8/9 463 | * 3. Removes excess padding in IE7 464 | Known issue: excess padding remains in IE6 465 | */ 466 | input[type="checkbox"], 467 | input[type="radio"] { 468 | box-sizing: border-box; 469 | /* 1 */ 470 | padding: 0; 471 | /* 2 */ 472 | *height: 13px; 473 | /* 3 */ 474 | *width: 13px; 475 | /* 3 */ 476 | } 477 | 478 | /* 479 | * 1. Addresses appearance set to searchfield in S5, Chrome 480 | * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) 481 | */ 482 | input[type="search"] { 483 | -webkit-appearance: textfield; 484 | /* 1 */ 485 | -moz-box-sizing: content-box; 486 | -webkit-box-sizing: content-box; 487 | /* 2 */ 488 | box-sizing: content-box; 489 | } 490 | 491 | /* 492 | * Removes inner padding and search cancel button in S5, Chrome on OS X 493 | */ 494 | input[type="search"]::-webkit-search-decoration, 495 | input[type="search"]::-webkit-search-cancel-button { 496 | -webkit-appearance: none; 497 | } 498 | 499 | /* 500 | * Removes inner padding and border in FF3+ 501 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 502 | */ 503 | button::-moz-focus-inner, 504 | input::-moz-focus-inner { 505 | border: 0; 506 | padding: 0; 507 | } 508 | 509 | /* 510 | * 1. Removes default vertical scrollbar in IE6/7/8/9 511 | * 2. Improves readability and alignment in all browsers 512 | */ 513 | textarea { 514 | overflow: auto; 515 | /* 1 */ 516 | vertical-align: top; 517 | /* 2 */ 518 | } 519 | 520 | /* ============================================================================= 521 | Tables 522 | ========================================================================== */ 523 | /* 524 | * Remove most spacing between table cells 525 | */ 526 | table { 527 | border-collapse: collapse; 528 | border-spacing: 0; 529 | } 530 | 531 | body { 532 | padding: 0px 0 20px 0px; 533 | margin: 0px; 534 | font: 14px/1.5 "OpenSansRegular", "Helvetica Neue", Helvetica, Arial, sans-serif; 535 | color: #f0e7d5; 536 | font-weight: normal; 537 | background: #252525; 538 | background-attachment: fixed !important; 539 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2a2a29), color-stop(100%, #1c1c1c)); 540 | background: -webkit-linear-gradient(#2a2a29, #1c1c1c); 541 | background: -moz-linear-gradient(#2a2a29, #1c1c1c); 542 | background: -o-linear-gradient(#2a2a29, #1c1c1c); 543 | background: -ms-linear-gradient(#2a2a29, #1c1c1c); 544 | background: linear-gradient(#2a2a29, #1c1c1c); 545 | } 546 | 547 | h1, h2, h3, h4, h5, h6 { 548 | color: #e8e8e8; 549 | margin: 0 0 10px; 550 | font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif; 551 | font-weight: normal; 552 | } 553 | 554 | p, ul, ol, table, pre, dl { 555 | margin: 0 0 20px; 556 | } 557 | 558 | h1, h2, h3 { 559 | line-height: 1.1; 560 | } 561 | 562 | h1 { 563 | font-size: 28px; 564 | } 565 | 566 | h2 { 567 | font-size: 24px; 568 | } 569 | 570 | h4, h5, h6 { 571 | color: #e8e8e8; 572 | } 573 | 574 | h3 { 575 | font-size: 18px; 576 | line-height: 24px; 577 | font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif !important; 578 | font-weight: normal; 579 | color: #b6b6b6; 580 | } 581 | 582 | a { 583 | color: #ffcc00; 584 | font-weight: 400; 585 | text-decoration: none; 586 | } 587 | a:hover { 588 | color: #ffeb9b; 589 | } 590 | 591 | a small { 592 | font-size: 11px; 593 | color: #666; 594 | margin-top: -0.6em; 595 | display: block; 596 | } 597 | 598 | ul { 599 | list-style-image: url("../images/bullet.png"); 600 | } 601 | 602 | strong { 603 | font-family: 'OpenSansBold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; 604 | font-weight: normal; 605 | } 606 | 607 | .wrapper { 608 | max-width: 650px; 609 | margin: 0 auto; 610 | position: relative; 611 | padding: 0 20px; 612 | } 613 | 614 | section img { 615 | max-width: 100%; 616 | } 617 | 618 | blockquote { 619 | border-left: 3px solid #ffcc00; 620 | margin: 0; 621 | padding: 0 0 0 20px; 622 | font-style: italic; 623 | } 624 | 625 | code { 626 | font-family: "Lucida Sans", Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; 627 | color: #efefef; 628 | font-size: 13px; 629 | margin: 0 4px; 630 | padding: 4px 6px; 631 | -moz-border-radius: 2px; 632 | -webkit-border-radius: 2px; 633 | -o-border-radius: 2px; 634 | -ms-border-radius: 2px; 635 | -khtml-border-radius: 2px; 636 | border-radius: 2px; 637 | } 638 | 639 | pre { 640 | padding: 8px 15px; 641 | background: #191919; 642 | -moz-border-radius: 2px; 643 | -webkit-border-radius: 2px; 644 | -o-border-radius: 2px; 645 | -ms-border-radius: 2px; 646 | -khtml-border-radius: 2px; 647 | border-radius: 2px; 648 | border: 1px solid #121212; 649 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 650 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 651 | -o-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 652 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 653 | overflow: auto; 654 | overflow-y: hidden; 655 | } 656 | pre code { 657 | color: #efefef; 658 | text-shadow: 0px 1px 0px #000; 659 | margin: 0; 660 | padding: 0; 661 | } 662 | 663 | table { 664 | width: 100%; 665 | border-collapse: collapse; 666 | } 667 | 668 | th { 669 | text-align: left; 670 | padding: 5px 10px; 671 | border-bottom: 1px solid #434343; 672 | color: #b6b6b6; 673 | font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; 674 | font-weight: normal; 675 | } 676 | 677 | td { 678 | text-align: left; 679 | padding: 5px 10px; 680 | border-bottom: 1px solid #434343; 681 | } 682 | 683 | hr { 684 | border: 0; 685 | outline: none; 686 | height: 3px; 687 | background: transparent url("../images/hr.gif") center center repeat-x; 688 | margin: 0 0 20px; 689 | } 690 | 691 | dt { 692 | color: #F0E7D5; 693 | font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; 694 | font-weight: normal; 695 | } 696 | 697 | #header { 698 | z-index: 100; 699 | left: 0; 700 | top: 0px; 701 | height: 60px; 702 | width: 100%; 703 | position: fixed; 704 | background: url(../images/nav-bg.gif) #353535; 705 | border-bottom: 4px solid #434343; 706 | -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 707 | -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 708 | -o-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 709 | box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 710 | } 711 | #header nav { 712 | max-width: 650px; 713 | margin: 0 auto; 714 | padding: 0 10px; 715 | background: blue; 716 | margin: 6px auto; 717 | } 718 | #header nav li { 719 | font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; 720 | font-weight: normal; 721 | list-style: none; 722 | display: inline; 723 | color: white; 724 | line-height: 50px; 725 | text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.2); 726 | font-size: 14px; 727 | } 728 | #header nav li a { 729 | color: white; 730 | border: 1px solid #5d910b; 731 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #93bd20), color-stop(100%, #659e10)); 732 | background: -webkit-linear-gradient(#93bd20, #659e10); 733 | background: -moz-linear-gradient(#93bd20, #659e10); 734 | background: -o-linear-gradient(#93bd20, #659e10); 735 | background: -ms-linear-gradient(#93bd20, #659e10); 736 | background: linear-gradient(#93bd20, #659e10); 737 | -moz-border-radius: 2px; 738 | -webkit-border-radius: 2px; 739 | -o-border-radius: 2px; 740 | -ms-border-radius: 2px; 741 | -khtml-border-radius: 2px; 742 | border-radius: 2px; 743 | -moz-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); 744 | -webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); 745 | -o-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); 746 | box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); 747 | background-color: #93bd20; 748 | padding: 10px 12px; 749 | margin-top: 6px; 750 | line-height: 14px; 751 | font-size: 14px; 752 | display: inline-block; 753 | text-align: center; 754 | } 755 | #header nav li a:hover { 756 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #749619), color-stop(100%, #527f0e)); 757 | background: -webkit-linear-gradient(#749619, #527f0e); 758 | background: -moz-linear-gradient(#749619, #527f0e); 759 | background: -o-linear-gradient(#749619, #527f0e); 760 | background: -ms-linear-gradient(#749619, #527f0e); 761 | background: linear-gradient(#749619, #527f0e); 762 | background-color: #659e10; 763 | border: 1px solid #527f0e; 764 | -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); 765 | -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); 766 | -o-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); 767 | box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); 768 | } 769 | #header nav li.fork { 770 | float: left; 771 | margin-left: 0px; 772 | } 773 | #header nav li.downloads { 774 | float: right; 775 | margin-left: 6px; 776 | } 777 | #header nav li.title { 778 | float: right; 779 | margin-right: 10px; 780 | font-size: 11px; 781 | } 782 | 783 | section { 784 | max-width: 650px; 785 | padding: 30px 0px 50px 0px; 786 | margin: 20px 0; 787 | margin-top: 70px; 788 | } 789 | section #title { 790 | border: 0; 791 | outline: none; 792 | margin: 0 0 50px 0; 793 | padding: 0 0 5px 0; 794 | } 795 | section #title h1 { 796 | font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; 797 | font-weight: normal; 798 | font-size: 40px; 799 | text-align: center; 800 | line-height: 36px; 801 | } 802 | section #title p { 803 | color: #d7cfbe; 804 | font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; 805 | font-weight: normal; 806 | font-size: 18px; 807 | text-align: center; 808 | } 809 | section #title .credits { 810 | font-size: 11px; 811 | font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif; 812 | font-weight: normal; 813 | color: #696969; 814 | margin-top: -10px; 815 | } 816 | section #title .credits.left { 817 | float: left; 818 | } 819 | section #title .credits.right { 820 | float: right; 821 | } 822 | 823 | @media print, screen and (max-width: 720px) { 824 | #title .credits { 825 | display: block; 826 | width: 100%; 827 | line-height: 30px; 828 | text-align: center; 829 | } 830 | #title .credits .left { 831 | float: none; 832 | display: block; 833 | } 834 | #title .credits .right { 835 | float: none; 836 | display: block; 837 | } 838 | } 839 | @media print, screen and (max-width: 480px) { 840 | #header { 841 | margin-top: -20px; 842 | } 843 | 844 | section { 845 | margin-top: 40px; 846 | } 847 | 848 | nav { 849 | display: none; 850 | } 851 | } 852 | --------------------------------------------------------------------------------