\n \u003Cinput type=\"text\" name=\"superpower\" />\n \u003Cbutton type=\"submit\" is=\"ajax-submit\" data-target=\"hero-container\">\n add new item\n \u003C/button>\n\u003C/form>\n\n\u003Cdiv id=\"hero-container\">\u003C/div>\n","html",[405,498,499,533,560,581,613,619,629,639,645],{"__ignoreMap":34},[500,501,504,508,511,515,518,522,525,527,530],"span",{"class":502,"line":503},"line",1,[500,505,507],{"class":506},"sVt8B","\u003C",[500,509,407],{"class":510},"s9eBZ",[500,512,514],{"class":513},"sScJk"," action",[500,516,517],{"class":506},"=",[500,519,521],{"class":520},"sZZnC","\"/heroes\"",[500,523,524],{"class":513}," method",[500,526,517],{"class":506},[500,528,529],{"class":520},"\"post\"",[500,531,532],{"class":506},">\n",[500,534,535,538,541,544,546,549,552,554,557],{"class":502,"line":98},[500,536,537],{"class":506}," \u003C",[500,539,540],{"class":510},"input",[500,542,543],{"class":513}," type",[500,545,517],{"class":506},[500,547,548],{"class":520},"\"text\"",[500,550,551],{"class":513}," name",[500,553,517],{"class":506},[500,555,556],{"class":520},"\"hero\"",[500,558,559],{"class":506}," />\n",[500,561,562,564,566,568,570,572,574,576,579],{"class":502,"line":249},[500,563,537],{"class":506},[500,565,540],{"class":510},[500,567,543],{"class":513},[500,569,517],{"class":506},[500,571,548],{"class":520},[500,573,551],{"class":513},[500,575,517],{"class":506},[500,577,578],{"class":520},"\"superpower\"",[500,580,559],{"class":506},[500,582,584,586,588,590,592,595,598,600,603,606,608,611],{"class":502,"line":583},4,[500,585,537],{"class":506},[500,587,485],{"class":510},[500,589,543],{"class":513},[500,591,517],{"class":506},[500,593,594],{"class":520},"\"submit\"",[500,596,597],{"class":513}," is",[500,599,517],{"class":506},[500,601,602],{"class":520},"\"ajax-submit\"",[500,604,605],{"class":513}," data-target",[500,607,517],{"class":506},[500,609,610],{"class":520},"\"hero-container\"",[500,612,532],{"class":506},[500,614,616],{"class":502,"line":615},5,[500,617,618],{"class":506}," add new item\n",[500,620,622,625,627],{"class":502,"line":621},6,[500,623,624],{"class":506}," \u003C/",[500,626,485],{"class":510},[500,628,532],{"class":506},[500,630,632,635,637],{"class":502,"line":631},7,[500,633,634],{"class":506},"\u003C/",[500,636,407],{"class":510},[500,638,532],{"class":506},[500,640,642],{"class":502,"line":641},8,[500,643,644],{"emptyLinePlaceholder":107},"\n",[500,646,648,650,652,655,657,659,662,664],{"class":502,"line":647},9,[500,649,507],{"class":506},[500,651,481],{"class":510},[500,653,654],{"class":513}," id",[500,656,517],{"class":506},[500,658,610],{"class":520},[500,660,661],{"class":506},">\u003C/",[500,663,481],{"class":510},[500,665,532],{"class":506},[492,667,671],{"className":668,"code":669,"language":670,"meta":34,"style":34},"language-javascript shiki shiki-themes github-light github-dark","class AjaxSubmitButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", async (event) => {\n event.preventDefault();\n let html = await ajaxFormSubmit();\n document.getElementById(this.dataset.target).innerHTML = html;\n });\n }\n}\n\ncustomElements.define(\"ajax-submit\", AjaxSubmitButton, {\n extends: \"button\",\n});\n","javascript",[405,672,673,691,699,738,749,767,788,793,798,803,808,824,836],{"__ignoreMap":34},[500,674,675,679,682,685,688],{"class":502,"line":503},[500,676,678],{"class":677},"szBVR","class",[500,680,681],{"class":513}," AjaxSubmitButton",[500,683,684],{"class":677}," extends",[500,686,687],{"class":513}," HTMLButtonElement",[500,689,690],{"class":506}," {\n",[500,692,693,696],{"class":502,"line":98},[500,694,695],{"class":513}," connectedCallback",[500,697,698],{"class":506},"() {\n",[500,700,701,705,708,711,714,717,720,723,726,730,733,736],{"class":502,"line":249},[500,702,704],{"class":703},"sj4cs"," this",[500,706,707],{"class":506},".",[500,709,710],{"class":513},"addEventListener",[500,712,713],{"class":506},"(",[500,715,716],{"class":520},"\"click\"",[500,718,719],{"class":506},", ",[500,721,722],{"class":677},"async",[500,724,725],{"class":506}," (",[500,727,729],{"class":728},"s4XuR","event",[500,731,732],{"class":506},") ",[500,734,735],{"class":677},"=>",[500,737,690],{"class":506},[500,739,740,743,746],{"class":502,"line":583},[500,741,742],{"class":506}," event.",[500,744,745],{"class":513},"preventDefault",[500,747,748],{"class":506},"();\n",[500,750,751,754,757,759,762,765],{"class":502,"line":615},[500,752,753],{"class":677}," let",[500,755,756],{"class":506}," html ",[500,758,517],{"class":677},[500,760,761],{"class":677}," await",[500,763,764],{"class":513}," ajaxFormSubmit",[500,766,748],{"class":506},[500,768,769,772,775,777,780,783,785],{"class":502,"line":621},[500,770,771],{"class":506}," document.",[500,773,774],{"class":513},"getElementById",[500,776,713],{"class":506},[500,778,779],{"class":703},"this",[500,781,782],{"class":506},".dataset.target).innerHTML ",[500,784,517],{"class":677},[500,786,787],{"class":506}," html;\n",[500,789,790],{"class":502,"line":631},[500,791,792],{"class":506}," });\n",[500,794,795],{"class":502,"line":641},[500,796,797],{"class":506}," }\n",[500,799,800],{"class":502,"line":647},[500,801,802],{"class":506},"}\n",[500,804,806],{"class":502,"line":805},10,[500,807,644],{"emptyLinePlaceholder":107},[500,809,811,814,817,819,821],{"class":502,"line":810},11,[500,812,813],{"class":506},"customElements.",[500,815,816],{"class":513},"define",[500,818,713],{"class":506},[500,820,602],{"class":520},[500,822,823],{"class":506},", AjaxSubmitButton, {\n",[500,825,827,830,833],{"class":502,"line":826},12,[500,828,829],{"class":506}," extends: ",[500,831,832],{"class":520},"\"button\"",[500,834,835],{"class":506},",\n",[500,837,839],{"class":502,"line":838},13,[500,840,841],{"class":506},"});\n",[18,843,844,845,848],{},"Der Vorteil des Custom-Elements gegenüber jQuery (oder sonstiger eigener Implementierung) ist, dass der Browser sich\ndarum kümmert, unser JavaScript mit dem HTML zu verbinden und eine Instanz des ",[405,846,847],{},"AjaxSubmitButtons"," zu erstellen.",[18,850,851,852,855,856,861],{},"Kommen zur Laufzeit weitere ",[405,853,854],{},"submit"," Buttons mit diesem Attribut hinzu, werden sie vom Browser automatisch mit unserem\ngewünschten Verhalten erweitert. Ist das JavaScript aus irgendwelchen Gründen nicht verfügbar funktioniert weiterhin das\ngute alte HTML Formular mit komplettem Seitenreload. Wir verbessern unsere Anwendung mit jedem weiteren\nTechnologie-Layer, der zur Verfügung\nsteht, ",[22,857,860],{"href":858,"rel":859},"https://developer.mozilla.org/de/docs/Glossary/Progressive_Enhancement",[26],"Progressive Enhancement"," genannt. HTML\nbeschreibt den Inhalt, CSS macht es bunt, und zu guter Letzt verbessern wir die Benutzererfahrung mit JavaScript.",[18,863,864],{},"Das Backend wird mit diesem Ansatz auch nicht komplizierter oder gar komplexer. Im Controller müssen wir erkennen, dass\nes sich um einen Ajax Request handelt. In dem Fall wird ein HTML Fragment gerendert, andernfalls wird die komplette\nSeite gerendert:",[492,866,870],{"className":867,"code":868,"language":869,"meta":34,"style":34},"language-java shiki shiki-themes github-light github-dark","@PostMapping(value = \"/heroes\")\npublic String addSuperhero(\n @RequestParam String hero,\n @RequestParam String superpower,\n @RequestHeader(name = \"X-Requested-With\", defaultValue = \"\") String requestedWith,\n Model model\n ) {\n\n model.addAttribute(\"hero\", hero);\n model.addAttribute(\"superpower\", superpower);\n\n if (\"ajax\".equals(requestedWith)) {\n return \"fragments/hero-fragment :: hero-fragment\";\n }\n\n return \"full-page-including-the-hero-fragment\";\n}\n\n","java",[405,871,872,877,882,887,892,897,902,907,911,916,921,925,930,935,941,946,952],{"__ignoreMap":34},[500,873,874],{"class":502,"line":503},[500,875,876],{},"@PostMapping(value = \"/heroes\")\n",[500,878,879],{"class":502,"line":98},[500,880,881],{},"public String addSuperhero(\n",[500,883,884],{"class":502,"line":249},[500,885,886],{}," @RequestParam String hero,\n",[500,888,889],{"class":502,"line":583},[500,890,891],{}," @RequestParam String superpower,\n",[500,893,894],{"class":502,"line":615},[500,895,896],{}," @RequestHeader(name = \"X-Requested-With\", defaultValue = \"\") String requestedWith,\n",[500,898,899],{"class":502,"line":621},[500,900,901],{}," Model model\n",[500,903,904],{"class":502,"line":631},[500,905,906],{}," ) {\n",[500,908,909],{"class":502,"line":641},[500,910,644],{"emptyLinePlaceholder":107},[500,912,913],{"class":502,"line":647},[500,914,915],{}," model.addAttribute(\"hero\", hero);\n",[500,917,918],{"class":502,"line":805},[500,919,920],{}," model.addAttribute(\"superpower\", superpower);\n",[500,922,923],{"class":502,"line":810},[500,924,644],{"emptyLinePlaceholder":107},[500,926,927],{"class":502,"line":826},[500,928,929],{}," if (\"ajax\".equals(requestedWith)) {\n",[500,931,932],{"class":502,"line":838},[500,933,934],{}," return \"fragments/hero-fragment :: hero-fragment\";\n",[500,936,938],{"class":502,"line":937},14,[500,939,940],{}," }\n",[500,942,944],{"class":502,"line":943},15,[500,945,644],{"emptyLinePlaceholder":107},[500,947,949],{"class":502,"line":948},16,[500,950,951],{}," return \"full-page-including-the-hero-fragment\";\n",[500,953,955],{"class":502,"line":954},17,[500,956,802],{},[335,958,960],{"id":959},"auf-dem-weg-zur-single-page-application","Auf dem Weg zur Single Page Application",[18,962,963],{},"Mittlerweile haben wir die ajax-submit Komponente in unserer Anwendung an sehr vielen Stellen im Einsatz. Bisher hat\nsie sich als robust und einfach bewährt. Jeder im Team hat verstanden wie man es einsetzt und wie es funktioniert. Wir\nbrauchen keine Runtime a la ReactJS, wir müssen Hooks nicht verstehen, wir benötigen kein komplexes Build Setup. Wir\nmüssen nicht überlegen wie wir vue Komponenten integrieren und wir müssen uns keine Gedanken bzgl Client Side only\nFunktionalität machen.",[18,965,966],{},"Kurz gesagt: Wir bauen unser Frontend ohne modernes JavaScript Framework und sind (trotzdem) glücklich.",[18,968,969],{},"Zugegeben, die Progressive Enhancement Denkweise ist herausfordernd. Daran zu denken und zu überlegen, wie Anforderungen\nohne JavaScript gelöst werden können, hat unsere Industrie vielleicht verlernt? In der Vergangenheit haben mich\nKollegen, die überwiegend im Backend unterwegs sind gefragt, wie man denn heutzutage ein Frontend baut. Nehme man da\nAngular oder React? Über Anforderungen war man sich da aber noch nicht bewusst… Es war zumindest nicht die Einleitung\nzur Technologie Frage.",[133,971,973],{"id":972},"herausforderungen-die-kommen-könnten","Herausforderungen die kommen (könnten)",[343,975,976],{},[346,977,978],{},[469,979,980],{},"Progressive Enhancement Denkweise",[18,982,983],{},"Sobald unsere Anwendung einen gewissen Charakter moderner Single Page Applications erreicht hat, wird das natürlich der\nDefault werden (im Geiste). Hier gilt es weiterhin an die Basis zu denken und nicht sofort an JavaScript only Lösungen.",[343,985,986],{},[346,987,988],{},[469,989,990],{},"History Handling",[18,992,993,994,999],{},"Vor und Zurück nach bestimmten Aktionen ist bisher keine Anforderung. Sollte diese Anforderung kommen ist das aber auch\nkein Hexenwerk. Nach JavaScript Aktionen die URL mit\nder ",[22,995,998],{"href":996,"rel":997},"https://developer.mozilla.org/en-US/docs/Web/API/History",[26],"history API"," ändern ist im Bereich des Möglichen 😉",[343,1001,1002],{},[346,1003,1004],{},[469,1005,1006],{},"State",[18,1008,1009,1010,1015],{},"Nach bestimmten Aktionen den State an mehreren Stellen im Browser aktualisieren. Hier muss denke ich abewägt werden, ob\nBibiliotheken eine Daseinsberechtigung bekommen, oder\nob ",[22,1011,1014],{"href":1012,"rel":1013},"https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent",[26],"Custom Events"," ausreichen.",[1017,1018,1019],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":34,"searchDepth":98,"depth":98,"links":1021},[1022,1023,1024,1025],{"id":337,"depth":98,"text":338},{"id":383,"depth":98,"text":384},{"id":457,"depth":98,"text":448},{"id":959,"depth":98,"text":960,"children":1026},[1027],{"id":972,"depth":249,"text":973},[1029,256],"developer-blog","2020-06-23T11:20:03","Mit einigen Jahren JavaScript und Reactjs Erfahrung durfte ich Ende letzten Jahres (November 2019) Teil eines neuen\\nTeams und eines neuen Projektes werden. Das Projekt ist ein Traumprojekt jeden Entwicklers. Ein grüne Wiese Projekt mit\\n“freier” Technologiewahl. “Frei” in Form von man darf sich die Zeit für eine Risikoanalyse nehmen und moderne Tools und\\nFrameworks evaluieren.","https://synyx.de/blog/frameworkless-frontend-und-trotzdem-gluecklich/",{},"/blog/frameworkless-frontend-und-trotzdem-gluecklich",{"title":273,"description":283},"frameworkless-frontend-und-trotzdem-gluecklich","blog/frameworkless-frontend-und-trotzdem-gluecklich",[1039,670,1040],"development","progressive-enhancement","Mit einigen Jahren JavaScript und Reactjs Erfahrung durfte ich Ende letzten Jahres (November 2019) Teil eines neuen Teams und eines neuen Projektes werden. Das Projekt ist ein Traumprojekt jeden Entwicklers. Ein grüne Wiese Projekt mit 'freier' Technologiewahl. 'Frei' in Form von man darf sich die Zeit für eine Risikoanalyse nehmen und moderne Tools und Frameworks evaluieren.","JGt7bZJ_gokf64kVf7XrDHMaEJ2JdAYkb3HTHT7LZEw",{"id":1044,"title":1045,"author":1046,"body":1048,"category":1173,"date":1175,"description":1055,"extension":104,"link":1176,"meta":1177,"navigation":107,"path":1178,"seo":1179,"slug":1052,"stem":1180,"tags":1181,"teaser":1183,"__hash__":1184},"blog/blog/ist-das-jetzt-urlaub-oder-arbeit-ein-typischer-tag-auf-der-jcrete.md","Ist das jetzt Urlaub oder Arbeit?! – Ein typischer Tag auf der JCrete",[1047],"jayasinghe",{"type":11,"value":1049,"toc":1171},[1050,1053,1056,1082,1088,1097,1100,1103,1109,1112,1115,1118,1121,1124,1127,1133,1136,1142,1157,1160,1168],[14,1051,1045],{"id":1052},"ist-das-jetzt-urlaub-oder-arbeit-ein-typischer-tag-auf-der-jcrete",[18,1054,1055],{},"Zuallererst möchte ich mich bedanken:",[343,1057,1058,1064,1070,1076],{},[346,1059,1060,1063],{},[469,1061,1062],{},"Bei der synyx:"," für ein modernes, eigenverantwortliches Weiterbildungskonzept, das es mir ermöglicht ohne große\nDiskussionen an so einer Veranstaltung teilzunehmen!",[346,1065,1066,1069],{},[469,1067,1068],{},"Bei den Disorganizern der JCrete",": dafür, dass ich dabei sein durfte!",[346,1071,1072,1075],{},[469,1073,1074],{},"Bei meinen Kindern",": weil sie so super mitgemacht haben und nicht verloren gegangen sind!",[346,1077,1078,1081],{},[469,1079,1080],{},"Bei meiner Frau:"," weil sie darauf vertraut hat, dass mir die Kinder nicht verloren gehen! 😉",[18,1083,1084],{},[32,1085],{"alt":1086,"src":1087},"jCrete: Dinner mit Meerblick","https://media.synyx.de/uploads/2019/07/IMG_20190715_075755-768x576.jpg",[18,1089,1090,1091,1096],{},"Wenn man als Unterkunft eins der Zimmer in der OAC (",[22,1092,1095],{"href":1093,"rel":1094},"https://www.oac.gr/en/",[26],"Orthodox Academy of Crete"," ) gebucht hat,\nwacht man jeden Morgen mit einer gigantischen Aussicht direkt aufs Meer auf. Die allgegenwärtigen Zikaden schlafen noch\nund man hört das Meer rauschen. Um 6:30 warten auf dem Hof vor dem Zimmer die zwei Läufergruppen. Egal bei wie viel Raki\nund Bier man den Abend zuvor über die Java-Welt philosophiert hat, Frühsport muss sein. Eine schnelle Gruppe mit 5er\nSchnitt und eine gemütliche Gruppe mit 7-8er Schnitt erklimmt die Hügel hinter der Akademie. Danach kann man direkt ins\nMeer springen und sich abkühlen.",[18,1098,1099],{},"Ich habe diesmal Kinder dabei und schaue den Läufern vom Balkon aus zu. 😉",[18,1101,1102],{},"Beim Frühstück sitze ich mit zum Teil mir unbekannten oder halt auch sehr prominenten Menschen aus der Java Community\nzusammen. Schon vor dem ersten Schluck Kaffe entwickeln sich coole Diskussionen. Kurz noch Brötchen für die Kinder auf\ndem Zimmer schmieren und dann geht’s schon los mit Konferenzprogramm.",[18,1104,1105],{},[32,1106],{"alt":1107,"src":1108},"jCrete: Beach-Programm-Slot","https://media.synyx.de/uploads/2019/07/Image-from-iOS-768x576.jpg",[18,1110,1111],{},"In die erste Session “Rust for Java Programmers” stolpere ich mehr aus Zufall. Da ich eh mal was über Rust lernen\nwollte, bleibe ich sitzen und lerne von Alex Snaps und Ben Harper einiges über die Programmiersprache von Mozilla.\nSpeichersicherheit und Performance spielen eine große Rolle. Was ich mitnehme: Rust kann und sollte man für Dinge\neinsetzen, die man sonst in C oder C++ schreiben würde. Also nah an der Hardware oder absolut Performance-kritisch.\nFür die Entwicklung von Webapplikationen oder Services gibt es aber geeignetere Sprachen.",[18,1113,1114],{},"In der Pause inhaliere ich zwei Becher Kaffe und schaue nach den Kindern. Die spielen inzwischen mit Kindern der anderen\nTeilnehmern auf dem Hof zwischen den beiden Hauptgebäuden und überwinden dabei jegliche Sprachbarrieren.",[18,1116,1117],{},"Die zweite Session findet in der großen Halle statt. Marcus Hirt stellt Java Mission Control und Flight Recorder vor.\nWir diskutieren die gemachten Erfahrungen sowie die Stärken und Schwächen der beiden Tools. Spannede und für mich neue\nInfo war, dass beide Tools ab OpenJDK 11 Opensource und damit frei nutzbar sind. Das sind IMHO großartige Neuigkeiten\nund bieten für uns viele neue Möglichkeiten zur Überwachung und Analyse unserer Produktiv-Applikationen. Zumindest\nsofern sie schon auf JDK 11 laufen. Es gab allerdings auch Andeutungen, dass ein Downport für OpenJDK 8 in Arbeit ist.",[18,1119,1120],{},"In der Pause wieder Kaffe, kurze Gespräche mit anderen Teilnehmern und Durchzählen der Kinder. 1 .. 2, alle noch da und\nnicht verhungert.",[18,1122,1123],{},"Die dritte Session dreht sich um das Testen von Microservices. Das war bereits im letzten Jahr ein großes Thema. Es wird\nlebhaft über die Testpyramide und den Aufwand, den man in unterschiedliche Arten von Tests stecken muss oder will,\ndiskutiert. Viele Teams, die größere Microservice Projekte umsetzen, scheinen stark mit dem “ich fahre gerade mal alles\nauf meinem Minikube hoch und teste dann” zu kämpfen. Es ist einfach ein nicht zu unterschätzender Aufwand. Ich erzähle\nein wenig, wie wir bei unserem Kunden Contargo entwickeln: SCS anstatt Microservices, bewusster Schnitt der Kontexte und\nlose Kopplung zwischen diesen. Viele Probleme, die diskutiert werden, ergeben sich überhaupt nicht für uns. Der richtige\nSchnitt der Kontexte kann so viel Arbeit sparen!",[18,1125,1126],{},"Nach den Sessions treffen wir uns nochmal alle in der großen Halle und wir sprechen das restliche Tagesprogramm aka\nAusflüge durch. Ich sammle meine Kinder ein und es geht los Richtung Mittagessen.",[18,1128,1129],{},[32,1130],{"alt":1131,"src":1132},"Foodporn bei der jCrete","https://media.synyx.de/uploads/2019/07/IMG_20190718_130038-768x576.jpg",[18,1134,1135],{},"Das Mittagsessen findet wie jeden Tag auf der großen, mit Segeltuch überspannten Dachterrasse der Akademie statt. Es\ngibt überwiegend vegetarische, super leckere Spezialitäten aus Kreta. Wer will, kann sich griechischen Wein genehmigen.\n😉 Während meine Kinder über das griechische Essen jammern und lieber den Nachtisch-Kuchen als Hauptmahlzeit nehmen,\ndiskutiere ich mit Sébastien Blanc, dem DevRel-Menschen aus dem Keycloak Team, über die Erfahrungen, die wir bei synyx\nund unseren Kunden mit Keycloak machen.",[18,1137,1138],{},[32,1139],{"alt":1140,"src":1141},"Foodtester Vivian war begeistert!","https://media.synyx.de/uploads/2019/07/IMG_20190715_202536-768x576.jpg",[18,1143,1144,1145,1150,1151,1156],{},"Den Nachmittag verbringen wir in einem Wasserpark in der Nähe von Chania. Da meine Kinder gerne Wasserrutschen rutschen,\nwar das auf jeden Fall ein Pflichttermin. Auch andere Teilnehmer hatten sich hier mit ihren Kindern eingefunden. Fotos\ngibt es keine, weil ich alle Hände damit zu tun hatte, dass die Kinder nicht verloren gehen oder ertrinken. Am Abend\nhatten die Disorganizers zwei Tavernen für alle Teilnehmer reserviert.\nDie",[22,1146,1149],{"href":1147,"rel":1148},"https://www.tripadvisor.com/Restaurant_Review-g1191160-d4605253-Reviews-Panorama_Tavern_Falasarna-Falassarna_Chania_Prefecture_Crete.html",[26],"Panorama Tavern in Falassarna","\nund\ndie ",[22,1152,1155],{"href":1153,"rel":1154},"https://www.tripadvisor.com/Restaurant_Review-g1028265-d12957049-Reviews-Fish_Tavern_1960-Kissamos_Chania_Prefecture_Crete.html",[26],"1960 Fish Tavern in Kissamos",".\nWir haben die Fisch-Taverne in Kissamos gewählt und haben uns in einem dreistündigen Seafood/Veggie DoS-Angriff\nwiedergefunden. Gegen 23 Uhr war der Tag dann vorbei. Genug erlebt.",[18,1158,1159],{},"So, das war nun nur einer von insgesamt 5 Konferenz-Tagen. 😉 Zum Abschluss noch ein Video der JCrete vom letzten Jahr.\nDort hatte uns ein Profi-Filmteam begleitet und super Aufnahmen gemacht.",[18,1161,1162,1167],{},[22,1163,1166],{"href":1164,"rel":1165},"https://youtu.be/6-zF8JrWlt4",[26],"Video auf YouTube ansehen"," (Mit dem Laden des Videos akzeptieren Sie die\nDatenschutzerklärung von YouTube.)",[18,1169,1170],{},"Heinz und Kirk erklären, wie die JCrete geboren wurde und was diese Unconference so besonders macht.",{"title":34,"searchDepth":98,"depth":98,"links":1172},[],[1174,101,256],"barcamp","2019-07-25T12:02:20","https://synyx.de/blog/ist-das-jetzt-urlaub-oder-arbeit-ein-typischer-tag-auf-der-jcrete/",{},"/blog/ist-das-jetzt-urlaub-oder-arbeit-ein-typischer-tag-auf-der-jcrete",{"title":1045,"description":1055},"blog/ist-das-jetzt-urlaub-oder-arbeit-ein-typischer-tag-auf-der-jcrete",[1182],"java-unconference-kreta-crete","Die JCrete ist eine, wenn nicht *die* Unconference im Java-Umfeld. Initiiert von Heinz Kabutz, Kirk Pepperdine und einem Team von Dis-Organizern, findet die Unconference seit 2011 jedes Jahr auf der griechischen Insel Kreta statt. Ich hatte die Ehre, dieses Jahr schon schon zum zweiten Mal dabei zu sein und beschreibe an dieser Stelle einen typischen Tag auf der JCrete.","EBvddf8HZccRoGUlG2DNdn17lwdnhRHgEjqYb02L0Yg",{"id":1186,"title":1187,"author":1188,"body":1190,"category":1248,"date":1249,"description":1197,"extension":104,"link":1250,"meta":1251,"navigation":107,"path":1252,"seo":1253,"slug":1254,"stem":1255,"tags":1256,"teaser":1261,"__hash__":1262},"blog/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen.md","Experiment JavaScript – Ein synyx Entwickler erzählt von seinen Anfängen",[1189],"lange",{"type":11,"value":1191,"toc":1243},[1192,1195,1198,1202,1209,1212,1215,1218,1222,1225,1228,1232,1235],[14,1193,1187],{"id":1194},"experiment-javascript-ein-synyx-entwickler-erzählt-von-seinen-anfängen",[18,1196,1197],{},"Alles ging vor knapp drei Jahren mit einer ganz harmlosen Frage los:",[335,1199,1201],{"id":1200},"kannst-du-dir-vorstellen-in-einem-javascript-projekt-zu-arbeiten","„Kannst Du Dir vorstellen, in einem JavaScript-Projekt zu arbeiten?“",[18,1203,1204,1205,1208],{},"Das war der Abschluss meines Bewerbungsgesprächs bei synyx. Lässt man die üblichen Website-Spielereien außer Acht, hatte ich zu diesem Zeitpunkt noch keine wirklichen Erfahrungen mit JavaScript vorzuweisen. Damals hätte ich es vermutlich nicht einmal als ",[168,1206,1207],{},"richtige"," Programmiersprache bezeichnet. Doch mein Interesse war geweckt und ich ließ mich auf das Experiment JavaScript ein.",[18,1210,1211],{},"Dann ging der für mich anfangs beschwerliche Weg los. Ich komme aus der klassischen Java-Welt und JavaScript ist doch ein bisschen anders als Java. Typsicherheit und einen Compiler habe ich vor allem anfangs sehr vermisst, aber mich auch über das direkte Ausführen von kleinen Fragmenten gefreut. JavaScript lässt sich einfach interaktiver / schneller entwickeln. Das macht Spaß, wenn man sich darauf einlässt. Die Sprache gibt einem viele Freiheiten, dynamisches Überschreiben von Methoden zum Beispiel.",[18,1213,1214],{},"Ach ja, Methoden – ein gutes Thema. Neben der Single-Threaded-Natur ist die prototypische Vererbung doch recht gewöhnungsbedürftig. Aber man sollte ja sowieso eher delegieren als vererben 😉 Und durch das Ducktyping sind Vererbungen auch gar nicht so wichtig. Die Sprache hatte für mich definitiv viele Eigenheiten, die ich mir erst nach und nach erarbeiten musste. Toll war, dass ich damit nicht allein im Team war.",[18,1216,1217],{},"Oh ja, das Team. Es ist klasse, wie offen wir über Probleme sprechen und gemeinsam nach Lösungen suchen können. Die verschiedenen Charaktere helfen dabei, zu guten Ergebnissen zu gelangen.",[335,1219,1221],{"id":1220},"eine-interessante-sprache-und-ein-tolles-team-aber-was-machen-wir-da-eigentlich","Eine interessante Sprache und ein tolles Team, aber was machen wir da eigentlich?",[18,1223,1224],{},"Natürlich einem großen Kunden gute Software abliefern. Wobei so natürlich ist es eigentlich gar nicht. Wir bearbeiten zur Zeit zwei Projekte, die ihre Ausführungsumgebung gewechselt haben und parallel weiterentwickelt werden sollten. Von einem Headless-Browser in eine Node-Umgebung zu wechseln, klingt nach einem natürlichen Schritt. Allerdings brachte das viele kleinere und größere Probleme mit sich, die nur durch die intensive Zusammenarbeit mit dem Kunden, der die Ausführungsumgebung bereitstellt, zu bewältigen waren.",[18,1226,1227],{},"Erschwerend kam hinzu, dass nur eine Lösung komplett bei synyx erstellt wurde. Bei der anderen unterstützen wir einen Dienstleister, der an dem Programm seit rund sechs Jahren arbeitet. Gemeinsam machen wir die Anwendung fit für die Zukunft. Musik-Streaming ins Auto, ein interessantes, wenn auch stellenweise stressiges Projekt. Vor sechs Jahren wurde JavaScript doch noch ganz anders geschrieben als heute.",[335,1229,1231],{"id":1230},"bock-mitzumachen","Bock mitzumachen?",[18,1233,1234],{},"Wenn Dich JavaScript als allgegenwärtige Sprache interessiert, Du Deine Erfahrungen einbringen willst oder einfach mal schauen möchtest, wie es ist, mit einem großen Kunden zusammenzuarbeiten, der versucht Probleme prozessorientiert zu lösen, komm doch mal bei uns vorbei und wir schauen, ob es für Dich und uns passt.",[18,1236,1237,1238,1242],{},"Melde Dich einfach bei uns unter ",[22,1239,1241],{"href":1240},"mailto:jobs@synyx.de","jobs@synyx.de"," und erzähl uns, wer Du bist und was Du so kannst. Zeugnisse sind uns nicht so wichtig. Statt Deine Grundschulnoten zu analysieren, wollen wir lieber mehr über Deine Interessen und Fähigkeiten erfahren. Wir freuen uns, von Dir zu hören!",{"title":34,"searchDepth":98,"depth":98,"links":1244},[1245,1246,1247],{"id":1200,"depth":98,"text":1201},{"id":1220,"depth":98,"text":1221},{"id":1230,"depth":98,"text":1231},[256],"2019-05-28T16:26:03","https://synyx.de/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen/",{},"/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen",{"title":1187,"description":1197},"experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen","blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen",[1257,1258,1259,670,1260],"arbeitsalltag","developer","entwickler","jobs","Werdegang des JavaScript Developers Christian Lange bei synyx.","AqRd7fJ4-roSV9bB721fTkY674GmJh6n6gpoi-HeEUI",{"id":1264,"title":1265,"author":1266,"body":1268,"category":1674,"date":1675,"description":1676,"extension":104,"link":1677,"meta":1678,"navigation":107,"path":1679,"seo":1680,"slug":1272,"stem":1681,"tags":1682,"teaser":1686,"__hash__":1687},"blog/blog/java-deep-dive-class-file-format-for-debug-information.md","Java Deep Dive – class file format for debug information",[1267],"ullinger",{"type":11,"value":1269,"toc":1668},[1270,1273,1276,1279,1288,1294,1305,1310,1313,1323,1327,1330,1341,1344,1353,1358,1383,1400,1405,1420,1426,1432,1438,1442,1452,1456,1459,1466,1471,1478,1483,1490,1498,1536,1539,1544,1547,1550,1559,1562,1568,1572,1575,1578,1602,1611,1614,1617,1621,1638,1645,1665],[14,1271,1265],{"id":1272},"java-deep-dive-class-file-format-for-debug-information",[18,1274,1275],{},"We are developing Apps for Android and one important aspect of our pipeline is automated device testing using Espresso.\nMy co-worker recently had to debug an Android test that would fail on only one of our various devices.",[18,1277,1278],{},"The test claimed that one of the checked Views was not visible (enough), under visual inspection the User Interface\nlooked fine though. To find out exactly why the offending View made Espresso fail, he decided to debug into the\nViewMatcher.",[18,1280,1281,1282,1287],{},"We were surprised that no variable values where available when debugging inside\nEspresso",[22,1283,1286],{"href":1284,"rel":1285},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#1",[26],"[1]",". There was no way for us to read\nthe values of the offending View-object:",[18,1289,1290],{},[32,1291],{"alt":1292,"src":1293},"Code Beispiel","https://media.synyx.de/uploads/2019/03/matcher-no-debug-1-cut-768x718.png",[18,1295,1296,1297,1300,1301,1304],{},"Here we even get the warning ",[405,1298,1299],{},"Variables are not avaible","when at least the local variable ",[168,1302,1303],{},"areaPercent"," with its value\nshould be displayed:",[18,1306,1307],{},[32,1308],{"alt":1292,"src":1309},"https://media.synyx.de/uploads/2019/03/matcher-no-debug-2-768x634.png",[18,1311,1312],{},"Debugging our own code was working as expected, so the IDE and debugger itself seemed to work fine. We suspected that\nthe Espresso library might miss some critical debug information. The test was fixed shortly after, but we were still\ncurious about the behaviour of the debugger and wanted to know if the initial guess was correct.",[18,1314,1315,1318,1319,1322],{},[168,1316,1317],{},"What"," exactly is stored in java classes for debugging? ",[168,1320,1321],{},"How"," is that information stored? We will find out right here.",[335,1324,1326],{"id":1325},"debug-information-format-in-java-class-files","Debug information format in Java class files",[18,1328,1329],{},"A class file can contain three different pieces of debug information:",[343,1331,1332,1335,1338],{},[346,1333,1334],{},"line information",[346,1336,1337],{},"local variable information",[346,1339,1340],{},"source information",[18,1342,1343],{},"This is explained when calling",[492,1345,1347],{"className":867,"code":1346,"language":869,"meta":34,"style":34},"javac -help\n",[405,1348,1349],{"__ignoreMap":34},[500,1350,1351],{"class":502,"line":503},[500,1352,1346],{},[18,1354,1355],{},[32,1356],{"alt":1292,"src":1357},"https://media.synyx.de/uploads/2019/03/javac-options.png",[18,1359,1360,1361,1364,1365,1368,1369,1372,1373,1378,1379,1382],{},"Compiling with",[405,1362,1363],{},"javac","using default settings results in classes containing ",[168,1366,1367],{},"lines"," and ",[168,1370,1371],{},"vars","\ninformation",[22,1374,1377],{"href":1375,"rel":1376},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#2",[26],"[2]",". Existing class files can be\nanalyzed using the ",[405,1380,1381],{},"javap","tool that is part of the JDK.",[18,1384,1385,1386,1391,1392,1395,1396,1399],{},"We decided to download",[22,1387,1390],{"href":1388,"rel":1389},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#3",[26],"[3]"," the Espresso core\nlibrary from a maven repository, unzip the Android ",[168,1393,1394],{},"aar","-Archive and finally unzip the contained classes.jar. Running\n",[405,1397,1398],{},"javap -l"," on our ViewMatchers class gave the following output:",[18,1401,1402],{},[32,1403],{"alt":1292,"src":1404},"https://media.synyx.de/uploads/2019/03/javap-debuggable-768x158.png",[18,1406,1407,1408,1411,1412,1415,1416,1419],{},"Adding the ",[168,1409,1410],{},"-l"," option should print line ",[469,1413,1414],{},"and"," variable information but we only find ",[168,1417,1418],{},"LineNumberTable"," entries.",[18,1421,1422,1423,1425],{},"Using a hex-editor we can verify that the class file seems to contain a ",[168,1424,1418],{}," of some kind:",[18,1427,1428],{},[32,1429],{"alt":1430,"src":1431},"espresso test","https://media.synyx.de/uploads/2019/03/bless-espresso-768x439.png",[18,1433,1434,1435,1437],{},"We check one of our own, fully debuggable class files again with ",[405,1436,1398],{},":",[18,1439,1440],{},[32,1441],{"alt":1292,"src":1404},[18,1443,1444,1445,1447,1448,1451],{},"We can see the ",[168,1446,1418],{}," as well as a ",[168,1449,1450],{},"LocalVariableTable"," that was missing in the ViewMatchers class.",[335,1453,1455],{"id":1454},"assumption-verification","Assumption & Verification",[18,1457,1458],{},"At this point we can assume:",[18,1460,1461],{},[469,1462,1463,1465],{},[168,1464,1450],{}," is required to debug variable values",[18,1467,1468],{},[168,1469,1470],{},"Comparing behaviour and class content between Espresso and our own code we can assume LocalVariableTable is required to\ndebug variable values.",[18,1472,1473],{},[469,1474,1475,1477],{},[168,1476,1418],{}," is required to use breakpoints",[18,1479,1480],{},[168,1481,1482],{},"Unrelated to the above behaviour: A single line of source code can be translated to many bytecode statements. Placing a\nbreakpoint on a source code line requires a debugger to know which bytecode statement to break at. Therefor, omitting\nthe LineNumberTable should break the ability to set and trigger breakpoints.",[18,1484,1485,1486,1489],{},"We will try to verify both of these assumptions by compiling our own class using ",[405,1487,1488],{},"javac -g:none"," and observe how the\ndebug behaviour changes.",[18,1491,1492,1493],{},"The Android Gradle/Groovy build is sometimes difficult to work with, this is a solution to add arguments to the java\ncompiler ",[22,1494,1497],{"href":1495,"rel":1496},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#4",[26],"[4]",[492,1499,1501],{"className":867,"code":1500,"language":869,"meta":34,"style":34},"allprojects {\n gradle.projectsEvaluated {\n tasks.withType(JavaCompile) {\n options.compilerArgs << "-g:none"\n }\n }\n}\n",[405,1502,1503,1508,1513,1518,1523,1528,1532],{"__ignoreMap":34},[500,1504,1505],{"class":502,"line":503},[500,1506,1507],{},"allprojects {\n",[500,1509,1510],{"class":502,"line":98},[500,1511,1512],{}," gradle.projectsEvaluated {\n",[500,1514,1515],{"class":502,"line":249},[500,1516,1517],{}," tasks.withType(JavaCompile) {\n",[500,1519,1520],{"class":502,"line":583},[500,1521,1522],{}," options.compilerArgs << "-g:none"\n",[500,1524,1525],{"class":502,"line":615},[500,1526,1527],{}," }\n",[500,1529,1530],{"class":502,"line":621},[500,1531,940],{},[500,1533,1534],{"class":502,"line":631},[500,1535,802],{},[18,1537,1538],{},"After cleaning & rebuilding our app we restart the test and try to debug:",[18,1540,1541],{},[32,1542],{"alt":1292,"src":1543},"https://media.synyx.de/uploads/2019/03/debug-own-no-debuginfo-768x301.png",[18,1545,1546],{},"We can see the breakpoint is disabled! Additionally, after starting the test we fell right through to the next\nstackframe in ViewMatchers. We did not stop at the breakpoint! Breaking in ViewMatchers and dropping down the stack we\ncan also confirm we there is no local variable information available.",[18,1548,1549],{},"Checking the output of",[492,1551,1553],{"className":867,"code":1552,"language":869,"meta":34,"style":34},"javap -l\n",[405,1554,1555],{"__ignoreMap":34},[500,1556,1557],{"class":502,"line":503},[500,1558,1552],{},[18,1560,1561],{},"again we can verify the missing debug information:",[18,1563,1564],{},[32,1565],{"alt":1566,"src":1567},"Code Ausgabe","https://media.synyx.de/uploads/2019/03/javap-own-nondebuggable-768x183.png",[335,1569,1571],{"id":1570},"final-thoughts","Final Thoughts",[18,1573,1574],{},"We have seen how the IDEs debug behaviour and capabilities relate to the information embedded in the class files.",[18,1576,1577],{},"This is relevant to Android just as well as “traditional” java applications. Android also relies on the information\nembedded in the class files, as we have seen when unpacking the Espresso-Core AAR archive. We were able to use basic\ntools from the JDK (javap) to analyze Espresso classes.",[18,1579,1580,1581,1584,1585,1587,1588,1591,1592,1595,1596,1601],{},"We still don’t know ",[469,1582,1583],{},"why"," Espresso is missing the ",[168,1586,1450],{},". Building our App in the ",[168,1589,1590],{},"Release","-Variant\nwith ",[168,1593,1594],{},"debuggable=false"," did still include the full debug information. It is possible that Proguard is responsible for\nstripping the information from the class files, it seems at least\ncapable ",[22,1597,1600],{"href":1598,"rel":1599},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#5",[26],"[5]"," to do so. Unfortunately, the\ndocumentation for ProGuard is not explaining when and how debug information is manipulated.",[18,1603,1604,1605,1610],{},"For a deeper technical reading of the class file format you can consult the documentation provided by\nOracle ",[22,1606,1609],{"href":1607,"rel":1608},"https://synyx.de/blog/2019-03-14-android-debug-class-format/?page=1#6",[26],"[6]",". Be careful to check the correct\njava version.",[18,1612,1613],{},"And finally, the next time you debug an application you hopefully have a better understanding of some of the involved\nparts.",[18,1615,1616],{},"We might possibly explore the remaining open questions in a future blog post.",[335,1618,1620],{"id":1619},"references","References",[1622,1623,1624,1630,1636],"ol",{},[346,1625,1626],{},[22,1627,1628],{"href":1628,"rel":1629},"https://developer.android.com/training/testing/espresso",[26],[346,1631,1632],{},[22,1633,1634],{"href":1634,"rel":1635},"https://www.logicbig.com/how-to/java-command/compile-with-debug-info.html",[26],[346,1637],{},[18,1639,1640,1641],{},"e.g.: ",[22,1642,1643],{"href":1643,"rel":1644},"https://mvnrepository.com/artifact/com.android.support.test.espresso/espresso-core/3.0.2",[26],[1622,1646,1647,1653,1659],{"start":583},[346,1648,1649],{},[22,1650,1651],{"href":1651,"rel":1652},"https://stackoverflow.com/a/42297051",[26],[346,1654,1655],{},[22,1656,1657],{"href":1657,"rel":1658},"https://stackoverflow.com/a/5258014",[26],[346,1660,1661],{},[22,1662,1663],{"href":1663,"rel":1664},"https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.12",[26],[1017,1666,1667],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":98,"depth":98,"links":1669},[1670,1671,1672,1673],{"id":1325,"depth":98,"text":1326},{"id":1454,"depth":98,"text":1455},{"id":1570,"depth":98,"text":1571},{"id":1619,"depth":98,"text":1620},[256],"2019-03-14T12:15:02","We are developing Apps for Android and one important aspect of our pipeline is automated device testing using Espresso.\\nMy co-worker recently had to debug an Android test that would fail on only one of our various devices.","https://synyx.de/blog/java-deep-dive-class-file-format-for-debug-information/",{},"/blog/java-deep-dive-class-file-format-for-debug-information",{"title":1265,"description":1275},"blog/java-deep-dive-class-file-format-for-debug-information",[1683,869,1684,1685],"android","mobile","test","We are developing Apps for Android and one important aspect of our pipeline is automated device testing using Espresso. My co-worker recently had to debug an Android test that would fail on only one of our various devices.","lL-cpl7U2Hib5KMqgnEiAPswVdD5Ewyxua6xj3G-aJQ",{"id":1689,"title":1690,"author":1691,"body":1693,"category":2523,"date":2524,"description":2525,"extension":104,"link":2526,"meta":2527,"navigation":107,"path":2528,"seo":2529,"slug":1697,"stem":2530,"tags":2531,"teaser":2537,"__hash__":2538},"blog/blog/using-travis-ci-to-deploy-to-maven-repositories-and-github-releases.md","Using Travis CI to deploy to Maven repositories and GitHub Releases",[1692],"larrasz",{"type":11,"value":1694,"toc":2512},[1695,1698,1701,1705,1718,1743,1749,1781,1806,2004,2008,2015,2069,2073,2084,2088,2116,2208,2212,2219,2266,2285,2298,2302,2308,2319,2332,2417,2421,2424,2430,2464,2471,2474,2477,2510],[14,1696,1690],{"id":1697},"using-travis-ci-to-deploy-to-maven-repositories-and-github-releases",[18,1699,1700],{},"This post outlines the steps needed to simultaneously deploy to Maven repositories and to GitHub Releases. Every time a\ntagged commit is pushed, a Travis CI build will be triggered automatically and start the release process. This blog post\nuses Sonatype Nexus as an example for a Maven repository manager.",[335,1702,1704],{"id":1703},"preparing-github-releases","Preparing GitHub Releases",[18,1706,1707,1708,1713,1714,1717],{},"Sergey Mashkov has written a ",[22,1709,1712],{"href":1710,"rel":1711},"https://github.com/cy6erGn0m/github-release-plugin",[26],"Maven plugin"," that allows us to create\na new release on our project’s releases page and upload our build artifacts to a release. The following sections\ndescribe how we need to configure our ",[405,1715,1716],{},"pom.xml"," in order to use this plugin.",[18,1719,1720,1721,1724,1725,1728,1729,1732,1733,1736,1737,1742],{},"The plugin uses the ",[405,1722,1723],{},"scm"," settings to find out for which project the new release should be created. Right now, there’s\nstill a bug in the plugin which restricts the format for our git URIs. The only working format is\n",[405,1726,1727],{},"scm:git:git@github.com:...",". Neither ",[405,1730,1731],{},"scm:git:https://github.com/...","nor ",[405,1734,1735],{},"scm:git:ssh://git@github.com/...","work, but\na ",[22,1738,1741],{"href":1739,"rel":1740},"https://github.com/cy6erGn0m/github-release-plugin/pull/2",[26],"pull request"," has been created that adds this\nfunctionality.",[18,1744,1745,1746,1748],{},"So add an ",[405,1747,1723],{},"section to your pom that looks like this:",[492,1750,1754],{"className":1751,"code":1752,"language":1753,"meta":34,"style":34},"language-xml shiki shiki-themes github-light github-dark","\u003Cscm>\n \u003Curl>https://github.com/example/project\u003C/url>\n \u003Cconnection>scm:git:git@github.com:example/project.git\u003C/connection>\n \u003CdeveloperConnection>scm:git:git@github.com:example/project.git\u003C/developerConnection>\n\u003C/scm>\n\n\n","xml",[405,1755,1756,1761,1766,1771,1776],{"__ignoreMap":34},[500,1757,1758],{"class":502,"line":503},[500,1759,1760],{},"\u003Cscm>\n",[500,1762,1763],{"class":502,"line":98},[500,1764,1765],{}," \u003Curl>https://github.com/example/project\u003C/url>\n",[500,1767,1768],{"class":502,"line":249},[500,1769,1770],{}," \u003Cconnection>scm:git:git@github.com:example/project.git\u003C/connection>\n",[500,1772,1773],{"class":502,"line":583},[500,1774,1775],{}," \u003CdeveloperConnection>scm:git:git@github.com:example/project.git\u003C/developerConnection>\n",[500,1777,1778],{"class":502,"line":615},[500,1779,1780],{},"\u003C/scm>\n",[18,1782,1783,1784,1789,1790,1793,1794,1799,1800,1805],{},"The second step is to include the plugin in our pom. Right now the plugin is only available\nfrom ",[22,1785,1788],{"href":1786,"rel":1787},"http://dl.bintray.com/cy6ergn0m/maven",[26],"bintray.com"," so we need to add it as a plugin repository. We only want to\ncreate a new release on GitHub when we are building a new release. Hence we configure the plugin in an extra release\nprofile section. This leads to the plugin being executed only if Maven is started with ",[405,1791,1792],{},"-Prelease","and only if the deploy\ngoal is invoked. For more information on how to configure the plugin options please refer to\nits ",[22,1795,1798],{"href":1796,"rel":1797},"https://github.com/cy6erGn0m/github-release-plugin#plugin-configuration-options",[26],"documentation"," and\nthe ",[22,1801,1804],{"href":1802,"rel":1803},"https://synyx.de/blog/2018-01-24-travisci-github-releases/?page=3#pitfalls",[26],"pitfalls"," below.",[492,1807,1809],{"className":1751,"code":1808,"language":1753,"meta":34,"style":34},"\u003Cprofiles>\n ...\n \u003Cprofile>\n \u003Cid>release\u003C/id>\n \u003CpluginRepositories>\n \u003CpluginRepository>\n \u003Cid>bintray-cy6ergn0m-maven\u003C/id>\n \u003Cname>bintray-plugins\u003C/name>\n \u003Curl>http://dl.bintray.com/cy6ergn0m/maven\u003C/url>\n \u003C/pluginRepository>\n \u003C/pluginRepositories>\n \u003Cbuild>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>cy.github\u003C/groupId>\n \u003CartifactId>github-release-plugin\u003C/artifactId>\n \u003Cversion>0.5.1\u003C/version>\n \u003Cconfiguration>\n \u003CtagName>${project.version}\u003C/tagName>\n \u003CreleaseTitle>${project.artifactId}-${project.version}\u003C/releaseTitle>\n \u003CserverId>github\u003C/serverId>\n \u003C/configuration>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cgoals>\n \u003Cgoal>gh-upload\u003C/goal>\n \u003C/goals>\n \u003Cphase>deploy\u003C/phase>\n \u003C/execution>\n \u003C/executions>\n \u003C/plugin>\n \u003C/plugins>\n \u003C/build>\n \u003C/profile>\n\u003C/profiles>\n",[405,1810,1811,1816,1821,1826,1831,1836,1841,1846,1851,1856,1861,1866,1871,1876,1881,1886,1891,1896,1902,1908,1914,1920,1926,1932,1938,1944,1950,1956,1962,1968,1974,1980,1986,1992,1998],{"__ignoreMap":34},[500,1812,1813],{"class":502,"line":503},[500,1814,1815],{},"\u003Cprofiles>\n",[500,1817,1818],{"class":502,"line":98},[500,1819,1820],{}," ...\n",[500,1822,1823],{"class":502,"line":249},[500,1824,1825],{}," \u003Cprofile>\n",[500,1827,1828],{"class":502,"line":583},[500,1829,1830],{}," \u003Cid>release\u003C/id>\n",[500,1832,1833],{"class":502,"line":615},[500,1834,1835],{}," \u003CpluginRepositories>\n",[500,1837,1838],{"class":502,"line":621},[500,1839,1840],{}," \u003CpluginRepository>\n",[500,1842,1843],{"class":502,"line":631},[500,1844,1845],{}," \u003Cid>bintray-cy6ergn0m-maven\u003C/id>\n",[500,1847,1848],{"class":502,"line":641},[500,1849,1850],{}," \u003Cname>bintray-plugins\u003C/name>\n",[500,1852,1853],{"class":502,"line":647},[500,1854,1855],{}," \u003Curl>http://dl.bintray.com/cy6ergn0m/maven\u003C/url>\n",[500,1857,1858],{"class":502,"line":805},[500,1859,1860],{}," \u003C/pluginRepository>\n",[500,1862,1863],{"class":502,"line":810},[500,1864,1865],{}," \u003C/pluginRepositories>\n",[500,1867,1868],{"class":502,"line":826},[500,1869,1870],{}," \u003Cbuild>\n",[500,1872,1873],{"class":502,"line":838},[500,1874,1875],{}," \u003Cplugins>\n",[500,1877,1878],{"class":502,"line":937},[500,1879,1880],{}," \u003Cplugin>\n",[500,1882,1883],{"class":502,"line":943},[500,1884,1885],{}," \u003CgroupId>cy.github\u003C/groupId>\n",[500,1887,1888],{"class":502,"line":948},[500,1889,1890],{}," \u003CartifactId>github-release-plugin\u003C/artifactId>\n",[500,1892,1893],{"class":502,"line":954},[500,1894,1895],{}," \u003Cversion>0.5.1\u003C/version>\n",[500,1897,1899],{"class":502,"line":1898},18,[500,1900,1901],{}," \u003Cconfiguration>\n",[500,1903,1905],{"class":502,"line":1904},19,[500,1906,1907],{}," \u003CtagName>${project.version}\u003C/tagName>\n",[500,1909,1911],{"class":502,"line":1910},20,[500,1912,1913],{}," \u003CreleaseTitle>${project.artifactId}-${project.version}\u003C/releaseTitle>\n",[500,1915,1917],{"class":502,"line":1916},21,[500,1918,1919],{}," \u003CserverId>github\u003C/serverId>\n",[500,1921,1923],{"class":502,"line":1922},22,[500,1924,1925],{}," \u003C/configuration>\n",[500,1927,1929],{"class":502,"line":1928},23,[500,1930,1931],{}," \u003Cexecutions>\n",[500,1933,1935],{"class":502,"line":1934},24,[500,1936,1937],{}," \u003Cexecution>\n",[500,1939,1941],{"class":502,"line":1940},25,[500,1942,1943],{}," \u003Cgoals>\n",[500,1945,1947],{"class":502,"line":1946},26,[500,1948,1949],{}," \u003Cgoal>gh-upload\u003C/goal>\n",[500,1951,1953],{"class":502,"line":1952},27,[500,1954,1955],{}," \u003C/goals>\n",[500,1957,1959],{"class":502,"line":1958},28,[500,1960,1961],{}," \u003Cphase>deploy\u003C/phase>\n",[500,1963,1965],{"class":502,"line":1964},29,[500,1966,1967],{}," \u003C/execution>\n",[500,1969,1971],{"class":502,"line":1970},30,[500,1972,1973],{}," \u003C/executions>\n",[500,1975,1977],{"class":502,"line":1976},31,[500,1978,1979],{}," \u003C/plugin>\n",[500,1981,1983],{"class":502,"line":1982},32,[500,1984,1985],{}," \u003C/plugins>\n",[500,1987,1989],{"class":502,"line":1988},33,[500,1990,1991],{}," \u003C/build>\n",[500,1993,1995],{"class":502,"line":1994},34,[500,1996,1997],{}," \u003C/profile>\n",[500,1999,2001],{"class":502,"line":2000},35,[500,2002,2003],{},"\u003C/profiles>\n",[335,2005,2007],{"id":2006},"preparing-for-maven-releases","Preparing for Maven releases",[18,2009,2010,2011,2014],{},"If you don’t already have a repository where you want to deploy to, you need to create a release and a snapshot\nrepository and add them to ",[405,2012,2013],{},"distributionManagement",".You might also want to create a separate user that has access only\nto your target repositories. This user will be used to upload the releases.",[492,2016,2018],{"className":1751,"code":2017,"language":1753,"meta":34,"style":34},"\u003CdistributionManagement>\n \u003Crepository>\n \u003Cid>oss\u003C/id>\n \u003Curl>https://nexus.example.com/content/repositories/oss-releases\u003C/url>\n \u003C/repository>\n \u003CsnapshotRepository>\n \u003Cid>oss\u003C/id>\n \u003Curl>https://nexus.example.com/content/repositories/oss-snapshots\u003C/url>\n \u003C/snapshotRepository>\n\u003C/distributionManagement>\n",[405,2019,2020,2025,2030,2035,2040,2045,2050,2054,2059,2064],{"__ignoreMap":34},[500,2021,2022],{"class":502,"line":503},[500,2023,2024],{},"\u003CdistributionManagement>\n",[500,2026,2027],{"class":502,"line":98},[500,2028,2029],{}," \u003Crepository>\n",[500,2031,2032],{"class":502,"line":249},[500,2033,2034],{}," \u003Cid>oss\u003C/id>\n",[500,2036,2037],{"class":502,"line":583},[500,2038,2039],{}," \u003Curl>https://nexus.example.com/content/repositories/oss-releases\u003C/url>\n",[500,2041,2042],{"class":502,"line":615},[500,2043,2044],{}," \u003C/repository>\n",[500,2046,2047],{"class":502,"line":621},[500,2048,2049],{}," \u003CsnapshotRepository>\n",[500,2051,2052],{"class":502,"line":631},[500,2053,2034],{},[500,2055,2056],{"class":502,"line":641},[500,2057,2058],{}," \u003Curl>https://nexus.example.com/content/repositories/oss-snapshots\u003C/url>\n",[500,2060,2061],{"class":502,"line":647},[500,2062,2063],{}," \u003C/snapshotRepository>\n",[500,2065,2066],{"class":502,"line":805},[500,2067,2068],{},"\u003C/distributionManagement>\n",[335,2070,2072],{"id":2071},"putting-it-all-together","Putting it all together",[18,2074,2075,2076,2079,2080,2083],{},"So far we have configured the GitHub release plugin to deploy our artifacts to the GitHub Releases page and setup Maven\nreleases. Now it’s time to glue the parts together. In order to do this we have to create a ",[405,2077,2078],{},"settings.xml","for use with\nMaven, a ",[405,2081,2082],{},".travis.yml"," that manages our Travis CI builds and we have to configure some environment variables in Travis\nCI itself. Furthermore we need a small shell script that orchestrates our release.",[133,2085,2087],{"id":2086},"maven-settings","Maven settings",[18,2089,2090,2091,2093,2094,2097,2098,2101,2102,2105,2106,2109,2110,2115],{},"Create a new ",[405,2092,2078],{},"file in your repository, e.g in a ",[405,2095,2096],{},".travis/","directory. The content of this file should look\nlike the following snippet. The ",[405,2099,2100],{},"\u003Cserver>","ids have to match the ids in ",[405,2103,2104],{},"\u003CdistributionManagement>","and the ",[405,2107,2108],{},"\u003CserverId>","of\nthe GitHub release plugin exactly. Do not use static credentials here! You don’t want everyone who stumbles upon your\nrepository on GitHub to have write access to your Nexus/Artifactory and GitHub. We will use Travis CI’s capability to\ninject environment variables into builds;\nthe ",[22,2111,2114],{"href":2112,"rel":2113},"https://synyx.de/blog/2018-01-24-travisci-github-releases/?page=3#configuring-travis-ci-itself",[26],"environment variables","\nwill be configured soon.",[492,2117,2119],{"className":1751,"code":2118,"language":1753,"meta":34,"style":34},"\u003Csettings xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.0.0\n http://maven.apache.org/xsd/settings-1.0.0.xsd\">\n \u003Cservers>\n \u003Cserver>\n \u003Cid>oss\u003C/id>\n \u003Cusername>${env.NEXUS_USERNAME}\u003C/username>\n \u003Cpassword>${env.NEXUS_PASSWORD}\u003C/password>\n \u003C/server>\n \u003Cserver>\n \u003Cid>github\u003C/id>\n \u003Cusername>${env.GITHUB_USERNAME}\u003C/username>\n \u003Cpassword>${env.GITHUB_TOKEN}\u003C/password>\n \u003C/server>\n \u003C/servers>\n\n\u003C/settings>\n",[405,2120,2121,2126,2131,2136,2141,2146,2151,2156,2161,2166,2171,2175,2180,2185,2190,2194,2199,2203],{"__ignoreMap":34},[500,2122,2123],{"class":502,"line":503},[500,2124,2125],{},"\u003Csettings xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"\n",[500,2127,2128],{"class":502,"line":98},[500,2129,2130],{}," xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n",[500,2132,2133],{"class":502,"line":249},[500,2134,2135],{}," xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.0.0\n",[500,2137,2138],{"class":502,"line":583},[500,2139,2140],{}," http://maven.apache.org/xsd/settings-1.0.0.xsd\">\n",[500,2142,2143],{"class":502,"line":615},[500,2144,2145],{}," \u003Cservers>\n",[500,2147,2148],{"class":502,"line":621},[500,2149,2150],{}," \u003Cserver>\n",[500,2152,2153],{"class":502,"line":631},[500,2154,2155],{}," \u003Cid>oss\u003C/id>\n",[500,2157,2158],{"class":502,"line":641},[500,2159,2160],{}," \u003Cusername>${env.NEXUS_USERNAME}\u003C/username>\n",[500,2162,2163],{"class":502,"line":647},[500,2164,2165],{}," \u003Cpassword>${env.NEXUS_PASSWORD}\u003C/password>\n",[500,2167,2168],{"class":502,"line":805},[500,2169,2170],{}," \u003C/server>\n",[500,2172,2173],{"class":502,"line":810},[500,2174,2150],{},[500,2176,2177],{"class":502,"line":826},[500,2178,2179],{}," \u003Cid>github\u003C/id>\n",[500,2181,2182],{"class":502,"line":838},[500,2183,2184],{}," \u003Cusername>${env.GITHUB_USERNAME}\u003C/username>\n",[500,2186,2187],{"class":502,"line":937},[500,2188,2189],{}," \u003Cpassword>${env.GITHUB_TOKEN}\u003C/password>\n",[500,2191,2192],{"class":502,"line":943},[500,2193,2170],{},[500,2195,2196],{"class":502,"line":948},[500,2197,2198],{}," \u003C/servers>\n",[500,2200,2201],{"class":502,"line":954},[500,2202,644],{"emptyLinePlaceholder":107},[500,2204,2205],{"class":502,"line":1898},[500,2206,2207],{},"\u003C/settings>\n",[133,2209,2211],{"id":2210},"release-script","Release script",[18,2213,2214,2215,2218],{},"We need a small shell script that orchestrates our releases. This script sets the correct version, creates a release and\nuploads it to our Maven repository and to GitHub. To release the correct version the",[405,2216,2217],{},"TRAVIS_TAG"," environment variable\nwill be used. Travis CI uses this variable to inject the value of the git tag into the build.",[492,2220,2222],{"className":1751,"code":2221,"language":1753,"meta":34,"style":34},"#!/usr/bin/env bash\n\nset -e\n\necho \"Ensuring that pom matches $TRAVIS_TAG\"\n./mvnw org.codehaus.mojo:versions-maven-plugin:2.5:set -DnewVersion=$TRAVIS_TAG\n\necho \"Uploading to oss repo and GitHub\"\n./mvnw deploy --settings .travis/settings.xml -DskipTests=true --batch-mode --update-snapshots -Prelease\n",[405,2223,2224,2229,2233,2238,2242,2247,2252,2256,2261],{"__ignoreMap":34},[500,2225,2226],{"class":502,"line":503},[500,2227,2228],{},"#!/usr/bin/env bash\n",[500,2230,2231],{"class":502,"line":98},[500,2232,644],{"emptyLinePlaceholder":107},[500,2234,2235],{"class":502,"line":249},[500,2236,2237],{},"set -e\n",[500,2239,2240],{"class":502,"line":583},[500,2241,644],{"emptyLinePlaceholder":107},[500,2243,2244],{"class":502,"line":615},[500,2245,2246],{},"echo \"Ensuring that pom matches $TRAVIS_TAG\"\n",[500,2248,2249],{"class":502,"line":621},[500,2250,2251],{},"./mvnw org.codehaus.mojo:versions-maven-plugin:2.5:set -DnewVersion=$TRAVIS_TAG\n",[500,2253,2254],{"class":502,"line":631},[500,2255,644],{"emptyLinePlaceholder":107},[500,2257,2258],{"class":502,"line":641},[500,2259,2260],{},"echo \"Uploading to oss repo and GitHub\"\n",[500,2262,2263],{"class":502,"line":647},[500,2264,2265],{},"./mvnw deploy --settings .travis/settings.xml -DskipTests=true --batch-mode --update-snapshots -Prelease\n",[18,2267,2268,2269,2272,2273,2276,2277,2280,2281,2284],{},"The script first sets the ",[405,2270,2271],{},"\u003Cversion>","in the pom exactly to our git tag’s value. So your tag always matches the version\nyou want to release, e.g. ",[405,2274,2275],{},"1.0","or ",[405,2278,2279],{},"1.5.1",". The second part creates the release. We need to reference the Maven settings\nin our repository here so that Travis CI has access rights to the Maven repositories and GitHub Releases. The important\npart here is to activate the",[405,2282,2283],{},"release","profile. This tells Maven to not only create and upload a Maven release but also to\ncreate a new GitHub Release.",[18,2286,2287,2288,2291,2292,2294,2295,352],{},"Name this script ",[405,2289,2290],{},"release.sh",", put it inside the ",[405,2293,2096],{},"directory and make it executable (",[405,2296,2297],{},"chmod +x",[133,2299,2301],{"id":2300},"build-configuration","Build configuration",[18,2303,2304,2305,2307],{},"Travis CI uses a file named ",[405,2306,2082],{},"at the root of a GitHub repository. The snipped contains the necessary steps.",[18,2309,2310,2311,2314,2315,2318],{},"In a normal build we just want to execute a simple ",[405,2312,2313],{},"clean verify",". The ",[405,2316,2317],{},"verify","goal will execute unit and integration\ntests. To make subsequent builds faster, we want to cache the m2 repositories during builds.",[18,2320,2321,2322,2324,2325,2328,2329,707],{},"The most important part is the deploy section. Here we configure Travis CI to run the ",[405,2323,2290],{},"script if and only if\na tag has been pushed (",[405,2326,2327],{},"tags: true",") on the repo ",[405,2330,2331],{},"example/project",[492,2333,2335],{"className":1751,"code":2334,"language":1753,"meta":34,"style":34},"sudo: false\nlanguage: java\njdk:\n - oraclejdk8\nscript: ./mvnw clean verify\ncache:\n directories:\n - $HOME/.m2\ndeploy:\n provider: script\n script: .travis/release.sh\n skip_cleanup: true\n on:\n repo: example/project\n tags: true\n jdk: oraclejdk8\n",[405,2336,2337,2342,2347,2352,2357,2362,2367,2372,2377,2382,2387,2392,2397,2402,2407,2412],{"__ignoreMap":34},[500,2338,2339],{"class":502,"line":503},[500,2340,2341],{},"sudo: false\n",[500,2343,2344],{"class":502,"line":98},[500,2345,2346],{},"language: java\n",[500,2348,2349],{"class":502,"line":249},[500,2350,2351],{},"jdk:\n",[500,2353,2354],{"class":502,"line":583},[500,2355,2356],{}," - oraclejdk8\n",[500,2358,2359],{"class":502,"line":615},[500,2360,2361],{},"script: ./mvnw clean verify\n",[500,2363,2364],{"class":502,"line":621},[500,2365,2366],{},"cache:\n",[500,2368,2369],{"class":502,"line":631},[500,2370,2371],{}," directories:\n",[500,2373,2374],{"class":502,"line":641},[500,2375,2376],{}," - $HOME/.m2\n",[500,2378,2379],{"class":502,"line":647},[500,2380,2381],{},"deploy:\n",[500,2383,2384],{"class":502,"line":805},[500,2385,2386],{}," provider: script\n",[500,2388,2389],{"class":502,"line":810},[500,2390,2391],{}," script: .travis/release.sh\n",[500,2393,2394],{"class":502,"line":826},[500,2395,2396],{}," skip_cleanup: true\n",[500,2398,2399],{"class":502,"line":838},[500,2400,2401],{}," on:\n",[500,2403,2404],{"class":502,"line":937},[500,2405,2406],{}," repo: example/project\n",[500,2408,2409],{"class":502,"line":943},[500,2410,2411],{}," tags: true\n",[500,2413,2414],{"class":502,"line":948},[500,2415,2416],{}," jdk: oraclejdk8\n",[133,2418,2420],{"id":2419},"configuring-travis-ci-itself","Configuring Travis CI itself",[18,2422,2423],{},"Now we need to teach Travis CI the values of the environment variables used in our Maven settings. To do this navigate\nto the Travis CI settings for your project:",[18,2425,2426],{},[32,2427],{"alt":2428,"src":2429},"travis environment","https://media.synyx.de/uploads/2018/01/travis-environment-768x282.png",[18,2431,2432,2433,2436,2437,2440,2441,2446,2447,2436,2450,2453,2454,2457,2458,2463],{},"Add ",[405,2434,2435],{},"NEXUS_USER","and ",[405,2438,2439],{},"NEXUS_PASSWORD","for your\nnewly ",[22,2442,2445],{"href":2443,"rel":2444},"https://synyx.de/blog/2018-01-24-travisci-github-releases/?page=3#preparing-maven-releases",[26],"created user",". You\nalso need to configure ",[405,2448,2449],{},"GITHUB_USERNAME",[405,2451,2452],{},"GITHUB_TOKEN",". But even though the field is called password, what your\nreally want to configure here is your ",[469,2455,2456],{},"GitHub API token",". Otherwise, the GitHub release plugin will not be able to\nupload artifacts. You can obtain your personal access token ",[22,2459,2462],{"href":2460,"rel":2461},"https://github.com/settings/tokens",[26],"here",". The token needs\naccess to the repo scope.",[18,2465,2466,2467,2470],{},"And that’s it. Now you can simply create a new tag in your repository (",[405,2468,2469],{},"git tag -a 1.1 -m \"Release 1.1\"","), push it to\nGitHub and Travis CI will trigger the release process. Happy releasing!",[335,2472,2473],{"id":1804},"Pitfalls",[18,2475,2476],{},"Some advice so that you do not encounter the same problems we did:",[343,2478,2479,2488,2500,2503],{},[346,2480,2481,2482,2484,2485],{},"Do not forget to make ",[405,2483,2290],{},"executable otherwise the Travis CI build will fail with a rather unhelpful message:\n",[405,2486,2487],{},"Script failed with status 127.",[346,2489,2490,2491,2494,2495,2497,2498,707],{},"The ",[405,2492,2493],{},"serverId","in the GitHub release plugin’s configuration refers to a ",[405,2496,2100],{},"entry in Maven’s ",[405,2499,2078],{},[346,2501,2502],{},"Do not be tempted to use your GitHub account password to configure the server settings for the GitHub release plugin.\nEven though the field is called password it’s really an API token that is needed here.",[346,2504,2505,2506,2509],{},"Do not change the value of ",[405,2507,2508],{},"\u003CtagName>","when configuring the GitHub release plugin. Using anything different from the\nproject’s version (i.e. the git tag’s value), will lead to recursive release builds. This happens because the release\nplugin will create and push a new tag if it does not already exist. Pushing a tag invokes another Travis CI build\nwhich will create a new tag and so on.",[1017,2511,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":2513},[2514,2515,2516,2522],{"id":1703,"depth":98,"text":1704},{"id":2006,"depth":98,"text":2007},{"id":2071,"depth":98,"text":2072,"children":2517},[2518,2519,2520,2521],{"id":2086,"depth":249,"text":2087},{"id":2210,"depth":249,"text":2211},{"id":2300,"depth":249,"text":2301},{"id":2419,"depth":249,"text":2420},{"id":1804,"depth":98,"text":2473},[1029,256],"2018-01-24T12:05:58","This post outlines the steps needed to simultaneously deploy to Maven repositories and to GitHub Releases. Every time a\\ntagged commit is pushed, a Travis CI build will be triggered automatically and start the release process. This blog post\\nuses Sonatype Nexus as an example for a Maven repository manager.","https://synyx.de/blog/using-travis-ci-to-deploy-to-maven-repositories-and-github-releases/",{},"/blog/using-travis-ci-to-deploy-to-maven-repositories-and-github-releases",{"title":1690,"description":1700},"blog/using-travis-ci-to-deploy-to-maven-repositories-and-github-releases",[2532,2533,869,2534,2535,2536],"artifactory","github","maven","nexus","travisci","This post outlines the steps needed to simultaneously deploy to Maven repositories and to GitHub Releases. Every time a tagged commit is pushed, a Travis CI build will be triggered automatically and start the release process. This blog post uses Sonatype Nexus as an example for a Maven repository manager. Preparing GitHub Releases Sergey Mashkov has written a Maven plugin that allows us to create a new release on our project’s releases page and upload our build artifacts to a release.","2TPRgpLddHBxZy1b9W1YaqOnatHf0HzZXEjowW2rwB4",{"id":2540,"title":2541,"author":2542,"body":2545,"category":2701,"date":2703,"description":2704,"extension":104,"link":2705,"meta":2706,"navigation":107,"path":2707,"seo":2708,"slug":2549,"stem":2710,"tags":2711,"teaser":2717,"__hash__":2718},"blog/blog/devoxx4kids-javaland4kids.md","Devoxx4Kids @ JavaLand4Kids",[2543,2544],"hammann","schneider",{"type":11,"value":2546,"toc":2696},[2547,2550,2577,2581,2590,2593,2598,2607,2616,2621,2624,2628,2637,2648,2652,2660,2663,2671,2674],[14,2548,2541],{"id":2549},"devoxx4kids-javaland4kids",[18,2551,2552,2553,2558,2559,2564,2565,2570,2571,2576],{},"Dieses Jahr waren wir, ",[22,2554,2557],{"href":2555,"rel":2556},"http://www.devoxx4kids.de/",[26],"Devoxx4Kids Deutschland",", wieder als Mentoren mit bei der\nOrganisation und Durchführung\nder ",[22,2560,2563],{"href":2561,"rel":2562},"https://web.archive.org/web/20160420190058/http://www.javaland.eu:80/de/javaland4kids/",[26],"JavaLand4Kids"," dabei. Die\nvon der ",[22,2566,2569],{"href":2567,"rel":2568},"https://www.doag.org/de/home/",[26],"DOAG"," organisierte Veranstaltung findet alljährlich im Rahmen\nder ",[22,2572,2575],{"href":2573,"rel":2574},"https://www.javaland.eu/de/home/",[26],"JavaLand Konferenz"," statt. Am Tag 0, bevor die “Alt”-Entwickler zum\nPhantasialand in Brühl strömen, haben dort 30 Kinder aus den vierten Klassen der Brühler Sankt August Grundschule die\nChance mit viel Spiel und Spaß erste Kenntnisse in der Programmierung zu erlernen.",[335,2578,2580],{"id":2579},"jumping-sumo-4-scratch","Jumping Sumo 4 Scratch",[18,2582,2583,2584,2589],{},"Wir hatten dieses Jahr die Möglichkeit die 30 Kinder mit\nunserem ",[22,2585,2588],{"href":2586,"rel":2587},"https://github.com/Devoxx4KidsDE/workshop-jumping-sumo-4-scratch",[26],"Jumping Sumo 4 Scratch Workshop"," spielerisch\nan die Informatik heranzuführen.",[18,2591,2592],{},"Die kleinen robusten Jumping Sumo Roboter sind mit zwei von einander getrennt ansteuerbaren Rädern ausgestattet, wodurch\nsie in sehr flink in beliebige Richtungen fahren können. Mit Hilfe eines Sprungmechanismus können sie außerdem bis zu\neinem Meter hoch springen. Zusätzlich kann durch die verbaute Sensorik die Lage des Roboters festgestellt und angepasst\nwerden. Zu guter Letzt ist über die integrierte Kamera die Möglichkeit vorhanden durch die “Augen” des Roboters die\nUmgebung zu erkunden.",[18,2594,2595],{},[32,2596],{"alt":34,"src":2597},"https://media.synyx.de/uploads//2017/03/IMG_20170327_091100.jpg",[18,2599,2600,2601,2606],{},"Die Steuerung bei diesem Workshop basiert auf der graphischen Programmierung\nvia ",[22,2602,2605],{"href":2603,"rel":2604},"https://scratch.mit.edu/",[26],"Scratch 2.0",". Die einzelnen Anweisungen werden wie Puzzleteile mit einander verbunden\nund bilden einen Ablauf welcher durch unterschiedliche Ereignisse gestartet werden kann. Damit ist es zum Beispiel\nmöglich durch einen Tastendruck eine Bewegungsabfolge des kleinen Roboters zu starten.",[18,2608,2609,2610,2615],{},"Die Kommunikation zwischen dem zur Programmierung verwendeten Computer und dem Jumping Sumo wird über WLAN und\nden ",[22,2611,2614],{"href":2612,"rel":2613},"https://github.com/Devoxx4KidsDE/drone-controller",[26],"Drone-Controller"," sichergestellt und bildet die technische\nBasis unseres Workshops. Dieser Controller wurde dafür in mühevoller Kleinarbeit reverse-engenieered da das genaue\nProtokoll bis zu diesem Zeitpunkt nicht bekannt war.",[18,2617,2618],{},[32,2619],{"alt":34,"src":2620},"https://media.synyx.de/uploads//2017/03/2017-03-27-12.21.13-e1490863119907.jpg",[18,2622,2623],{},"Ziel des Workshops ist es dem kleinen Roboter bei seiner Marsmission zu helfen. Diese startet allerdings mit einer\nBruchlandung wodurch die Programme des Jumping Sumos durcheinander gekommen sind. Die Kinder erhielten die Aufgabe ihren\nJumping Sumo durch ihre Programmierung wieder zu reparieren. Für diese Aufgabe wurden verschiedene Progammbausteine\nbenötigt. Anhand dieser Bausteine konnten wir den Kindern zahlreiche Programmierkonzepte wie Bedingungen, Schleifen und\nMethoden spielerisch näher bringen und durch das direkte Feedback des Jumping Sumos einfacher vermitteln.",[335,2625,2627],{"id":2626},"lego-mindstorms","Lego Mindstorms",[18,2629,2630,2631,2636],{},"Bauen und spielen. Das Erfolgsrezept von Lego wurde auch auf der Javaland4Kids aufgenommen. Die Kinder durften ihren\neigenen Roboter aufbauen und einen einfachen Wegfolge-Algorithmus programmieren, sodass ihr Roboter einer roten Linie\nfolgt. Als kleiner Helfer stand den Kindern ",[22,2632,2635],{"href":2633,"rel":2634},"https://www.ald.softbankrobotics.com/en/cool-robots/pepper",[26],"Pepper"," zur\nSeite.",[18,2638,2639,180,2642,180,2645],{},[32,2640],{"alt":34,"src":2641},"https://media.synyx.de/uploads//2017/03/2017-03-27-13.33.47-e1490863662532.jpg",[32,2643],{"alt":34,"src":2644},"https://media.synyx.de/uploads//2017/03/IMG_20170327_140827664.jpg",[32,2646],{"alt":34,"src":2647},"https://media.synyx.de/uploads//2017/03/IMG_20170327_113718.jpg",[335,2649,2651],{"id":2650},"chibitronics","Chibitronics",[18,2653,2654,2655,2659],{},"Im ",[22,2656,2651],{"href":2657,"rel":2658},"https://chibitronics.com/",[26]," Workshops drehte sich alles um Kupferleiterbahnen und deren Verdrahtung.\nAuf der Rückseite der Kupferleiterbahnen sind Klebestreifen aufgebraucht, wodurch diese auf Papier aufgeklebt werden\nkönnen. Damit war es den Kindern möglich einfache Schaltkreise zu kleben und mittels einer Knopfzelle den Schaltkreis zu\nschließen und die LEDs zum leuchten zu bringen.",[18,2661,2662],{},"Die Kinder erhielten die Aufgabe Osterkarten zu basteln und die Nase des Hasen zum Leuchten zu bringen.",[18,2664,2665,180,2668],{},[32,2666],{"alt":34,"src":2667},"https://media.synyx.de/uploads//2017/03/2017-03-27-13.53.02-e1490863318470.jpg",[32,2669],{"alt":34,"src":2670},"https://media.synyx.de/uploads//2017/03/2017-03-27-13.48.57-e1490863304407.jpg",[18,2672,2673],{},"Ein großer Dank geht an das Organisationsteam rund um Simone Fischer die dieser Devoxx4Kids auf der Javaland4Kids einen\ntollen Rahmen gegeben haben.",[18,2675,2676,2677,2680,2681,2684,2685,2690,2691,707],{},"Die nächste Devoxx4Kids werden in ",[469,2678,2679],{},"Wiesbaden am 22.4.2017"," und in ",[469,2682,2683],{},"Karlsruhe am 06.05.2017"," stattfinden. Alle\nInformationen dazu gibt es auf ",[22,2686,2689],{"href":2687,"rel":2688},"http://www.Devoxx4Kids.de",[26],"Devoxx4Kids.de"," und\nüber ",[22,2692,2695],{"href":2693,"rel":2694},"https://twitter.com/devoxx4kidsde",[26],"Twitter",{"title":34,"searchDepth":98,"depth":98,"links":2697},[2698,2699,2700],{"id":2579,"depth":98,"text":2580},{"id":2626,"depth":98,"text":2627},{"id":2650,"depth":98,"text":2651},[2702],"devoxx4kids","2017-03-31T09:50:08","Dieses Jahr waren wir, Devoxx4Kids Deutschland, wieder als Mentoren mit bei der\\nOrganisation und Durchführung\\nder JavaLand4Kids dabei. Die\\nvon der DOAG organisierte Veranstaltung findet alljährlich im Rahmen\\nder JavaLand Konferenz statt. Am Tag 0, bevor die “Alt”-Entwickler zum\\nPhantasialand in Brühl strömen, haben dort 30 Kinder aus den vierten Klassen der Brühler Sankt August Grundschule die\\nChance mit viel Spiel und Spaß erste Kenntnisse in der Programmierung zu erlernen.","https://synyx.de/blog/devoxx4kids-javaland4kids/",{},"/blog/devoxx4kids-javaland4kids",{"title":2541,"description":2709},"Dieses Jahr waren wir, Devoxx4Kids Deutschland, wieder als Mentoren mit bei der\nOrganisation und Durchführung\nder JavaLand4Kids dabei. Die\nvon der DOAG organisierte Veranstaltung findet alljährlich im Rahmen\nder JavaLand Konferenz statt. Am Tag 0, bevor die “Alt”-Entwickler zum\nPhantasialand in Brühl strömen, haben dort 30 Kinder aus den vierten Klassen der Brühler Sankt August Grundschule die\nChance mit viel Spiel und Spaß erste Kenntnisse in der Programmierung zu erlernen.","blog/devoxx4kids-javaland4kids",[2650,2702,266,2712,2713,268,2714,2715,2716],"javaland4kids","jumping-sumo","lego","pepper","scratch","Dieses Jahr waren wir, Devoxx4Kids Deutschland, wieder als Mentoren mit bei der Organisation und Durchführung der JavaLand4Kids dabei. Die von der DOAG organisierte Veranstaltung findet alljährlich im Rahmen der JavaLand…","LCuMhPe96plS0FrZjy8wDfCisfe74mKaQzsJzBWwXJw",{"id":2720,"title":2721,"author":2722,"body":2725,"category":2925,"date":2926,"description":2927,"extension":104,"link":2928,"meta":2929,"navigation":107,"path":2930,"seo":2931,"slug":2729,"stem":2933,"tags":2934,"teaser":2938,"__hash__":2939},"blog/blog/axon-3-event-replaying.md","Axon 3: Event Replaying",[2723,2724],"messner","thieme",{"type":11,"value":2726,"toc":2911},[2727,2730,2744,2748,2755,2758,2762,2765,2769,2772,2776,2783,2787,2790,2798,2804,2807,2816,2826,2860,2863,2870,2879,2883,2886,2890,2893,2897,2900,2902,2906,2909],[14,2728,2721],{"id":2729},"axon-3-event-replaying",[18,2731,2732,2737,2738,2743],{},[22,2733,2736],{"href":2734,"rel":2735},"http://www.axonframework.org/",[26],"Axon"," is a lightweight framework that supports the implemenation of CQRS patterns by\nproviding commonly used building blocks. One of those patterns is an event sourced application architecture. Even though\nEvent Sourcing and CQRS are orthogonal concepts they fit together very well and are often used together. Event sourcing\nin an ",[22,2739,2742],{"href":2740,"rel":2741},"https://msdn.microsoft.com/en-us/library/jj591577.aspx",[26],"ES/CQRS"," architecture means that all changes to the\napplication state are done via domain events and the current state can always be rebuilt from the series of events\navailable in a persistent event store. In addition to the event store there might also be one or more read models, for\nexample to achieve opimtized query performance. No matter if there are read models or not, the event store is considered\nthe single source of truth.",[335,2745,2747],{"id":2746},"event-replaying-what-and-why","Event Replaying: what and why?",[18,2749,2750,2751,2754],{},"Besides rebuilding current application state from the stored events we can employ a technique called ",[469,2752,2753],{},"Event Replaying","\nto achieve several other goals. For this purpose we need a mechanism to read all events from the event store and send\nthem to a set of components which are interested in handling them. This implies selecting and registering those\ncomponents.",[18,2756,2757],{},"Some use cases where event replaying can be applied are: generating new read models, removing inconsistencies by\nrebuilding an existing read model, or for analysis and debugging purposes.",[133,2759,2761],{"id":2760},"adding-new-read-models","Adding new read models",[18,2763,2764],{},"Suppose you have a library managemenent application where you can track and add meta data about your books. Sometime you\nmight want to switch from your table based search implementation to Elasticsearch. This can be done with event\nreplaying: all you have to do is to implement one or more event handlers that are responsible for extracting appropriate\ninformation from the domain events and inserting them into the Elasticsearch index.",[133,2766,2768],{"id":2767},"removing-inconsistencies-from-existing-read-models","Removing inconsistencies from existing read models",[18,2770,2771],{},"As software developers we may have to deal with bugs in our software even though we try hard to avoid them.\nNevertheless, bugs are inevitable. Imagine there is a bug in one of the event handling components that are supposed to\nrebuild the aforementioned Elasticsearch index. In such a situation, first the bug must be fixed (in code) and then all\ndomain events simply have to be replayed to the now well behaving event handler again.",[133,2773,2775],{"id":2774},"debugging-purposes","Debugging purposes",[18,2777,2778,2779,2782],{},"Sometimes you want to know exactly ",[168,2780,2781],{},"when"," an inconsistent application state has been introduced. One approach to achieve\nthis could be to replay all events up to a certain point in time and examine the corresponding application state.",[335,2784,2786],{"id":2785},"how","How?",[18,2788,2789],{},"Introducing event replaying in an application that already uses the Axon 3 framework is fairly easy. Axon provides all\nbuilding blocks needed to achieve this, in particular there are event handlers and event processors. Event handlers\nimplement all business logic, whereas event processors are responsible for taking care of the technical aspects of event\nprocessing.",[18,2791,2792,2793,1437],{},"There are two types of event processors: Subscribing Event Processors and Tracking Event Processors. From\nthe ",[22,2794,2797],{"href":2795,"rel":2796},"https://docs.axonframework.org/v/3.0/part3/event-processing.html",[26],"Axon documentation",[2799,2800,2801],"blockquote",{},[18,2802,2803],{},"The Subscribing Event Processors subscribe themselves to a source of Events and are invoked by the thread managed by\nthe publishing mechanism. Tracking Event Processors, on the other hand, pull their messages from a source using a\nthread\nthat it manages itself.",[18,2805,2806],{},"It’s the tracking event processors that provide event replaying capabilities. They’re keeping track of which events have\nalready been processed by means of storing a token. So let’s configure Axon to use tracking processors (instead of the\ndefault subscribing processors):",[492,2808,2810],{"className":1751,"code":2809,"language":1753,"meta":34,"style":34},"eventHandlingConfiguration.usingTrackingProcessors();\n",[405,2811,2812],{"__ignoreMap":34},[500,2813,2814],{"class":502,"line":503},[500,2815,2809],{},[18,2817,2818,2819,2822,2823,1437],{},"This will automatically create tracking event processors for your event handlers. Axon uses a token store to determine\nwhether there are events that need to be processed. This token store will be checked regulary. If you’re using the Axon\nJPA provider there is already an entity class ",[405,2820,2821],{},"TokenEntry"," available. You have to tell your JPA provider where it is\nlocated. In Spring Boot this can be done with ",[405,2824,2825],{},"@EntityScan",[492,2827,2829],{"className":1751,"code":2828,"language":1753,"meta":34,"style":34}," @EntityScan(\n basePackages = {\n ...,\n \"org.axonframework.eventhandling.tokenstore.jpa\"\n }\n )\n",[405,2830,2831,2836,2841,2846,2851,2855],{"__ignoreMap":34},[500,2832,2833],{"class":502,"line":503},[500,2834,2835],{}," @EntityScan(\n",[500,2837,2838],{"class":502,"line":98},[500,2839,2840],{}," basePackages = {\n",[500,2842,2843],{"class":502,"line":249},[500,2844,2845],{}," ...,\n",[500,2847,2848],{"class":502,"line":583},[500,2849,2850],{}," \"org.axonframework.eventhandling.tokenstore.jpa\"\n",[500,2852,2853],{"class":502,"line":615},[500,2854,1527],{},[500,2856,2857],{"class":502,"line":621},[500,2858,2859],{}," )\n",[18,2861,2862],{},"This is everything needed to configure your application to be able to do event replaying.",[18,2864,2865,2866,2869],{},"In order to trigger an event replay in production you just need to delete the tracking tokens associated with an event\nprocessor from the",[405,2867,2868],{},"token_entry"," table. The corresponding event processor then pulls all events from the event store.",[18,2871,2872,2873,2878],{},"This is a really simple configuration. It’s also possible to configure seperate tracking event processors for different\nreplaying use cases. See\nthe ",[22,2874,2877],{"href":2875,"rel":2876},"http://www.axonframework.org/apidocs/3.0/org/axonframework/config/EventHandlingConfiguration.html",[26],"EventHandlingConfiguration","\nclass for more detail.",[335,2880,2882],{"id":2881},"common-pitfalls","Common pitfalls",[18,2884,2885],{},"Event replaying is a very useful technique but you have to be aware of some common pitfalls.",[133,2887,2889],{"id":2888},"replaying-events-to-external-services","Replaying events to external services",[18,2891,2892],{},"Make sure that replayed events never get processed accidentally by the wrong event handlers. For example events might be\ntransformed into messages and published to a messaging middleware, and eventually consumed by external systems. In this\ncase you want to exclude the corresponding event handlers from event replaying because messages should only be published\nonce.",[133,2894,2896],{"id":2895},"eventual-consistency","Eventual Consistency",[18,2898,2899],{},"If you’ve used the default subscribing event processors and switch to tracking event processors remember that they are\npulling for events in a thread managed on their own. If your user interface relies on everything being handled in one\nthread, the user interface will break now. In this case, consider using subscribing event processors for\nbusiness-as-usual actions and only use tracking ones if you intent to trigger an event replay. This requires some more\nelaborate configuration for when event replaying is executed. Another option could be to let your user interface pull\nfor updates.",[133,2901],{"id":34},[335,2903,2905],{"id":2904},"conclusion","Conclusion",[18,2907,2908],{},"Event replaying in Axon 3 is supported by tracking event processors: they keep track of the last position in the event\nlog. In order to replay all events all you have to do is resetting the corresponding tracking token.",[1017,2910,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":2912},[2913,2918,2919,2924],{"id":2746,"depth":98,"text":2747,"children":2914},[2915,2916,2917],{"id":2760,"depth":249,"text":2761},{"id":2767,"depth":249,"text":2768},{"id":2774,"depth":249,"text":2775},{"id":2785,"depth":98,"text":2786},{"id":2881,"depth":98,"text":2882,"children":2920},[2921,2922,2923],{"id":2888,"depth":249,"text":2889},{"id":2895,"depth":249,"text":2896},{"id":34,"depth":249,"text":34},{"id":2904,"depth":98,"text":2905},[1029],"2017-03-01T08:14:51","Axon is a lightweight framework that supports the implemenation of CQRS patterns by\\nproviding commonly used building blocks. One of those patterns is an event sourced application architecture. Even though\\nEvent Sourcing and CQRS are orthogonal concepts they fit together very well and are often used together. Event sourcing\\nin an ES/CQRS architecture means that all changes to the\\napplication state are done via domain events and the current state can always be rebuilt from the series of events\\navailable in a persistent event store. In addition to the event store there might also be one or more read models, for\\nexample to achieve opimtized query performance. No matter if there are read models or not, the event store is considered\\nthe single source of truth.","https://synyx.de/blog/axon-3-event-replaying/",{},"/blog/axon-3-event-replaying",{"title":2721,"description":2932},"Axon is a lightweight framework that supports the implemenation of CQRS patterns by\nproviding commonly used building blocks. One of those patterns is an event sourced application architecture. Even though\nEvent Sourcing and CQRS are orthogonal concepts they fit together very well and are often used together. Event sourcing\nin an ES/CQRS architecture means that all changes to the\napplication state are done via domain events and the current state can always be rebuilt from the series of events\navailable in a persistent event store. In addition to the event store there might also be one or more read models, for\nexample to achieve opimtized query performance. No matter if there are read models or not, the event store is considered\nthe single source of truth.","blog/axon-3-event-replaying",[2935,2936,2937,869],"axon","cqrs","event-sourcing","Axon is a lightweight framework that supports the implemenation of CQRS patterns by providing commonly used building blocks. One of those patterns is an event sourced application architecture. Even…","EsUmpQAdRuDUdhFKW4wvztcDCze9dI5vCmw1_n2XNcA",{"id":2941,"title":2942,"author":2943,"body":2945,"category":3345,"date":3346,"description":2952,"extension":104,"link":3347,"meta":3348,"navigation":107,"path":3349,"seo":3350,"slug":2949,"stem":3351,"tags":3352,"teaser":3356,"__hash__":3357},"blog/blog/bean-x-of-type-y-is-not-eligible-for-getting-processed-by-all-beanpostprocessors.md","Bean X of type Y is not eligible for getting processed by all BeanPostProcessors",[2944],"kannegiesser",{"type":11,"value":2946,"toc":3337},[2947,2950,2953,2957,2960,2963,3005,3025,3030,3039,3045,3061,3065,3072,3096,3103,3107,3110,3122,3126,3129,3140,3147,3150,3156,3169,3197,3200,3204,3207,3235,3238,3289,3310,3319,3322,3326,3329,3332,3335],[14,2948,2942],{"id":2949},"bean-x-of-type-y-is-not-eligible-for-getting-processed-by-all-beanpostprocessors",[18,2951,2952],{},"Recently we had a problem related Springs auto-proxy feature that I think is worth writing about.",[335,2954,2956],{"id":2955},"the-problem","The Problem",[18,2958,2959],{},"We use Spring as our framework of choice because it provides us with a nice set of convenience features when\nbootstrapping and plugging together our application.",[18,2961,2962],{},"One of these features is caching: We cache our users’ roles because their definitions are stored in a pretty slow\nexternal system and change rarely.",[492,2964,2966],{"className":867,"code":2965,"language":869,"meta":34,"style":34},"\n@Component\npublic class RoleRepository {\n @Cacheable(CacheConfig.ROLES_NAME)\n public Set\u003CRole> loadRoles() {\n // .. slow call to external system\n }\n}\n\n",[405,2967,2968,2972,2977,2982,2987,2992,2997,3001],{"__ignoreMap":34},[500,2969,2970],{"class":502,"line":503},[500,2971,644],{"emptyLinePlaceholder":107},[500,2973,2974],{"class":502,"line":98},[500,2975,2976],{},"@Component\n",[500,2978,2979],{"class":502,"line":249},[500,2980,2981],{},"public class RoleRepository {\n",[500,2983,2984],{"class":502,"line":583},[500,2985,2986],{}," @Cacheable(CacheConfig.ROLES_NAME)\n",[500,2988,2989],{"class":502,"line":615},[500,2990,2991],{}," public Set\u003CRole> loadRoles() {\n",[500,2993,2994],{"class":502,"line":621},[500,2995,2996],{}," // .. slow call to external system\n",[500,2998,2999],{"class":502,"line":631},[500,3000,797],{},[500,3002,3003],{"class":502,"line":641},[500,3004,802],{},[18,3006,3007,3008,3011,3012,3015,3016,3021,3022,3024],{},"Since our ",[405,3009,3010],{},"RoleRepository"," is a component managed by Spring it gets picked up automatically during boot and\n",[405,3013,3014],{},"loadRoles()"," gets backed by a cache. Spring implements this by proxying our repository.\nSee ",[22,3017,3020],{"href":3018,"rel":3019},"http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html",[26],"Spring Reference Documentation","\nfor details. As a result the “real” ",[405,3023,3014],{}," method gets triggered only once in 10 minutes and all the other calls\nare served from the cache.",[18,3026,3027,3028,707],{},"After several sprints we noticed a problem with the caching. While in caching worked for other beans it stopped working\nfor the shown ",[405,3029,3010],{},[18,3031,3032,3033,3035,3036,3038],{},"We noticed this because our health-check which (indirectly) triggers the ",[405,3034,3014],{}," and runs every 5 seconds did not\nhit the cache and therefore produced a log-entry every 5 seconds. The cache for roles was empty, regardless of how\noften ",[405,3037,3014],{}," was called.",[18,3040,3041,3042,3044],{},"While debugging the issue we found out that the proxy that should do the caching was not generated for ",[405,3043,3010],{},".\nDuring bootstrap of the application Spring gave us a corresponding hint:",[492,3046,3050],{"className":3047,"code":3048,"language":3049,"meta":34,"style":34},"language-plaintext shiki shiki-themes github-light github-dark","\nINFO 7189 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'RoleRepository' of type [class ...RoleRepository] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)\n\n","plaintext",[405,3051,3052,3056],{"__ignoreMap":34},[500,3053,3054],{"class":502,"line":503},[500,3055,644],{"emptyLinePlaceholder":107},[500,3057,3058],{"class":502,"line":98},[500,3059,3060],{},"INFO 7189 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'RoleRepository' of type [class ...RoleRepository] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)\n",[335,3062,3064],{"id":3063},"what-is-a-beanpostprocessor","What is a BeanPostProcessor",[18,3066,3067,3068,3071],{},"A ",[405,3069,3070],{},"BeanPostProcessor"," is a special component that allows to manipulate other beans after they are created.",[18,3073,3074,3075,3078,3079,3082,3083,719,3086,719,3089,3092,3093,3095],{},"These postprocessors can manipulate beans or even replace them completely. Spring ships with several implementations\nthat do all kinds of stuff. For example there is one that checks if a bean implements ",[405,3076,3077],{},"ApplicationContextAware"," and sets\nthe ",[405,3080,3081],{},"ApplicationContext"," if so. Also many of the “proxy-stuff” for ",[405,3084,3085],{},"@Async",[405,3087,3088],{},"@Transactional",[405,3090,3091],{},"@Caching"," and so on is\ndone using a ",[405,3094,3070],{},". You can also implement your own postprocessors.",[18,3097,3098,3099,3102],{},"To be able to postprocess all beans with all ",[405,3100,3101],{},"BeanPostProcessors"," Spring has to create them before it creates the\n“regular” beans.",[335,3104,3106],{"id":3105},"the-chicken-and-the-egg","The Chicken and the Egg",[18,3108,3109],{},"But you can also inject Spring beans into postprocessors. If you inject a bean into a postprocessor Spring has to create\nthis bean even before it creates the corresponding postprocessor you inject it to. In this case it no longer guarantees\nthat all postprocessors are able to process the injected bean but instead logs the message shown above.",[18,3111,3112,3113,3115,3116,3119,3120,707],{},"In our example the log-message means that there is one postprocessor that (directly or indirectly) leads to creation of\nour ",[405,3114,3010],{}," and there are more postprocessors to be created later (probaly the one that handles ",[405,3117,3118],{},"@Cachable",")\nthat will not be able to post-process our ",[405,3121,3010],{},[335,3123,3125],{"id":3124},"debugging-the-problem","Debugging the Problem",[18,3127,3128],{},"Unfortunately the log-entry does not help very much in finding out what exactly is the problem.",[18,3130,3131,3132,3135,3136,3139],{},"The statement is produced by ",[405,3133,3134],{},"BeanPostProcessorChecker"," which is an inner class in ",[405,3137,3138],{},"PostProcessorRegistrationDelegate",".\nTo find out what exactly happens here I set a breakpoint right at the log-statement and booted our application in\ndebug-mode. Since there are several beans created I waited for the statement in question (in our case the one with\nbeanName = roleRepository).",[18,3141,3142,3143,3146],{},"To find out what caused the creation of our roleRepository I simply followed the call-stack down the ",[405,3144,3145],{},"getObject","\nmethods. In our case the chain was:",[18,3148,3149],{},"roleRepository -> authorityService -> customerPermissionEvaluator -> delegatingPermissionEvaluator ->\ncustomMethodSecurityConfiguration -> methodSecurityMetadataSource -> (… more Spring setup code …)",[18,3151,3152],{},[32,3153],{"alt":3154,"src":3155},"\"debugging_getobject\"","https://media.synyx.de/uploads//2016/11/debugging_getObject.png",[18,3157,3158,3159,3161,3162,3165,3166,1437],{},"So the ",[405,3160,3010],{}," is created because it is needed by our custom implementation of Spring Securities\n",[405,3163,3164],{},"PermissionEvaluator","-Interface that is used to evaluate security related expressions like the ones that can be used\nwith ",[405,3167,3168],{},"@PreAuthorize",[492,3170,3172],{"className":867,"code":3171,"language":869,"meta":34,"style":34},"\n@PreAuthorize(\"hasPermission(#customer, AUTHORITY_BILLING)\")\npublic Optional\u003CBillingDoc> findById(Customer customer, String documentId) {\n // boring business logic here\n}\n\n",[405,3173,3174,3178,3183,3188,3193],{"__ignoreMap":34},[500,3175,3176],{"class":502,"line":503},[500,3177,644],{"emptyLinePlaceholder":107},[500,3179,3180],{"class":502,"line":98},[500,3181,3182],{},"@PreAuthorize(\"hasPermission(#customer, AUTHORITY_BILLING)\")\n",[500,3184,3185],{"class":502,"line":249},[500,3186,3187],{},"public Optional\u003CBillingDoc> findById(Customer customer, String documentId) {\n",[500,3189,3190],{"class":502,"line":583},[500,3191,3192],{}," // boring business logic here\n",[500,3194,3195],{"class":502,"line":615},[500,3196,802],{},[18,3198,3199],{},"So when Spring Security is bootstrapped (which seems to be done before caching is) it also initializes a part of our\n“business-beans” which then cannot be postprocessed with caching.",[335,3201,3203],{"id":3202},"the-fix","The Fix",[18,3205,3206],{},"To fix the problem I cut the eager dependency from our security-code and replaced it by a lazy one.",[492,3208,3210],{"className":867,"code":3209,"language":869,"meta":34,"style":34},"\n@Autowired\npublic CustomerPermissionEvaluator(AuthorityService authorityService) {\n this.authorityService = authorityService;\n}\n\n",[405,3211,3212,3216,3221,3226,3231],{"__ignoreMap":34},[500,3213,3214],{"class":502,"line":503},[500,3215,644],{"emptyLinePlaceholder":107},[500,3217,3218],{"class":502,"line":98},[500,3219,3220],{},"@Autowired\n",[500,3222,3223],{"class":502,"line":249},[500,3224,3225],{},"public CustomerPermissionEvaluator(AuthorityService authorityService) {\n",[500,3227,3228],{"class":502,"line":583},[500,3229,3230],{}," this.authorityService = authorityService;\n",[500,3232,3233],{"class":502,"line":615},[500,3234,802],{},[18,3236,3237],{},"was changed to",[492,3239,3241],{"className":867,"code":3240,"language":869,"meta":34,"style":34},"\n@Autowired\npublic CustomerPermissionEvaluator(ObjectFactory\u003CAuthorityService> authorityServiceObjectFactory) {\n this.authorityServiceObjectFactory = authorityServiceObjectFactory;\n}\n@Override\npublic boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {\n AuthorityService service = this.authorityServiceObjectFactory.getObject();\n // ... do stuff with service\n}\n\n",[405,3242,3243,3247,3251,3256,3261,3265,3270,3275,3280,3285],{"__ignoreMap":34},[500,3244,3245],{"class":502,"line":503},[500,3246,644],{"emptyLinePlaceholder":107},[500,3248,3249],{"class":502,"line":98},[500,3250,3220],{},[500,3252,3253],{"class":502,"line":249},[500,3254,3255],{},"public CustomerPermissionEvaluator(ObjectFactory\u003CAuthorityService> authorityServiceObjectFactory) {\n",[500,3257,3258],{"class":502,"line":583},[500,3259,3260],{}," this.authorityServiceObjectFactory = authorityServiceObjectFactory;\n",[500,3262,3263],{"class":502,"line":615},[500,3264,802],{},[500,3266,3267],{"class":502,"line":621},[500,3268,3269],{},"@Override\n",[500,3271,3272],{"class":502,"line":631},[500,3273,3274],{},"public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {\n",[500,3276,3277],{"class":502,"line":641},[500,3278,3279],{}," AuthorityService service = this.authorityServiceObjectFactory.getObject();\n",[500,3281,3282],{"class":502,"line":647},[500,3283,3284],{}," // ... do stuff with service\n",[500,3286,3287],{"class":502,"line":805},[500,3288,802],{},[18,3290,3291,3292,3295,3296,3299,3300,3303,3304,3307,3308,352],{},"By using Springs ",[405,3293,3294],{},"ObjectFactory"," and calling its ",[405,3297,3298],{},"getObject()"," later (not during construction of the\n",[405,3301,3302],{},"CustomerPermissionEvaluator",") we delay the creation of ",[405,3305,3306],{},"AuthorityService"," (and of beans it needs such as the\n",[405,3309,3010],{},[18,3311,3312,3313,3315,3316,707],{},"By doing so they can later be processed by all ",[405,3314,3101],{}," and the log message in question disappeares &\ncaching works again ",[469,3317,3318],{},"o/",[18,3320,3321],{},"Note that there are other ways to solve the problem. One other thing I can think of is to change the order the\nPostProcessors are created.",[335,3323,3325],{"id":3324},"final-thougths","Final Thougths",[18,3327,3328],{},"In conclusion you might want to actively watch the statements Spring logs at INFO and above during your applications\nbootstrap.",[18,3330,3331],{},"Especially the mentioned statement about beans being not eligible for auto-proxying should not contain any of your\nnon-infrastructure beans.",[18,3333,3334],{},"Also make sure there is no eager dependency between your infrastructure-related code and your business-logic. At least\ndouble-check and challenge these decisions. This is especially true because dependency-graphs get pretty big pretty\nfast. In our example there were 20 other “business” beans not eligable for auto-proxying because of the same eager\ndependency.",[1017,3336,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":3338},[3339,3340,3341,3342,3343,3344],{"id":2955,"depth":98,"text":2956},{"id":3063,"depth":98,"text":3064},{"id":3105,"depth":98,"text":3106},{"id":3124,"depth":98,"text":3125},{"id":3202,"depth":98,"text":3203},{"id":3324,"depth":98,"text":3325},[1029],"2016-11-04T09:45:17","https://synyx.de/blog/bean-x-of-type-y-is-not-eligible-for-getting-processed-by-all-beanpostprocessors/",{},"/blog/bean-x-of-type-y-is-not-eligible-for-getting-processed-by-all-beanpostprocessors",{"title":2942,"description":2952},"blog/bean-x-of-type-y-is-not-eligible-for-getting-processed-by-all-beanpostprocessors",[3353,1039,869,3354,3355],"bug","proxy","spring","Recently we had a problem related Springs auto-proxy feature that I think is worth writing about. The Problem We use Spring as our framework of choice because it provides us…","g_18Z38wUIExfcwJbi-RkuL033DdE2J3_WTwAuhdGNo",{"id":3359,"title":3360,"author":3361,"body":3362,"category":4838,"date":4839,"description":4840,"extension":104,"link":4841,"meta":4842,"navigation":107,"path":4843,"seo":4844,"slug":3366,"stem":4846,"tags":4847,"teaser":4850,"__hash__":4851},"blog/blog/javascript-code-refactoring-automatisieren.md","JavaScript Code Refactoring automatisieren",[275],{"type":11,"value":3363,"toc":4829},[3364,3367,3375,3395,3401,3405,3414,3421,3424,3440,3444,3447,3452,3460,3465,3468,3473,3476,3479,3487,3490,3494,3497,3514,3517,3521,3524,3535,3632,3635,3662,3665,3720,3723,3726,3755,3758,3761,3860,3901,3904,3918,3921,3946,3952,3998,4010,4113,4133,4145,4152,4204,4207,4221,4224,4238,4241,4244,4247,4267,4277,4410,4418,4424,4551,4554,4576,4582,4683,4686,4689,4701,4704,4718,4721,4725,4734,4737,4754,4798,4800,4803,4818,4823,4826],[14,3365,3360],{"id":3366},"javascript-code-refactoring-automatisieren",[18,3368,3369,3370,3374],{},"Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\nArt von asynchronen Specs und einmal die verwendeten Expectations.\nUnter ",[22,3371,3372],{"href":3372,"rel":3373},"http://jasmine.github.io/2.0/upgrading.html",[26]," wurde super\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.",[18,3376,3377,3378,297,3381,3384,3385,3388,3389,3394],{},"In diesem Artikel möchte ich zeigen, wie ich die Transformation der ",[405,3379,3380],{},"runs",[405,3382,3383],{},"waitsFor"," Blöcke zum neuen ",[405,3386,3387],{},"done","\nCallback Muster mittels ",[22,3390,3393],{"href":3391,"rel":3392},"https://github.com/facebook/jscodeshift",[26],"jscodeshift"," automatisiert habe.",[18,3396,3397],{},[32,3398],{"alt":3399,"src":3400},"jasmine_async_vergleich","https://media.synyx.de/uploads//2016/08/jasmine_async_vergleich-1024x469.png",[335,3402,3404],{"id":3403},"jscodeshift-recast-esprima-codemods","jscodeshift / recast / esprima / codemods",[18,3406,3407,3408,3413],{},"Jscodeshift ist ein Werkzeug dass von Facebook gebaut wurde und ",[22,3409,3412],{"href":3410,"rel":3411},"https://github.com/benjamn/recast",[26],"recast"," erweitert.\nDieses wiederum arbeitet mit dem Esprima Parser. Dieser baut einen abstrakten Syntaxbaum (engl. abstract source tree,\nAST) auf, der traversiert werden kann.",[18,3415,3416,3417,3420],{},"Mit jscodeshift ist es z. B. möglich, alle anonyme Funktionen herauszuholen und mit einem Namen ",[168,3418,3419],{},"ichBinNichtMehrAnonym","\nzu ersetzen. Ein weiteres, nettes Feature wie ich finde, ist die Beibehaltung der originalen Code Formatierung (so weit\nmöglich).",[18,3422,3423],{},"Man baut also ein Codeschnipsel welches anderen Code umschreibt. Dieses Codeschnipsel wird codemod genannt. Eine\nschnelle Google Suche bringt uns zu zwei interessanten Artikeln; die für das Folgende aber nicht unbedingt gelesen\nwerden müssen 🙂",[343,3425,3426,3433],{},[346,3427,3428],{},[22,3429,3432],{"href":3430,"rel":3431},"https://vramana.github.io/blog/2015/12/21/codemod-tutorial/",[26],"How to write a codemod",[346,3434,3435],{},[22,3436,3439],{"href":3437,"rel":3438},"https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb",[26],"Effective JavaScript Codemods",[335,3441,3443],{"id":3442},"automatisieren-von-code-refactorings","Automatisieren von Code Refactorings",[18,3445,3446],{},"Warum automatisieren mag sich der ein oder andere denken. Das kann mehrere Gründe haben. Zum einen hat man vielleicht\nkeinen Auszubildenden zur Verfügung der die Tests umschreiben kann, zum anderen… Nein… Auszubildende bitte mit\njscodeshift ersetzen. Also für die Arbeit des Refactorings… Aber zurück zu den Gründen der Automatisierung.",[18,3448,3449],{},[469,3450,3451],{},"Spaß",[18,3453,3454,3455,3459],{},"Ich musste kurz überlegen wann ich ein Refactoring in JavaScript Projekten gemacht habe, bei dem ich auch Spaß hatte.\nMir fiel keines ein. Die Arbeit ",[3456,3457,3458],"del",{},"war"," ist stupide: Suchen und Ersetzen. Für das automatisierte Code Refactoring wird\nein Code Schnipsel geschrieben welches das Suchen und Ersetzen für mich übernimmt! Ich darf Code hacken!",[18,3461,3462],{},[469,3463,3464],{},"Zuverlässigkeit",[18,3466,3467],{},"Ein generelles Argument zum Automatisieren trifft denke ich auch bei Code Refactoring zu. Es wird immer zu jeder Zeit an\njeder Stelle das selbe gemacht. Es gibt keinen Finger der auf der Tastatur um eine Taste verrutscht. Es gibt keine\nUnachtsamkeit die zum Vergessen einer Stelle führt. Die Maschine erledigt zuverlässig was getan werden soll. Immer.\nJederzeit. Überall.",[18,3469,3470],{},[469,3471,3472],{},"Effektivität",[18,3474,3475],{},"Ist etwas automatisiert gilt es nur noch ein Knöpfchen zu drücken. Bezogen auf das Code Refactoring kann das Ergebnis in\nwenigen (Milli-)Sekunden bestaunt werden. Hier spreche ich aber noch nicht konkret von codemods und jscodeshift als\nWerkzeug. Eine Regex kann hier auch schon völlig ausreichen.",[18,3477,3478],{},"Eine Regex zum",[343,3480,3481,3484],{},[346,3482,3483],{},"löschen von Abschnitten wenn Bedingung A zutrifft",[346,3485,3486],{},"verschieben von Code Blöcken",[18,3488,3489],{},"kann entweder einmal geschrieben und nie wieder verstanden werden, oder ist gar unmöglich zu schreiben. Hier kommt\njscodeshift mit codemods zur Rettung.",[335,3491,3493],{"id":3492},"codemods-zum-upgrade-von-jasmine-1x-auf-2x","Codemods zum Upgrade von Jasmine 1.x auf 2.x",[18,3495,3496],{},"Eine codemod ist ein Codeschnipsel welches vorhandenen Quellcode transformiert. Im unserem Fall der Jasmine Migrierung\nvon Version 1.x zu 2.x müssen transformiert werden:",[343,3498,3499,3502,3505,3508,3511],{},[346,3500,3501],{},"spies",[346,3503,3504],{},"asynchrone Tests",[346,3506,3507],{},"expectations",[346,3509,3510],{},"custom matchers (falls vorhanden)",[346,3512,3513],{},"clock",[18,3515,3516],{},"Wir wenden uns folgend den asynchronen Tests zu.",[133,3518,3520],{"id":3519},"asynchrone-tests-transformieren","Asynchrone Tests transformieren",[18,3522,3523],{},"Das Projekt das es zu refactored galt hatte überwiegend sehr einfach geschriebene asynchrone Tests. Perfekt für den\nEinstieg in jscodeshift.",[18,3525,3526,3527,3530,3531,3534],{},"Eine Variable die initial ",[405,3528,3529],{},"false"," ist und nach $Aktion auf ",[405,3532,3533],{},"true"," gesetzt wird. Die Assertions werden dann erst\nausgeführt, wenn die Variable gesetzt wurde.",[492,3536,3540],{"className":3537,"code":3538,"language":3539,"meta":34,"style":34},"language-js shiki shiki-themes github-light github-dark","it(\"tests something async\", function () {\n var done;\n doSomethingAsync(function callback() {\n // assertions\n done = true;\n });\n waitsFor(function () {\n return done;\n });\n});\n","js",[405,3541,3542,3560,3568,3582,3588,3601,3606,3617,3624,3628],{"__ignoreMap":34},[500,3543,3544,3547,3549,3552,3554,3557],{"class":502,"line":503},[500,3545,3546],{"class":513},"it",[500,3548,713],{"class":506},[500,3550,3551],{"class":520},"\"tests something async\"",[500,3553,719],{"class":506},[500,3555,3556],{"class":677},"function",[500,3558,3559],{"class":506}," () {\n",[500,3561,3562,3565],{"class":502,"line":98},[500,3563,3564],{"class":677}," var",[500,3566,3567],{"class":506}," done;\n",[500,3569,3570,3573,3575,3577,3580],{"class":502,"line":249},[500,3571,3572],{"class":513}," doSomethingAsync",[500,3574,713],{"class":506},[500,3576,3556],{"class":677},[500,3578,3579],{"class":513}," callback",[500,3581,698],{"class":506},[500,3583,3584],{"class":502,"line":583},[500,3585,3587],{"class":3586},"sJ8bj"," // assertions\n",[500,3589,3590,3593,3595,3598],{"class":502,"line":615},[500,3591,3592],{"class":506}," done ",[500,3594,517],{"class":677},[500,3596,3597],{"class":703}," true",[500,3599,3600],{"class":506},";\n",[500,3602,3603],{"class":502,"line":621},[500,3604,3605],{"class":506}," });\n",[500,3607,3608,3611,3613,3615],{"class":502,"line":631},[500,3609,3610],{"class":513}," waitsFor",[500,3612,713],{"class":506},[500,3614,3556],{"class":677},[500,3616,3559],{"class":506},[500,3618,3619,3622],{"class":502,"line":641},[500,3620,3621],{"class":677}," return",[500,3623,3567],{"class":506},[500,3625,3626],{"class":502,"line":647},[500,3627,3605],{"class":506},[500,3629,3630],{"class":502,"line":805},[500,3631,841],{"class":506},[18,3633,3634],{},"Für jasmine 2.x müssen wir also",[343,3636,3637,3646,3656],{},[346,3638,3639,3640,3642,3643,3645],{},"einmal der Funktion die dem ",[405,3641,3546],{}," übergeben wird einen Parameter ",[405,3644,3387],{}," hinzufügen",[346,3647,3648,3651,3652,3655],{},[405,3649,3650],{},"done = true;"," mit ",[405,3653,3654],{},"done();"," ersetzen",[346,3657,3658,3659,3661],{},"und den ",[405,3660,3383],{}," Block löschen",[18,3663,3664],{},"Nach dem Refactoring soll das Ganze also wie folgt aussehen:",[492,3666,3668],{"className":3537,"code":3667,"language":3539,"meta":34,"style":34},"it(\"tests something async\", function (done) {\n doSomethingAsync(function callback() {\n // assertions\n done();\n });\n});\n",[405,3669,3670,3689,3701,3705,3712,3716],{"__ignoreMap":34},[500,3671,3672,3674,3676,3678,3680,3682,3684,3686],{"class":502,"line":503},[500,3673,3546],{"class":513},[500,3675,713],{"class":506},[500,3677,3551],{"class":520},[500,3679,719],{"class":506},[500,3681,3556],{"class":677},[500,3683,725],{"class":506},[500,3685,3387],{"class":728},[500,3687,3688],{"class":506},") {\n",[500,3690,3691,3693,3695,3697,3699],{"class":502,"line":98},[500,3692,3572],{"class":513},[500,3694,713],{"class":506},[500,3696,3556],{"class":677},[500,3698,3579],{"class":513},[500,3700,698],{"class":506},[500,3702,3703],{"class":502,"line":249},[500,3704,3587],{"class":3586},[500,3706,3707,3710],{"class":502,"line":583},[500,3708,3709],{"class":513}," done",[500,3711,748],{"class":506},[500,3713,3714],{"class":502,"line":615},[500,3715,3605],{"class":506},[500,3717,3718],{"class":502,"line":621},[500,3719,841],{"class":506},[3721,3722,3393],"h4",{"id":3393},[18,3724,3725],{},"Bevor wir loslegen können, müssen noch wenige Dinge erledigt werden.",[492,3727,3729],{"className":3047,"code":3728,"language":3049,"meta":34,"style":34},"\n$> npm install -g jscodeshift\n$> mkdir jasmineCodemods && cd jasmineCodemods\n$> git init && git commit -m \"initial commit\" --allow-empty\n$> touch jasmine-async.js\n\n",[405,3730,3731,3735,3740,3745,3750],{"__ignoreMap":34},[500,3732,3733],{"class":502,"line":503},[500,3734,644],{"emptyLinePlaceholder":107},[500,3736,3737],{"class":502,"line":98},[500,3738,3739],{},"$> npm install -g jscodeshift\n",[500,3741,3742],{"class":502,"line":249},[500,3743,3744],{},"$> mkdir jasmineCodemods && cd jasmineCodemods\n",[500,3746,3747],{"class":502,"line":583},[500,3748,3749],{},"$> git init && git commit -m \"initial commit\" --allow-empty\n",[500,3751,3752],{"class":502,"line":615},[500,3753,3754],{},"$> touch jasmine-async.js\n",[18,3756,3757],{},"Der Einfachkeit halber installieren wir jscodeshift global um das binary auf der Konsole ausführen zu können. Und das\nGit Repo zum einfachen hacken, sichern und zurückrollen darf auch nicht fehlen!",[18,3759,3760],{},"Dann legen wir uns eine Datei für der/die/das erste codemod an und schreiben folgenden Inhalt:",[492,3762,3764],{"className":3537,"code":3763,"language":3539,"meta":34,"style":34},"// jasmine-async.js\nmodule.exports = function transformer(file, api) {\n const j = api.jscodeshift;\n const { statement } = j.template;\n const root = j(file.source);\n return root;\n};\n",[405,3765,3766,3771,3802,3815,3833,3847,3855],{"__ignoreMap":34},[500,3767,3768],{"class":502,"line":503},[500,3769,3770],{"class":3586},"// jasmine-async.js\n",[500,3772,3773,3776,3778,3781,3784,3787,3790,3792,3795,3797,3800],{"class":502,"line":98},[500,3774,3775],{"class":703},"module",[500,3777,707],{"class":506},[500,3779,3780],{"class":703},"exports",[500,3782,3783],{"class":677}," =",[500,3785,3786],{"class":677}," function",[500,3788,3789],{"class":513}," transformer",[500,3791,713],{"class":506},[500,3793,3794],{"class":728},"file",[500,3796,719],{"class":506},[500,3798,3799],{"class":728},"api",[500,3801,3688],{"class":506},[500,3803,3804,3807,3810,3812],{"class":502,"line":249},[500,3805,3806],{"class":677}," const",[500,3808,3809],{"class":703}," j",[500,3811,3783],{"class":677},[500,3813,3814],{"class":506}," api.jscodeshift;\n",[500,3816,3817,3819,3822,3825,3828,3830],{"class":502,"line":583},[500,3818,3806],{"class":677},[500,3820,3821],{"class":506}," { ",[500,3823,3824],{"class":703},"statement",[500,3826,3827],{"class":506}," } ",[500,3829,517],{"class":677},[500,3831,3832],{"class":506}," j.template;\n",[500,3834,3835,3837,3840,3842,3844],{"class":502,"line":615},[500,3836,3806],{"class":677},[500,3838,3839],{"class":703}," root",[500,3841,3783],{"class":677},[500,3843,3809],{"class":513},[500,3845,3846],{"class":506},"(file.source);\n",[500,3848,3849,3852],{"class":502,"line":621},[500,3850,3851],{"class":677}," return",[500,3853,3854],{"class":506}," root;\n",[500,3856,3857],{"class":502,"line":631},[500,3858,3859],{"class":506},"};\n",[18,3861,3862,3863,3866,3867,719,3870,719,3873,719,3876,3879,3880,3883,3884,180,3889,3894,3895,3900],{},"Auf ",[405,3864,3865],{},"root"," können wir jetzt jscodeshift Methoden aufrufen wie ",[405,3868,3869],{},"find",[405,3871,3872],{},"filter",[405,3874,3875],{},"forEach",[405,3877,3878],{},"replaceWith"," und zuletzt\n",[405,3881,3882],{},"toSource",". Die Methoden machen genau das was der Name sagt, selbsterklärend. Genaueres muss man sich leider selbst\nim ",[22,3885,3888],{"href":3886,"rel":3887},"https://github.com/facebook/jscodeshift/blob/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/Collection.js",[26],"Source",[22,3890,3893],{"href":3891,"rel":3892},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/collections",[26],"Code","\nauf ",[22,3896,3899],{"href":3897,"rel":3898},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b",[26],"Github"," zusammenkratzen.",[18,3902,3903],{},"Ausführen können wir das Skript später mit",[492,3905,3907],{"className":3047,"code":3906,"language":3049,"meta":34,"style":34},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n\n",[405,3908,3909,3913],{"__ignoreMap":34},[500,3910,3911],{"class":502,"line":503},[500,3912,644],{"emptyLinePlaceholder":107},[500,3914,3915],{"class":502,"line":98},[500,3916,3917],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n",[18,3919,3920],{},"Doch zuerst müssen Transformationen gecoded werden 😮",[18,3922,3923,3924,3926,3927,3929,3930,3933,3934,3936,3937,3939,3940,3945],{},"Wir wollen fürs erste alle ",[405,3925,3546],{}," Knoten finden und der übergebenen Funktion einen ",[405,3928,3387],{}," Parameter spendieren. Zum Suchen\nvon Ausdrücken verwenden wir die jscodeshift Methode ",[405,3931,3932],{},"root.find",". Diese traversiert den AST und gibt uns eine Collection\nvon passenden Knoten zurück. Als Argument müssen wird dem ",[405,3935,3869],{}," Aufruf eine AST Beschreibung des ",[405,3938,3546],{}," Knotens mitgeben.\nBeim Finden der Beschreibung hilft uns der geniale ",[22,3941,3944],{"href":3942,"rel":3943},"https://astexplorer.net",[26],"astexplorer.net",". Wir kopieren den Code den\nwir transformieren wollen in den Editor und bekommen den AST ausgespuckt. Wir können sogar auf jeden beliebigen Knoten\nim Editor klicken und bekommen im AST den enstsprechenden Teil markiert!",[18,3947,3948],{},[32,3949],{"alt":3950,"src":3951},"astexplorer","https://media.synyx.de/uploads//2016/08/astexplorer-1024x631.png",[492,3953,3955],{"className":3537,"code":3954,"language":3539,"meta":34,"style":34},"// jasmine-async.js\nreturn root.find(j.CallExpression, {\n callee: {\n name: \"it\",\n },\n});\n",[405,3956,3957,3961,3974,3979,3989,3994],{"__ignoreMap":34},[500,3958,3959],{"class":502,"line":503},[500,3960,3770],{"class":3586},[500,3962,3963,3966,3969,3971],{"class":502,"line":98},[500,3964,3965],{"class":677},"return",[500,3967,3968],{"class":506}," root.",[500,3970,3869],{"class":513},[500,3972,3973],{"class":506},"(j.CallExpression, {\n",[500,3975,3976],{"class":502,"line":249},[500,3977,3978],{"class":506}," callee: {\n",[500,3980,3981,3984,3987],{"class":502,"line":583},[500,3982,3983],{"class":506}," name: ",[500,3985,3986],{"class":520},"\"it\"",[500,3988,835],{"class":506},[500,3990,3991],{"class":502,"line":615},[500,3992,3993],{"class":506}," },\n",[500,3995,3996],{"class":502,"line":621},[500,3997,841],{"class":506},[18,3999,4000,4001,4003,4004,4006,4007,4009],{},"Dann wollen wir für alle Knoten die gefunden werden etwas tun. Nämlich den ",[405,4002,3387],{}," Parameter hinzufügen zur eigentlichen\nTestfunktion. Mit ",[405,4005,3875],{}," können wir über die von ",[405,4008,3869],{}," zurückgegebene Collection iterieren und dies tun.",[492,4011,4013],{"className":3537,"code":4012,"language":3539,"meta":34,"style":34},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // p.node.arguments[0] would be the spec description\n const specCallee = p.node.arguments[1];\n // add 'done' parameter\n specCallee.params.push(statment`done`);\n })\n\n",[405,4014,4015,4019,4023,4030,4045,4060,4065,4084,4089,4108],{"__ignoreMap":34},[500,4016,4017],{"class":502,"line":503},[500,4018,644],{"emptyLinePlaceholder":107},[500,4020,4021],{"class":502,"line":98},[500,4022,3770],{"class":3586},[500,4024,4025,4027],{"class":502,"line":249},[500,4026,3965],{"class":677},[500,4028,4029],{"class":506}," root\n",[500,4031,4032,4035,4037,4039,4042],{"class":502,"line":583},[500,4033,4034],{"class":506}," .",[500,4036,3869],{"class":513},[500,4038,713],{"class":506},[500,4040,4041],{"class":677},"...",[500,4043,4044],{"class":506},")\n",[500,4046,4047,4049,4051,4053,4055,4058],{"class":502,"line":615},[500,4048,4034],{"class":506},[500,4050,3875],{"class":513},[500,4052,713],{"class":506},[500,4054,18],{"class":728},[500,4056,4057],{"class":677}," =>",[500,4059,690],{"class":506},[500,4061,4062],{"class":502,"line":621},[500,4063,4064],{"class":3586}," // p.node.arguments[0] would be the spec description\n",[500,4066,4067,4070,4073,4075,4078,4081],{"class":502,"line":631},[500,4068,4069],{"class":677}," const",[500,4071,4072],{"class":703}," specCallee",[500,4074,3783],{"class":677},[500,4076,4077],{"class":506}," p.node.arguments[",[500,4079,4080],{"class":703},"1",[500,4082,4083],{"class":506},"];\n",[500,4085,4086],{"class":502,"line":641},[500,4087,4088],{"class":3586}," // add 'done' parameter\n",[500,4090,4091,4094,4097,4099,4102,4105],{"class":502,"line":647},[500,4092,4093],{"class":506}," specCallee.params.",[500,4095,4096],{"class":513},"push",[500,4098,713],{"class":506},[500,4100,4101],{"class":513},"statment",[500,4103,4104],{"class":520},"`done`",[500,4106,4107],{"class":506},");\n",[500,4109,4110],{"class":502,"line":805},[500,4111,4112],{"class":506}," })\n",[18,4114,4115,4116,4118,4119,4122,4123,4126,4127,4129,4130,4132],{},"Die Variable ",[405,4117,18],{}," ist der Pfad des gefundenen Knotens. Man könnte die Variable auch ",[405,4120,4121],{},"path"," benennen, würde sich aber\nbeißen mit dem node Modul ",[405,4124,4125],{},"const path = require('path');",". Das importieren dieses Moduls ist keine Seltenheit in\ncodemods denke ich. Und als Konvention nehmen wir einfach ",[405,4128,18],{}," statt ",[405,4131,4121],{},", immer!",[18,4134,4135,4136,4138,4139,4141,4142,4144],{},"Der ASTExplorer zeigt wie wir an die Funktion kommen der wir den ",[405,4137,3387],{}," Parameter hinzufügen möchten. Wir holen uns das\nzweite Element der CallExpression Argumente und fügen dessen Parameter Liste einfach das ",[405,4140,3387],{}," hinzu. Leider (?) können\nwir aber keinen String übergeben. Wir erinnern uns an den AST. Wir brauchen eine Beschreibung des Knotens. Man könnte\njetzt entweder ein komplexes Objekt erstellen, oder man nimmt sich einfach die nützliche ",[405,4143,3824],{}," Funktion zu Hilfe.\nAuf die Funktion machte mich ein Kollege aufmerksam. Sie ist leider nicht in der Doku zu finden sondern nur in codemods\nauf Github… Sagte ich schon, dass die Doku etwas spärlich ist?",[18,4146,4147,4148,4151],{},"Zum Abschluss müssen wir die Änderungen mit ",[405,4149,4150],{},"toSource()"," an jscodeshift zurückgeben um die Datei neu zu schreiben.",[492,4153,4155],{"className":3537,"code":4154,"language":3539,"meta":34,"style":34},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(...)\n .toSource()\n\n",[405,4156,4157,4161,4165,4171,4183,4195],{"__ignoreMap":34},[500,4158,4159],{"class":502,"line":503},[500,4160,644],{"emptyLinePlaceholder":107},[500,4162,4163],{"class":502,"line":98},[500,4164,3770],{"class":3586},[500,4166,4167,4169],{"class":502,"line":249},[500,4168,3965],{"class":677},[500,4170,4029],{"class":506},[500,4172,4173,4175,4177,4179,4181],{"class":502,"line":583},[500,4174,4034],{"class":506},[500,4176,3869],{"class":513},[500,4178,713],{"class":506},[500,4180,4041],{"class":677},[500,4182,4044],{"class":506},[500,4184,4185,4187,4189,4191,4193],{"class":502,"line":615},[500,4186,4034],{"class":506},[500,4188,3875],{"class":513},[500,4190,713],{"class":506},[500,4192,4041],{"class":677},[500,4194,4044],{"class":506},[500,4196,4197,4199,4201],{"class":502,"line":621},[500,4198,4034],{"class":506},[500,4200,3882],{"class":513},[500,4202,4203],{"class":506},"()\n",[18,4205,4206],{},"Zum schnellen Testen kann die Transformation auf der Konsole mit",[492,4208,4210],{"className":3047,"code":4209,"language":3049,"meta":34,"style":34},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n\n",[405,4211,4212,4216],{"__ignoreMap":34},[500,4213,4214],{"class":502,"line":503},[500,4215,644],{"emptyLinePlaceholder":107},[500,4217,4218],{"class":502,"line":98},[500,4219,4220],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n",[18,4222,4223],{},"ausgeführt werden. Nach dem ersten Staunen aber mit",[492,4225,4227],{"className":3047,"code":4226,"language":3049,"meta":34,"style":34},"\n$> git checkout HEAD -- pfad/zur/source/datei.js\n\n",[405,4228,4229,4233],{"__ignoreMap":34},[500,4230,4231],{"class":502,"line":503},[500,4232,644],{"emptyLinePlaceholder":107},[500,4234,4235],{"class":502,"line":98},[500,4236,4237],{},"$> git checkout HEAD -- pfad/zur/source/datei.js\n",[18,4239,4240],{},"zurück gesetzt werden.",[18,4242,4243],{},"Git o/",[18,4245,4246],{},"Der erste Punkt ist erledigt.",[343,4248,4249,4257,4263],{},[346,4250,4251],{},[3456,4252,3639,4253,3642,4255,3645],{},[405,4254,3546],{},[405,4256,3387],{},[346,4258,4259,3651,4261,3655],{},[405,4260,3650],{},[405,4262,3654],{},[346,4264,3658,4265,3661],{},[405,4266,3383],{},[18,4268,4269,4270,4272,4273,4276],{},"Ersetzen wir als nächstes ",[405,4271,3650],{}," mit dem ",[405,4274,4275],{},"done()"," Aufruf. Dazu klicken wir im ASTExplorer auf den entsprechenden\nAusdruck und schauen rechts im AST nach der Pfad Beschreibung die wir brauchen.",[492,4278,4280],{"className":3537,"code":4279,"language":3539,"meta":34,"style":34},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // replace 'done = true' with done() invocation\n j(p).find(j.ExpressionStatement, {\n expression: {\n type: j.AssignmentExpression.name,\n left: {\n name: 'done'\n }\n }\n }).replaceWith(p => statement`done();`);\n })\n .toSource()\n\n",[405,4281,4282,4286,4290,4296,4308,4322,4327,4332,4345,4350,4355,4360,4368,4372,4377,4398,4402],{"__ignoreMap":34},[500,4283,4284],{"class":502,"line":503},[500,4285,644],{"emptyLinePlaceholder":107},[500,4287,4288],{"class":502,"line":98},[500,4289,3770],{"class":3586},[500,4291,4292,4294],{"class":502,"line":249},[500,4293,3965],{"class":677},[500,4295,4029],{"class":506},[500,4297,4298,4300,4302,4304,4306],{"class":502,"line":583},[500,4299,4034],{"class":506},[500,4301,3869],{"class":513},[500,4303,713],{"class":506},[500,4305,4041],{"class":677},[500,4307,4044],{"class":506},[500,4309,4310,4312,4314,4316,4318,4320],{"class":502,"line":615},[500,4311,4034],{"class":506},[500,4313,3875],{"class":513},[500,4315,713],{"class":506},[500,4317,18],{"class":728},[500,4319,4057],{"class":677},[500,4321,690],{"class":506},[500,4323,4324],{"class":502,"line":621},[500,4325,4326],{"class":3586}," // ...\n",[500,4328,4329],{"class":502,"line":631},[500,4330,4331],{"class":3586}," // replace 'done = true' with done() invocation\n",[500,4333,4334,4337,4340,4342],{"class":502,"line":641},[500,4335,4336],{"class":513}," j",[500,4338,4339],{"class":506},"(p).",[500,4341,3869],{"class":513},[500,4343,4344],{"class":506},"(j.ExpressionStatement, {\n",[500,4346,4347],{"class":502,"line":647},[500,4348,4349],{"class":506}," expression: {\n",[500,4351,4352],{"class":502,"line":805},[500,4353,4354],{"class":506}," type: j.AssignmentExpression.name,\n",[500,4356,4357],{"class":502,"line":810},[500,4358,4359],{"class":506}," left: {\n",[500,4361,4362,4365],{"class":502,"line":826},[500,4363,4364],{"class":506}," name: ",[500,4366,4367],{"class":520},"'done'\n",[500,4369,4370],{"class":502,"line":838},[500,4371,1527],{"class":506},[500,4373,4374],{"class":502,"line":937},[500,4375,4376],{"class":506}," }\n",[500,4378,4379,4382,4384,4386,4388,4390,4393,4396],{"class":502,"line":943},[500,4380,4381],{"class":506}," }).",[500,4383,3878],{"class":513},[500,4385,713],{"class":506},[500,4387,18],{"class":728},[500,4389,4057],{"class":677},[500,4391,4392],{"class":513}," statement",[500,4394,4395],{"class":520},"`done();`",[500,4397,4107],{"class":506},[500,4399,4400],{"class":502,"line":948},[500,4401,4112],{"class":506},[500,4403,4404,4406,4408],{"class":502,"line":954},[500,4405,4034],{"class":506},[500,4407,3882],{"class":513},[500,4409,4203],{"class":506},[18,4411,4412,4413,4415,4416,707],{},"Da wir wissen, dass ",[405,4414,3650],{}," nur einmal vorkommt, können wir der Collection direkt sagen bitte ersetzen mit dem\nStatement ",[405,4417,3654],{},[18,4419,4420,4421,4423],{},"Die ",[405,4422,3387],{}," Variable ist jetzt natürlich obsolet und kann komplett entfernt werden. Wieder schauen wir im ASTExplorer\nnach der Beschreibung die wir brauchen um auf folgendes zu kommen:",[492,4425,4427],{"className":3537,"code":4426,"language":3539,"meta":34,"style":34},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // get rid of 'var done = false'\n j(p).find(j.VariableDeclaration, {\n declarations: [\n {\n type: j.VariableDeclarator.name,\n id: {\n name: 'done'\n }\n }\n ]\n }).remove()\n })\n .toSource()\n\n",[405,4428,4429,4433,4437,4443,4455,4469,4473,4478,4489,4494,4499,4504,4509,4516,4521,4525,4530,4539,4543],{"__ignoreMap":34},[500,4430,4431],{"class":502,"line":503},[500,4432,644],{"emptyLinePlaceholder":107},[500,4434,4435],{"class":502,"line":98},[500,4436,3770],{"class":3586},[500,4438,4439,4441],{"class":502,"line":249},[500,4440,3965],{"class":677},[500,4442,4029],{"class":506},[500,4444,4445,4447,4449,4451,4453],{"class":502,"line":583},[500,4446,4034],{"class":506},[500,4448,3869],{"class":513},[500,4450,713],{"class":506},[500,4452,4041],{"class":677},[500,4454,4044],{"class":506},[500,4456,4457,4459,4461,4463,4465,4467],{"class":502,"line":615},[500,4458,4034],{"class":506},[500,4460,3875],{"class":513},[500,4462,713],{"class":506},[500,4464,18],{"class":728},[500,4466,4057],{"class":677},[500,4468,690],{"class":506},[500,4470,4471],{"class":502,"line":621},[500,4472,4326],{"class":3586},[500,4474,4475],{"class":502,"line":631},[500,4476,4477],{"class":3586}," // get rid of 'var done = false'\n",[500,4479,4480,4482,4484,4486],{"class":502,"line":641},[500,4481,4336],{"class":513},[500,4483,4339],{"class":506},[500,4485,3869],{"class":513},[500,4487,4488],{"class":506},"(j.VariableDeclaration, {\n",[500,4490,4491],{"class":502,"line":647},[500,4492,4493],{"class":506}," declarations: [\n",[500,4495,4496],{"class":502,"line":805},[500,4497,4498],{"class":506}," {\n",[500,4500,4501],{"class":502,"line":810},[500,4502,4503],{"class":506}," type: j.VariableDeclarator.name,\n",[500,4505,4506],{"class":502,"line":826},[500,4507,4508],{"class":506}," id: {\n",[500,4510,4511,4514],{"class":502,"line":838},[500,4512,4513],{"class":506}," name: ",[500,4515,4367],{"class":520},[500,4517,4518],{"class":502,"line":937},[500,4519,4520],{"class":506}," }\n",[500,4522,4523],{"class":502,"line":943},[500,4524,1527],{"class":506},[500,4526,4527],{"class":502,"line":948},[500,4528,4529],{"class":506}," ]\n",[500,4531,4532,4534,4537],{"class":502,"line":954},[500,4533,4381],{"class":506},[500,4535,4536],{"class":513},"remove",[500,4538,4203],{"class":506},[500,4540,4541],{"class":502,"line":1898},[500,4542,4112],{"class":506},[500,4544,4545,4547,4549],{"class":502,"line":1904},[500,4546,4034],{"class":506},[500,4548,3882],{"class":513},[500,4550,4203],{"class":506},[18,4552,4553],{},"Zweiter Punkt auch erledigt.",[343,4555,4556,4564,4572],{},[346,4557,4558],{},[3456,4559,3639,4560,3642,4562,3645],{},[405,4561,3546],{},[405,4563,3387],{},[346,4565,4566],{},[3456,4567,4568,3651,4570,3655],{},[405,4569,3650],{},[405,4571,3654],{},[346,4573,3658,4574,3661],{},[405,4575,3383],{},[18,4577,4578,4579,4581],{},"Fehlt nur noch das Entfernen des ",[405,4580,3383],{}," Blocks. Richtig geraten! Der ASTExplorer zeigt uns was wir suchen müssen.",[492,4583,4585],{"className":3537,"code":4584,"language":3539,"meta":34,"style":34},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // get rid of obsolete waitsFor block\n j(p).find(j.CallExpression, {\n callee: {\n name: 'waitsFor'\n }\n }).remove()\n })\n .toSource()\n\n",[405,4586,4587,4591,4595,4601,4613,4627,4631,4636,4646,4651,4659,4663,4671,4675],{"__ignoreMap":34},[500,4588,4589],{"class":502,"line":503},[500,4590,644],{"emptyLinePlaceholder":107},[500,4592,4593],{"class":502,"line":98},[500,4594,3770],{"class":3586},[500,4596,4597,4599],{"class":502,"line":249},[500,4598,3965],{"class":677},[500,4600,4029],{"class":506},[500,4602,4603,4605,4607,4609,4611],{"class":502,"line":583},[500,4604,4034],{"class":506},[500,4606,3869],{"class":513},[500,4608,713],{"class":506},[500,4610,4041],{"class":677},[500,4612,4044],{"class":506},[500,4614,4615,4617,4619,4621,4623,4625],{"class":502,"line":615},[500,4616,4034],{"class":506},[500,4618,3875],{"class":513},[500,4620,713],{"class":506},[500,4622,18],{"class":728},[500,4624,4057],{"class":677},[500,4626,690],{"class":506},[500,4628,4629],{"class":502,"line":621},[500,4630,4326],{"class":3586},[500,4632,4633],{"class":502,"line":631},[500,4634,4635],{"class":3586}," // get rid of obsolete waitsFor block\n",[500,4637,4638,4640,4642,4644],{"class":502,"line":641},[500,4639,4336],{"class":513},[500,4641,4339],{"class":506},[500,4643,3869],{"class":513},[500,4645,3973],{"class":506},[500,4647,4648],{"class":502,"line":647},[500,4649,4650],{"class":506}," callee: {\n",[500,4652,4653,4656],{"class":502,"line":805},[500,4654,4655],{"class":506}," name: ",[500,4657,4658],{"class":520},"'waitsFor'\n",[500,4660,4661],{"class":502,"line":810},[500,4662,4376],{"class":506},[500,4664,4665,4667,4669],{"class":502,"line":826},[500,4666,4381],{"class":506},[500,4668,4536],{"class":513},[500,4670,4203],{"class":506},[500,4672,4673],{"class":502,"line":838},[500,4674,4112],{"class":506},[500,4676,4677,4679,4681],{"class":502,"line":937},[500,4678,4034],{"class":506},[500,4680,3882],{"class":513},[500,4682,4203],{"class":506},[18,4684,4685],{},"Fertig!",[18,4687,4688],{},"Schnell noch testen obs auch wirklich tut:",[492,4690,4691],{"className":3047,"code":4209,"language":3049,"meta":34,"style":34},[405,4692,4693,4697],{"__ignoreMap":34},[500,4694,4695],{"class":502,"line":503},[500,4696,644],{"emptyLinePlaceholder":107},[500,4698,4699],{"class":502,"line":98},[500,4700,4220],{},[18,4702,4703],{},"Und ab damit ins Repo",[492,4705,4707],{"className":3047,"code":4706,"language":3049,"meta":34,"style":34},"\n$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n\n",[405,4708,4709,4713],{"__ignoreMap":34},[500,4710,4711],{"class":502,"line":503},[500,4712,644],{"emptyLinePlaceholder":107},[500,4714,4715],{"class":502,"line":98},[500,4716,4717],{},"$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n",[18,4719,4720],{},"Mit dieser codemod können wir ab sofort mit einem Befehl zig JavaScript Dateien transformieren lassen o/ Wobei ich\ntrotzdem ein Code Review empfehlen würde. Und nicht blind auf den Master pushen.",[335,4722,4724],{"id":4723},"ausblick","Ausblick",[18,4726,4727,4728,4730,4731,4733],{},"Zugegeben. Ich habe hier ein wirklich einfaches Beispiel gewählt. Aber trotzdem hat das viele Tests unseres Projektes\nabgedeckt. Ich habe noch ein paar Bedingungen hinzugefügt zur codemod wie z. B. bitte abbrechen, wenn Anzahl der\n",[405,4729,3383],{}," Blöcke != 1. Das ignoriert dann alles specs die synchron sind und asynchrone Tests die ein anderes Muster\naufweisen. Bei uns waren das z. B. Integrations Tests die mehrere ",[405,4732,3383],{}," Blöcke hatten weil mehrere Klicks simuliert\nwurden.",[18,4735,4736],{},"Es fehlen auch noch die restlichen jasmine Transformationen für Matchers und Spies. Aber ich denke es sollte recht klar\ngeworden sein wie auch dafür codemods geschrieben werden können.",[18,4738,4739,4740,4745,4746,4749,4750,4753],{},"Werden codemods komplexer kann man über Unit Tests nachdenken. Hier gibt es Hilfe von jscodeshift. Als Beispiel\nhilft ",[22,4741,4744],{"href":4742,"rel":4743},"https://github.com/cpojer/js-codemod/blob/82af3089f22fa0687159f64177b73908b82d074f/transforms/__tests__/arrow-function-test.js",[26],"diese Stelle hier",".\nKurzer Ablauf: Als TestRunner wird jest benötigt. Der Quellcode der transformiert und der Quellcode der herausspringen\nsoll, werden jeweils als Datei im Verzeichnis ‘",[469,4747,4748],{},"testfixtures","’ abgelegt. Der Test liegt im Verzeichnis\n‘",[469,4751,4752],{},"tests","’ und definiert mittels den TestUtils einfach nur den Test. Codemods testgetrieben entwickeln steht nichts\nmehr im Weg.",[492,4755,4757],{"className":3537,"code":4756,"language":3539,"meta":34,"style":34},"// jasmine-async.spec.js\nconst defineTest = require(\"jscodeshift/dist/testUtils\").defineTest;\ndefineTest(__dirname, \"jasmine-async\");\n",[405,4758,4759,4764,4785],{"__ignoreMap":34},[500,4760,4761],{"class":502,"line":503},[500,4762,4763],{"class":3586},"// jasmine-async.spec.js\n",[500,4765,4766,4769,4772,4774,4777,4779,4782],{"class":502,"line":98},[500,4767,4768],{"class":677},"const",[500,4770,4771],{"class":703}," defineTest",[500,4773,3783],{"class":677},[500,4775,4776],{"class":513}," require",[500,4778,713],{"class":506},[500,4780,4781],{"class":520},"\"jscodeshift/dist/testUtils\"",[500,4783,4784],{"class":506},").defineTest;\n",[500,4786,4787,4790,4793,4796],{"class":502,"line":249},[500,4788,4789],{"class":513},"defineTest",[500,4791,4792],{"class":506},"(__dirname, ",[500,4794,4795],{"class":520},"\"jasmine-async\"",[500,4797,4107],{"class":506},[335,4799,235],{"id":234},[18,4801,4802],{},"Trotz spärlicher Doku findet man sich nach gewisser Zeit zurecht. Vor allem der ASTExplorer ist eine große Hilfe dabei!\nFür der/die/das erste codemod habe ich vielleicht 3x länger gebraucht, als im Projekt alles per Hand zu ersetzen. Aber\nkennt man das Vorgehen mit jscodeshift gleicht sich das schnell wieder aus. Und der/die/das codemod kann wiederverwendet\nwerden! Wenn auch mit kleinen Anpassungen für andere Gegebenheiten.",[18,4804,4805,4806,4811,4812,4817],{},"Oft verwendet habe ich bisher die ",[22,4807,4810],{"href":4808,"rel":4809},"https://github.com/cpojer/js-codemod",[26],"js-codemods","\nvon ",[22,4813,4816],{"href":4814,"rel":4815},"https://twitter.com/cpojer",[26],"@cpojer"," zum transformieren zu neuen es2015 Sprachfeatures.",[18,4819,4820],{},[469,4821,4822],{},"Meine Empfehlung:",[18,4824,4825],{},"Einmal reinknien und machen! Vielleicht sogar für kleinere Projekte.",[1017,4827,4828],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":34,"searchDepth":98,"depth":98,"links":4830},[4831,4832,4833,4836,4837],{"id":3403,"depth":98,"text":3404},{"id":3442,"depth":98,"text":3443},{"id":3492,"depth":98,"text":3493,"children":4834},[4835],{"id":3519,"depth":249,"text":3520},{"id":4723,"depth":98,"text":4724},{"id":234,"depth":98,"text":235},[1029],"2016-08-25T09:56:03","Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\\nArt von asynchronen Specs und einmal die verwendeten Expectations.\\nUnter http://jasmine.github.io/2.0/upgrading.html wurde super\\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.","https://synyx.de/blog/javascript-code-refactoring-automatisieren/",{},"/blog/javascript-code-refactoring-automatisieren",{"title":3360,"description":4845},"Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\nArt von asynchronen Specs und einmal die verwendeten Expectations.\nUnter http://jasmine.github.io/2.0/upgrading.html wurde super\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.","blog/javascript-code-refactoring-automatisieren",[4848,670,4849],"automatisierung","refactoring","Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion Bibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei…","npdOLNgV0j3l4jQhircA5afYDyTYYRrnbICTKF9u9Mw",{"id":4853,"title":4854,"author":4855,"body":4857,"category":4923,"date":4924,"description":4925,"extension":104,"link":4926,"meta":4927,"navigation":107,"path":4928,"seo":4929,"slug":4931,"stem":4932,"tags":4933,"teaser":4936,"__hash__":4937},"blog/blog/our-days-parallel-2016.md","Our days @ para//el 2016",[4856],"clausen",{"type":11,"value":4858,"toc":4921},[4859,4862,4877,4886,4895,4898,4903,4912,4915,4918],[14,4860,4854],{"id":4861},"our-days-parael-2016",[18,4863,4864,4865,4870,4871,4876],{},"Last week Stefan and me took part as guests at the ",[22,4866,4869],{"href":4867,"rel":4868},"http://parallelcon.de/",[26],"para//el conference"," in Heidelberg. The\nactual ",[22,4872,4875],{"href":4873,"rel":4874},"http://parallelcon.de/programm.php",[26],"program"," was separated into 2 keynotes, one per day, and 36 talks, 18 per\nday, three at a time (in parallel). Right at the beginning it was said, that we were two out of 150 participants, which,\nI would say, leads to a nice atmosphere. It wasn’t difficult to attend a discussion or meet other people during the\npauses and I haven’t further experienced, quite contrary to other conferences, any large queueing at launch. Why? Well,\nsomeone encountered that a table, if placed correctly, offers space for more than one line at the buffet, which is quite\na good news! 🙂",[18,4878,4879,4880,4885],{},"We’ve both really enjoyed the days viewed from that angle, so lets talk about the conference and the talks itself. It\nbecomes visible, that concurrency or parallism – a detail that people tend to interprete quite differently or have\ndifferent opionions about the exact meaning – is not considered something that is only subject to languages like C/C++\nor to specific topics of the IT like embedded programming or ",[22,4881,4884],{"href":4882,"rel":4883},"https://en.wikipedia.org/wiki/Supercomputer",[26],"HPC",", but\nrather something that can be discussed in a broader sense.",[18,4887,4888,4889,4894],{},"At synyx, we program most of the time in Java and talks had been scheduled very nicely without any or less overlap,\nwhich is true for other languages as well, but my point here is, that, concurrency on the JVM is getting more and more\nattention nowadays. When did you read the last time about the memory model of the JVM? But sometimes, it makes even more\nsense to listen to sessions, that do not cross your every-day-borders, just to see how problems get solved or pattern\napplied by different people or divisions. What’s a cache-line, volatile, atomic? What are the implications to\nmulti-core or multi-socket environments, and what if we put a JVM in between? A slight change to a system, for\ninstance, a change to the compiler, JVM or even a different CPU model might have an impact, due some underlying rules\nand optimization strategies, including hard and software. Visibility constraints might be a good keyword here and it was\nquite interesting to hear about so many different aspects again. The speakers did a good job to paint the picture with\nsmaller examples: a + b + c might not be c + b + a under certain circumstances. While this problem is not related to\nparallism at first, but rather to ",[22,4890,4893],{"href":4891,"rel":4892},"https://en.wikipedia.org/wiki/Numerical_analysis",[26],"numerical precision errors",", it\nmight be more visible to those environments and reveals the great spectrum of possible talks everyone could attend.",[18,4896,4897],{},"So, when I go through all of the sessions in my mind again, there was one challenging question to me, quite a bit\nphilosophic maybe, at least to me.",[18,4899,4900],{},[168,4901,4902],{},"In a concurrent world, how much precision would you be willing to relinquish, for a correct view of the shared world?",[18,4904,4905,4906,4911],{},"I would like to keep this open for now, you may want to think about it on your own, but you may also want to anticipate\nthe outer rim of the IT. I can really recommend the reading of ",[22,4907,4910],{"href":4908,"rel":4909},"http://jcip.net/",[26],"java concurrency in practice"," from\nGoetz Brian et. al to everyone who couldn’t join the para//el in 2016 and wants to know more about the concurrent world.\nThreads are not evil, if used with the right abstraction and pooling helps to minimize the initialization costs.",[18,4913,4914],{},"In general, there is only one thing that I’ve missed along the talks about performance, correctness and theory, which is\ntest. I’d really like to gain more insights about how people verify concurrent code. How fine grained should we\nformulate tests? What can we say about tools, software, patterns or even a simple setup? Might be even more complex for\nthe Embedded world.",[18,4916,4917],{},"Well, might be relevant for 2017, might not be relevant, we will see.",[18,4919,4920],{},"Thanks to everyone who made the conference to what we’ve seen in 2016!",{"title":34,"searchDepth":98,"depth":98,"links":4922},[],[1029],"2016-04-11T09:25:02","Last week Stefan and me took part as guests at the para//el conference in Heidelberg. The\\nactual program was separated into 2 keynotes, one per day, and 36 talks, 18 per\\nday, three at a time (in parallel). Right at the beginning it was said, that we were two out of 150 participants, which,\\nI would say, leads to a nice atmosphere. It wasn’t difficult to attend a discussion or meet other people during the\\npauses and I haven’t further experienced, quite contrary to other conferences, any large queueing at launch. Why? Well,\\nsomeone encountered that a table, if placed correctly, offers space for more than one line at the buffet, which is quite\\na good news! 🙂","https://synyx.de/blog/our-days-parallel-2016/",{},"/blog/our-days-parallel-2016",{"title":4854,"description":4930},"Last week Stefan and me took part as guests at the para//el conference in Heidelberg. The\nactual program was separated into 2 keynotes, one per day, and 36 talks, 18 per\nday, three at a time (in parallel). Right at the beginning it was said, that we were two out of 150 participants, which,\nI would say, leads to a nice atmosphere. It wasn’t difficult to attend a discussion or meet other people during the\npauses and I haven’t further experienced, quite contrary to other conferences, any large queueing at launch. Why? Well,\nsomeone encountered that a table, if placed correctly, offers space for more than one line at the buffet, which is quite\na good news! 🙂","our-days-parallel-2016","blog/our-days-parallel-2016",[4934,113,869,4935],"concurrency","parallism","Last week Stefan and me took part as guests at the para//el conference in Heidelberg. The actual program was separated into 2 keynotes, one per day, and 36 talks, 18…","gVStre4dXOOMAefhjnopDpeh9ljMwq_ok3cq7NrRQKk",{"id":4939,"title":4940,"author":4941,"body":4942,"category":5348,"date":5349,"description":5350,"extension":104,"link":5351,"meta":5352,"navigation":107,"path":5353,"seo":5354,"slug":5356,"stem":5357,"tags":5358,"teaser":5362,"__hash__":5363},"blog/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting.md","springboot & reactjs #2 | progressive enhancement based on list sorting",[275],{"type":11,"value":4943,"toc":5340},[4944,4947,4956,4959,4962,4966,5011,5013,5016,5019,5024,5031,5034,5037,5044,5066,5077,5080,5087,5094,5097,5104,5123,5129,5132,5135,5138,5152,5155,5178,5193,5196,5206,5212,5219,5222,5225,5228,5242,5261,5264,5273,5289,5293,5316,5322,5326,5333,5338],[14,4945,4940],{"id":4946},"springboot-reactjs-2-progressive-enhancement-based-on-list-sorting",[18,4948,4949,4950,4955],{},"This is the second article of a springboot & reactjs article series about server side rendering and progressive\nenhancement. In the ",[22,4951,4954],{"href":4952,"rel":4953},"https://synyx.de/2016/03/universal-webapp-development-with-spring-boot-react/",[26],"first article"," we\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\nCurrently we just see a static list of awesome products…",[18,4957,4958],{},"Today we will implement the sorting feature that should work with a plain html form submit as well as with a Ajax\nRequest and client side rendering. So the app is progressively enhanced with JavaScript o/",[4960,4961],"hr",{},[335,4963,4965],{"id":4964},"springboot-reactjs-article-series","springboot & reactjs article series",[1622,4967,4968,4976,5005,5008],{},[346,4969,4970,4975],{},[22,4971,4974],{"href":4972,"rel":4973},"https://synyx.de/2016/03/springboot-reactjs-server-side-rendering",[26],"server side rendering"," ✅",[346,4977,4978,4979],{},"progressive enhancement based on list sorting 🆕\n",[343,4980,4981,4987,4993,4999],{},[346,4982,4983],{},[22,4984,4986],{"href":4985},"#html-form-and-server-side-rendering","HTML form and server side rendering",[346,4988,4989],{},[22,4990,4992],{"href":4991},"#enhance-the-client","Enhance the client",[346,4994,4995],{},[22,4996,4998],{"href":4997},"#make-the-back-button-work-again","Make the back button work again",[346,5000,5001],{},[22,5002,5004],{"href":5003},"#what-do-we-have-learned-so-far","What do we have learned so far",[346,5006,5007],{},"improving developer experience",[346,5009,5010],{},"lessons learned",[4960,5012],{},[18,5014,5015],{},"While the JavaScript solution with client side rendering results in faster rendering and therefore a better user\nexperience, it also has it’s costs. We have to manage the browser url by ourselves. Furthermore we have to implement the\nbrowsers back button feature /o",[18,5017,5018],{},"But let’s take one step after another…",[18,5020,5021],{},[469,5022,5023],{},"tl;dr",[18,5025,5026],{},[22,5027,5030],{"href":5028,"rel":5029},"https://github.com/synyx/springboot-reactjs-demo",[26],"project source code is available on github",[335,5032,4986],{"id":5033},"html-form-and-server-side-rendering",[18,5035,5036],{},"Before we can even start thinking about the back button we have to implement the sorting feature. So we create a plain\nhtml form first that fires a good old get request.",[18,5038,5039,5040,5043],{},"The ProductFilterItem is a simple input field of type radio(button). For the moment the sorting should only consider one\nattribute. Therefore a radiobutton group is the way to go. So every input is defined with ",[405,5041,5042],{},"name=\"sort\""," and a label to\nincrease the clickable area.",[18,5045,5046,5047,5050,5051,5054,5055,5058,5059,5061,5062,5065],{},"In the previous blog the ",[469,5048,5049],{},"ProductList"," was the only component to render and therefore the entry in ",[469,5052,5053],{},"main.js",". But\nthe new ",[469,5056,5057],{},"ProductFilter"," is not part of the list. The ",[469,5060,5049],{}," reacts to the filter parameter set by the user.\nSo we have to create an ",[469,5063,5064],{},"App"," container that combines our awesome ProductList and ProductFilter components.",[18,5067,5068,5069,5072,5073,5076],{},"Since we have a container now to combine the ProductList and the ProductFilter components we also have to adjust the\n",[405,5070,5071],{},"global.renderServer"," function. First we add a second parameter ",[405,5074,5075],{},"sortBy"," to be able to render the selected radio button.\nThen we must render the new App container instead of the plain ProductList.",[18,5078,5079],{},"That’s it for the frontend part!",[18,5081,5082,5083,5086],{},"Next we need to extend the backend controller to process the ",[405,5084,5085],{},"sort"," request parameter defined in the ProductFilter form\nand use it to sort the product list.",[18,5088,5089,5090,5093],{},"Additionally the ",[405,5091,5092],{},"React#renderProducts"," method must be extended, too, of course.",[18,5095,5096],{},"And that’s it with the backend part as well!",[18,5098,5099,5100,5103],{},"Now build the frontend, start the spring boot app, open your browser on ",[405,5101,5102],{},"http://localhost:8080"," and start sorting the\nawesome product list 🙂",[492,5105,5107],{"className":3047,"code":5106,"language":3049,"meta":34,"style":34},"\n$ npm run build\n$ ./gradlew bootRun\n\n",[405,5108,5109,5113,5118],{"__ignoreMap":34},[500,5110,5111],{"class":502,"line":503},[500,5112,644],{"emptyLinePlaceholder":107},[500,5114,5115],{"class":502,"line":98},[500,5116,5117],{},"$ npm run build\n",[500,5119,5120],{"class":502,"line":249},[500,5121,5122],{},"$ ./gradlew bootRun\n",[18,5124,5125],{},[32,5126],{"alt":5127,"src":5128},"awesome_productlist_sorting","https://media.synyx.de/uploads//2016/04/awesome_productlist_sorting.gif",[335,5130,4992],{"id":5131},"enhance-the-client",[18,5133,5134],{},"So far our awesome product list is fully functional. Let’s recap what we can do now.",[18,5136,5137],{},"We are able to:",[343,5139,5140,5143,5146,5149],{},[346,5141,5142],{},"see the awesome product info",[346,5144,5145],{},"sort the awesome products by name or price",[346,5147,5148],{},"use the browser’s back and forward button (static site!)",[346,5150,5151],{},"bookmark every single view",[18,5153,5154],{},"As the next step we want to increase the user experience with AJAX requests and client side rendering. This results in a\nmuch quicker feedback for the user as requesting the whole html document.",[18,5156,5157,5158,5162,5163,5167,5168,835,5171,719,5174,5177],{},"To fetch data dynamically from the server we have to prevent the native form submit and take over the control by\nourselves. React provides [lifecycle methods](",[22,5159,5160],{"href":5160,"rel":5161},"http://git@gitlab-test.synyx.coffee",[26],":\nseber/SynyxBibliothek.git ",[22,5164,5165],{"href":5165,"rel":5166},"https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods",[26],") like ",[405,5169,5170],{},"onClick",[405,5172,5173],{},"onChange",[405,5175,5176],{},"onSubmit",", etc. So we simply have to register a handler for the form submit. The handler does nothing but\nto prevent the native behaviour and to inform the consumer of the ProductFilter about the submit.",[18,5179,5180,5181,5183,5184,5186,5187,5192],{},"You may ask why we are subscribing our submit handler via ",[405,5182,5176],{}," on the form and not the ",[405,5185,5170],{}," hook on the\nsubmit button. Well, actually we could listen to the button click. But then we had to keep track of all form data by\nourselves… And the html form element already provides all this data as\na ",[22,5188,5191],{"href":5189,"rel":5190},"https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements",[26],"HTMLFormControlsCollection","!",[18,5194,5195],{},"Since we use Reacts API for DOM event handling, we have to render our awesome product list on the client, too. We only\nhave server side rendering at this moment, remember? 😉",[18,5197,5198,5199,5201,5202,5205],{},"So additionally to the ",[405,5200,5071],{}," function used by Nashorn we need a second function ",[405,5203,5204],{},"window.renderClient","\nthat we have to call on the client side (browser) as we will see later in this tutorial.",[18,5207,5208,5209,5211],{},"Next we have to add the initial rendering to the index.html template. Of course, we must call ",[405,5210,5204],{}," with\nthe same data as on the server. However, React prints a nice error message on the browser console if the data differs (\nmeans the client side rendering would result in another DOM structure as the already existing one).",[18,5213,5214,5215,5218],{},"Back to the Java backend we have to inject the initial product list and the sortBy value into the server side model of\nthe ",[405,5216,5217],{},"ProductController.java"," class. Additionally we add a second endpoint to provide the sorted product list as json.",[18,5220,5221],{},"That’s it!",[18,5223,5224],{},"A form submit now fetches the minimal data from the server and the client takes care about the rendering. Awesome,\nright? If only this queasy feeling wouldn’t be there… Right… The browser url is not changing anymore /o And if it\ncouldn’t be worse… Without url changing we also lost the power of the glorious back button.",[335,5226,4998],{"id":5227},"make-the-back-button-work-again",[18,5229,5230,5231,5236,5237,707],{},"Okay, at first we should face the browser url. With HTML5 we’ve gained\nthe ",[22,5232,5235],{"href":5233,"rel":5234},"https://developer.mozilla.org/en-US/docs/Web/API/History_API",[26],"window.history"," api which is supported by all modern\nbrowsers. Changing the url is as simple as pushing the new state into the history\nwith ",[22,5238,5241],{"href":5239,"rel":5240},"https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries",[26],"window.history.pushState",[18,5243,5244,5245,5248,5249,5252,5253,5256,5257,5260],{},"Next we want to listen to the browsers back and forward buttons. This can be implemented with subscribing to the\n",[405,5246,5247],{},"popstate"," event. The subscription is done within ",[405,5250,5251],{},"componentDidMount"," since we only want to subscribe in the browser\nenvironment. ",[405,5254,5255],{},"Constructor"," and it’s counterpart ",[405,5258,5259],{},"componentWillMount"," are both called on server side creating the static\nhtml markup.",[18,5262,5263],{},"Finally we made it 🙂",[18,5265,5266,5267,5272],{},"Go on, build the frontend, start the spring boot app, open ",[168,5268,5269],{},[22,5270,5102],{"href":5102,"rel":5271},[26]," and admire our awesome progressively\nenhanced product list. Functional without JavaScript and even better with enabled JavaScript.",[492,5274,5275],{"className":3047,"code":5106,"language":3049,"meta":34,"style":34},[405,5276,5277,5281,5285],{"__ignoreMap":34},[500,5278,5279],{"class":502,"line":503},[500,5280,644],{"emptyLinePlaceholder":107},[500,5282,5283],{"class":502,"line":98},[500,5284,5117],{},[500,5286,5287],{"class":502,"line":249},[500,5288,5122],{},[335,5290,5292],{"id":5291},"what-do-we-have-learned-so-far","What do we have learned so far?",[343,5294,5295,5305,5313],{},[346,5296,5297,5298,5301,5302],{},"use plain HTML ",[405,5299,5300],{},"\u003Cform>"," element and enhance it with JavaScript and ",[405,5303,5304],{},"event.preventDefault",[346,5306,5307,5308,1368,5310,5312],{},"use ",[405,5309,5235],{},[405,5311,5247],{}," event to handle the browser back/forward button on the client",[346,5314,5315],{},"manually rebuilding and reloading the ReactJS app still sucks (autoreload would be cool, right)",[18,5317,5318],{},[32,5319],{"alt":5320,"src":5321},"progressive_js","https://media.synyx.de/uploads//2016/04/progressive_js.gif",[335,5323,5325],{"id":5324},"the-next-steps-will-be","The next steps will be",[343,5327,5328,5331],{},[346,5329,5330],{},"using webpack to enhance the developer experience",[346,5332,5010],{},[18,5334,5335],{},[469,5336,5337],{},"Stay tuned and keep learning!",[1017,5339,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":5341},[5342,5343,5344,5345,5346,5347],{"id":4964,"depth":98,"text":4965},{"id":5033,"depth":98,"text":4986},{"id":5131,"depth":98,"text":4992},{"id":5227,"depth":98,"text":4998},{"id":5291,"depth":98,"text":5292},{"id":5324,"depth":98,"text":5325},[1029],"2016-04-08T16:00:09","This is the second article of a springboot & reactjs article series about server side rendering and progressive\\nenhancement. In the first article we\\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\\nCurrently we just see a static list of awesome products…","https://synyx.de/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting/",{},"/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting",{"title":4940,"description":5355},"This is the second article of a springboot & reactjs article series about server side rendering and progressive\nenhancement. In the first article we\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\nCurrently we just see a static list of awesome products…","springboot-reactjs-progressive-enhancement-based-on-list-sorting","blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting",[869,670,5359,5360,3355,5361],"react","reactjs","springboot","This is the second article of a springboot & reactjs article series about server side rendering and progressive enhancement. In the first article we have learned how to render a…","-bm-m-HOVsX8sO3gHZgQPhuKHIL62ikfjs8JOfJb9w8",{"id":5365,"title":5366,"author":5367,"body":5369,"category":5382,"date":5383,"description":34,"extension":104,"link":5384,"meta":5385,"navigation":107,"path":5386,"seo":5387,"slug":5373,"stem":5388,"tags":5389,"teaser":5391,"__hash__":5392},"blog/blog/devoxx4kids-meets-javaland.md","Devoxx4Kids meets JavaLand",[5368],"karrasz",{"type":11,"value":5370,"toc":5380},[5371,5374],[14,5372,5366],{"id":5373},"devoxx4kids-meets-javaland",[18,5375,5376],{},[32,5377],{"alt":5378,"src":5379},"Javaland4kids","https://media.synyx.de/uploads//2016/03/IMG_1212-185x300.jpg",{"title":34,"searchDepth":98,"depth":98,"links":5381},[],[256],"2016-03-17T12:42:22","https://synyx.de/blog/devoxx4kids-meets-javaland/",{},"/blog/devoxx4kids-meets-javaland",{"title":5366,"description":34},"blog/devoxx4kids-meets-javaland",[2702,266,2712,5390],"synyx-event","Zum zweiten mal fand im Rahmen der JavaLand Konferenz im Phantasialand Brühl die JavaLand4Kids statt. Wie auch letztes Jahr hat die Devoxx4Kids das Event tatkräftig mit Hardware und dem JumpingSumo…","9rLGVfPNE3tY0WqDCKeQRkOdWwGwomgofLGylz-h5b8",{"id":5394,"title":5395,"author":5396,"body":5397,"category":5979,"date":5980,"description":5981,"extension":104,"link":5982,"meta":5983,"navigation":107,"path":5984,"seo":5985,"slug":5986,"stem":5987,"tags":5988,"teaser":5989,"__hash__":5990},"blog/blog/springboot-reactjs-server-side-rendering.md","springboot & reactjs #1 | server side rendering",[275],{"type":11,"value":5398,"toc":5972},[5399,5402,5405,5407,5409,5469,5471,5474,5477,5482,5485,5488,5491,5494,5497,5501,5506,5509,5512,5515,5523,5526,5529,5537,5543,5569,5572,5581,5587,5590,5593,5598,5605,5610,5625,5632,5643,5657,5661,5675,5679,5686,5701,5704,5707,5712,5727,5732,5739,5742,5766,5771,5781,5785,5806,5809,5811,5814,5827,5832,5835,5838,5841,5876,5887,5893,5898,5904,5907,5920,5926,5928,5950,5956,5958,5966,5970],[14,5400,5395],{"id":5401},"springboot-reactjs-1-server-side-rendering",[18,5403,5404],{},"This is the first article of a series about server side rendering and progressive enhancement. We will implement a\nproduct list that can be sorted by two parameters. Furthermore the app will be progressively enhanced, means the html\ndocument is rendered on the server and javascript will just enhance the app on the client if possible.",[4960,5406],{},[335,5408,4965],{"id":4964},[1622,5410,5411,5457,5465,5467],{},[346,5412,5413,5414],{},"server side rendering ✅\n",[343,5415,5416,5422,5428,5434,5440,5446,5452],{},[346,5417,5418],{},[22,5419,5421],{"href":5420},"#why-java","Why Java for the backend?",[346,5423,5424],{},[22,5425,5427],{"href":5426},"#why-reactjs","Why ReactJS for the client?",[346,5429,5430],{},[22,5431,5433],{"href":5432},"#springboot","Spring Boot Initializr",[346,5435,5436],{},[22,5437,5439],{"href":5438},"#backend","Backend",[346,5441,5442],{},[22,5443,5445],{"href":5444},"#frontend","Frontend",[346,5447,5448],{},[22,5449,5451],{"href":5450},"#running-the-app","Running the app",[346,5453,5454],{},[22,5455,5004],{"href":5456},"#what-we-have-learned-so-far",[346,5458,5459,5464],{},[22,5460,5463],{"href":5461,"rel":5462},"https://synyx.de/2016/04/springboot-reactjs-progressive-enhancement-based-on-list-sorting/",[26],"progressive enhancement based on list sorting","\n🆕",[346,5466,5007],{},[346,5468,5010],{},[4960,5470],{},[18,5472,5473],{},"Today we are going to create an awesome product list while using progressive enhancement to provide the best user\nexperience possible on each device. Progressive enhancement is a method in web development that sets focus on content\nfirst. The content will then be enhanced with dynamic features (JavaScript) and Layout (css). To provide content even\nwith disabled JavaScript the page has to be rendered on the server. For reasons (explained below) we use Java & spring\non the backend and ReactJS on the client side.",[18,5475,5476],{},"But why do we even want to make the effort of developing a universal web application? And even a progressive one? Of\ncourse this is overhead that must be handled and we have to implement and maintain more APIs as we will see later.\nHowever, the user experience is worth this effort in my opinion.",[18,5478,5479],{},[469,5480,5481],{},"Benefits of universal applications",[18,5483,5484],{},"thanks to server side rendered markup",[18,5486,5487],{},"– the app is fully functional from the start",[18,5489,5490],{},"– the app is usable instantly",[18,5492,5493],{},"JavaScript just enhances the features",[18,5495,5496],{},"– ajax calls without full page reloading are super fast",[18,5498,5499],{},[469,5500,5023],{},[18,5502,5503],{},[22,5504,5030],{"href":5028,"rel":5505},[26],[335,5507,5421],{"id":5508},"why-java-for-the-backend",[18,5510,5511],{},"To start with our little project we first have to think about the technologies we want to use. At synyx we are mainly\nusing Java for the backend. And over the last year we have built up a large knowledge around the spring ecosystem as\nwell.",[18,5513,5514],{},"So to answer the question",[343,5516,5517,5520],{},[346,5518,5519],{},"team knowledge (Spring, …)",[346,5521,5522],{},"battle tested solutions (Spring Security, Spring MVC, …)",[335,5524,5427],{"id":5525},"why-reactjs-for-the-client",[18,5527,5528],{},"Well, personally I am a fan of react and it’s ecosystem. That’s it! 😎",[18,5530,5531,5532,707],{},"Okay… actually there are good reasons to use react. React provides a simple API to develop UI components and it works\nwell with reactive architectures like Flux and its\nsuccessor ",[22,5533,5536],{"href":5534,"rel":5535},"https://web.archive.org/web/20160411023634/https://egghead.io/series/getting-started-with-redux",[26],"redux",[18,5538,5539,5540],{},"Furthermore it supports server side rendering quite well. ",[168,5541,5542],{},"(afaik Angular 2 and Ember could also be used, or cyclejs,\nor …)",[2799,5544,5545,5550],{},[18,5546,5547],{},[469,5548,5549],{},"Disclaimer",[18,5551,5552,5553,5558,5559,5563,5564,5568],{},"I will not explain spring, react or $technology. Nor I will explain Java or JavaScript, es2015 syntax in particular.\nThere already are excellent articles out there on the web\nlike ",[22,5554,5557],{"href":5555,"rel":5556},"http://javascriptplayground.com/blog/2016/02/the-react-webpack-tooling-problem",[26],"how to start with react"," (\nwithout\nusing webpack). Additionally I recommend to have a look at the official documentation\nfor ",[22,5560,5359],{"href":5561,"rel":5562},"https://facebook.github.io/react/docs/thinking-in-react.html",[26]," as well as ",[22,5565,3355],{"href":5566,"rel":5567},"https://spring.io/docs",[26],"\nof\ncourse.",[14,5570,5433],{"id":5571},"spring-boot-initializr",[18,5573,5574,5575,5580],{},"At first we’re going to generate a bootstrap project with the awesome ",[22,5576,5579],{"href":5577,"rel":5578},"https://start.spring.io",[26],"spring starter"," web\ninterface. We use gradle as build system, thymeleaf as template engine to render our views on the server and good old\nspring-web. Handlebars would also be an option as template engine but it is not supported by spring initializr.",[18,5582,5583],{},[32,5584],{"alt":5585,"src":5586},"springboot-initializr","https://media.synyx.de/uploads//2016/02/springboot-initializer.png",[14,5588,5439],{"id":5589},"backend",[18,5591,5592],{},"Starting with the backend we have to create the following files.",[18,5594,5595],{},[469,5596,5597],{},"index.html",[18,5599,5600,5601,5604],{},"The html template is as simple as it could be. We just need a div that acts as container for our ReactJS app.\n",[405,5602,5603],{},"th:utext=\"${content}\""," is thymeleaf specific and injects the content attribute of the view model as unescaped string.",[18,5606,5607],{},[469,5608,5609],{},"React.java",[18,5611,5612,5613,5616,5617,5620,5621,5624],{},"In React.java we are going to instantiate the Nashorn engine to be able to interpret JavaScript code which we will add\nlater. Nashorn is just a runtime environment for JavaScript on the JVM. It neither provides a ",[405,5614,5615],{},"window"," object nor a\n",[405,5618,5619],{},"console"," for logging. But since the latter one is required by ReactJS we have to load a ",[405,5622,5623],{},"nashorn-polyfill.js"," file\nbefore anything else.",[18,5626,5627,5628,5631],{},"We are loading our JavaScript sources into Nashorn with ",[405,5629,5630],{},"nashornScriptEngine.eval (\"load ('...')\")",". This is the same as\nincluding a script tag in a html document.",[18,5633,5634,5635,5638,5639,5642],{},"However, we could also call ",[405,5636,5637],{},"nashorn.eval (new InputStreamReader (...))"," to load the JavaScript files instead of using\nthe Nashorn specific ",[168,5640,5641],{},"load"," function. But we would lose the ability to debug the JavaScript code while running in\nNashorn (at least with IntelliJ). Which could be… useful 😉",[18,5644,5645,5646,5648,5649,5652,5653,5656],{},"Furthermore we have to implement a method ",[168,5647,5092],{}," which will invoke a global ",[405,5650,5651],{},"renderServer"," function\ndefined in ",[168,5654,5655],{},"app.bundle.js"," to create the rendered html string.",[18,5658,5659],{},[469,5660,5623],{},[18,5662,5663,5664,5667,5668,5670,5671,5674],{},"The polyfill for nashorn has to define a ",[469,5665,5666],{},"global"," variable (for reasons I will explain later) and the already\nmentioned ",[469,5669,5619],{},". ",[168,5672,5673],{},"print"," is a Nashorn function that logs on stdout.",[18,5676,5677],{},[469,5678,5217],{},[18,5680,5681,5682,5685],{},"The ProductController is responsible for getting the products and for setting the rendered html string as the ",[405,5683,5684],{},"content","\nattribute of the view model.",[18,5687,5688,5689,5694,5695,5700],{},"Additionally we need\na ",[22,5690,5693],{"href":5691,"rel":5692},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/Product.java",[26],"Product.java","\nPOJO and\na ",[22,5696,5699],{"href":5697,"rel":5698},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/ProductRepository.java",[26],"ProductRepository.java",".\nI think this is very straight forward and code snippets are obsolete here.",[14,5702,5445],{"id":5703},"frontend",[18,5705,5706],{},"With the backend part ready we can start with the frontend.",[18,5708,5709,5710,707],{},"At first we have to do a small setup to enable es2015 compilation and module bundling with webpack. Webpack is actually\nnot required but eases our developer lives immensely. Babel-cli could also be used with small adjustments in\n",[168,5711,5609],{},[492,5713,5715],{"className":3047,"code":5714,"language":3049,"meta":34,"style":34},"$ npm init\n$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n\n",[405,5716,5717,5722],{"__ignoreMap":34},[500,5718,5719],{"class":502,"line":503},[500,5720,5721],{},"$ npm init\n",[500,5723,5724],{"class":502,"line":98},[500,5725,5726],{},"$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n",[18,5728,5729],{},[469,5730,5731],{},"webpack.config.js",[18,5733,5734,5735,5738],{},"Next we configure webpack to generate a bundle of our JavaScript files including the ReactJS library and our app\nbusiness logic. Please note ",[469,5736,5737],{},"output.filename"," which is the file loaded by React.java.",[18,5740,5741],{},"Webpack can then simply be used to create the bundle by a npm task.",[492,5743,5745],{"className":3047,"code":5744,"language":3049,"meta":34,"style":34},"// package.json\n\"scripts\": {\n \"build\": \"webpack\"\n}\n\n",[405,5746,5747,5752,5757,5762],{"__ignoreMap":34},[500,5748,5749],{"class":502,"line":503},[500,5750,5751],{},"// package.json\n",[500,5753,5754],{"class":502,"line":98},[500,5755,5756],{},"\"scripts\": {\n",[500,5758,5759],{"class":502,"line":249},[500,5760,5761],{}," \"build\": \"webpack\"\n",[500,5763,5764],{"class":502,"line":583},[500,5765,802],{},[18,5767,5768],{},[469,5769,5770],{},"ProducList.js",[18,5772,5773,5774,5776,5777,5780],{},"To start with the UI components we are going to create the ProductList as the main component. It will be, drum roll,\nresponsible for displaying a list of products which have a name and a price (remember ",[168,5775,5693],{},"?). Therefore we\nimplement a function that takes our products and returns the representational markup. To avoid\n",[405,5778,5779],{},"\"Cannot read property 'map' of undefined\""," type errors we simply assign an empty array to the products by default.",[18,5782,5783],{},[469,5784,5053],{},[18,5786,5787,5788,5790,5791,5793,5794,5796,5797,5799,5800,5802,5803,5805],{},"Next we need the entry point of our ReactJS app to define the ",[469,5789,5651],{}," function invoked by Nashorn. Remember the\n",[469,5792,5666],{}," variable set in ",[168,5795,5623],{},"? We use this variable now to “export” our ",[168,5798,5651],{}," function. If\nyou are familiar with the NodeJS environment, you already know that the ",[168,5801,5666],{}," object is the equivalent to the\n",[168,5804,5615],{}," object available in the browser. And Nashorn is our equivalent of NodeJS 😉",[14,5807,5451],{"id":5808},"running-the-app",[18,5810,5221],{},[18,5812,5813],{},"Now we can run our first universal server side rendered springboot application to admire our graceful product list. Go\non, run",[492,5815,5817],{"className":3047,"code":5816,"language":3049,"meta":34,"style":34},"$ npm run build\n$ ./gradlew bootRun\n",[405,5818,5819,5823],{"__ignoreMap":34},[500,5820,5821],{"class":502,"line":503},[500,5822,5117],{},[500,5824,5825],{"class":502,"line":98},[500,5826,5122],{},[18,5828,5829,5830,707],{},"open your Browser and load ",[405,5831,5102],{},[18,5833,5834],{},"Just…",[18,5836,5837],{},"to see…",[18,5839,5840],{},"a wonderful stacktrace…",[492,5842,5844],{"className":3047,"code":5843,"language":3049,"meta":34,"style":34},"jdk.nashorn.internal.runtime.ECMAException: TypeError:\n[de.synyx...Product@553287f8, Product@65ae29e6] has no such function \"map\"\n at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]\n\n",[405,5845,5846,5851,5856,5861,5866,5871],{"__ignoreMap":34},[500,5847,5848],{"class":502,"line":503},[500,5849,5850],{},"jdk.nashorn.internal.runtime.ECMAException: TypeError:\n",[500,5852,5853],{"class":502,"line":98},[500,5854,5855],{},"[de.synyx...Product@553287f8, Product@65ae29e6] has no such function \"map\"\n",[500,5857,5858],{"class":502,"line":249},[500,5859,5860],{}," at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]\n",[500,5862,5863],{"class":502,"line":583},[500,5864,5865],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]\n",[500,5867,5868],{"class":502,"line":615},[500,5869,5870],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]\n",[500,5872,5873],{"class":502,"line":621},[500,5874,5875],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]\n",[18,5877,5878,5879,5882,5883,5886],{},"The reason is that Nashorn interprets Java objects as, surprise, Java objects. As you remember, our ReactJS component\n",[405,5880,5881],{},"\u003CProductList />"," expects a list of products (actually a JavaScript array). But currently the type of products is a\n",[405,5884,5885],{},"java.util.List"," which doesn’t have the map method. Note the datatype of products in the image below.",[18,5888,5889],{},[32,5890],{"alt":5891,"src":5892},"nashorn-debugging","https://media.synyx.de/uploads//2016/03/nashorn-debugging.png",[2799,5894,5895],{},[18,5896,5897],{},"“Given a Java array or Collection, this function returns a JavaScript array with a shallow copy of its contents”",[18,5899,5900,5901,5903],{},"So our renderServer function defined in ",[405,5902,5053],{}," must be extended to:",[18,5905,5906],{},"Now we’re ready to go 🙂",[18,5908,5909,5910,5913,5914,5919],{},"Rebuild the frontend with ",[405,5911,5912],{},"npm run build",", restart the Spring Boot application, reload ",[168,5915,5916],{},[22,5917,5102],{"href":5102,"rel":5918},[26]," and\nadmire our awesome product list.",[18,5921,5922],{},[32,5923],{"alt":5924,"src":5925},"awesome-product-list-001","https://media.synyx.de/uploads//2016/03/awesome-product-list-001.png",[335,5927,5292],{"id":5291},[343,5929,5930,5933,5939,5947],{},[346,5931,5932],{},"using Nashorn is no rocket science",[346,5934,5935,5936,5938],{},"load js files via ",[405,5937,5630],{}," to enable debugging (at least in IntelliJ)",[346,5940,5941,5943,5944],{},[168,5942,5885],{}," must be converted to JavaScript array with ",[405,5945,5946],{},"Java.from",[346,5948,5949],{},"manually rebuilding and reloading the ReactJS app sucks (autoreload would be cool, right)",[18,5951,5952],{},[32,5953],{"alt":5954,"src":5955},"js-webpack-nashorn","https://media.synyx.de/uploads//2016/03/js-webpack-nashorn.png",[335,5957,5325],{"id":5324},[343,5959,5960,5963],{},[346,5961,5962],{},"using webpack to enhance developer experience",[346,5964,5965],{},"implementing the sorting feature",[18,5967,5968],{},[469,5969,5337],{},[1017,5971,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":5973},[5974,5975,5976,5977,5978],{"id":4964,"depth":98,"text":4965},{"id":5508,"depth":98,"text":5421},{"id":5525,"depth":98,"text":5427},{"id":5291,"depth":98,"text":5292},{"id":5324,"depth":98,"text":5325},[1029],"2016-03-11T11:29:12","This is the first article of a series about server side rendering and progressive enhancement. We will implement a\\nproduct list that can be sorted by two parameters. Furthermore the app will be progressively enhanced, means the html\\ndocument is rendered on the server and javascript will just enhance the app on the client if possible.","https://synyx.de/blog/springboot-reactjs-server-side-rendering/",{},"/blog/springboot-reactjs-server-side-rendering",{"title":5395,"description":5404},"springboot-reactjs-server-side-rendering","blog/springboot-reactjs-server-side-rendering",[869,670,5359,5360,3355,5361],"This is the first article of a series about server side rendering and progressive enhancement. We will implement a product list that can be sorted by two parameters. Furthermore the…","NgbG5R1prPK9EUbjPI1FYf4uauo9uWfYO5zNB_kSUvk",{"id":5992,"title":5993,"author":5994,"body":5995,"category":6165,"date":6166,"description":6167,"extension":104,"link":6168,"meta":6169,"navigation":107,"path":6170,"seo":6171,"slug":5999,"stem":6172,"tags":6173,"teaser":6180,"__hash__":6181},"blog/blog/how-to-monitor-jaxrsjersey-applications.md","How to monitor JAXRS/Jersey applications",[4856],{"type":11,"value":5996,"toc":6163},[5997,6000,6003,6014,6022,6027,6030,6033,6042,6045,6104,6107,6114,6117,6134,6144,6151,6158,6161],[14,5998,5993],{"id":5999},"how-to-monitor-jaxrsjersey-applications",[18,6001,6002],{},"If you nowadays visit a conference, you still might get into contact with sessions where people are talking about\nmonitoring or at least some aspects of it and ALM (application lifecycle management) is a really important discipline a\nteam or project should should take into account right from the beginning and no, this doesn’t mean that you should trim\nor optimize prematurely, but to have an eye on it. Next to the developers and operators we can identify many more\nstakeholders who are interested in the data, but generally they prefer a different view on the data.",[343,6004,6005,6008,6011],{},[346,6006,6007],{},"Who is the audience, who uses the API in what version?",[346,6009,6010],{},"How can I economize resources, but for specific cases only?",[346,6012,6013],{},"How can I use the data to prevent accidents or control specific nodes?",[18,6015,6016,6017,6021],{},"The code can be found at ",[22,6018,3899],{"href":6019,"rel":6020},"https://github.com/synyx/meter.git",[26]," and this article shall, simply spoken, show the\nmotivation behind it. So, in one sentence I would say:",[2799,6023,6024],{},[18,6025,6026],{},"We want fine grained statistics about things that happen without writing much integration code and to partition the\ndata at runtime using the provided input.",[18,6028,6029],{},"Enabling monitoring ‘always’ require us to follow the same pattern (do something before and optionally do something\nafter), so it would be nice to simply not do the same things over and over again. Monitoring can be seen as a classical\ncross cutting concern and even if we loose some control at implementation level, we can profit on less maintenance\neffort and a better system design – which is a good trade in my opinion.",[18,6031,6032],{},"When I hear the words ‘cross cutting concerns’ then instantly AOP (aspect oriented programming) comes into my mind and\nthose techniques shall pave the way as described in the sentence above. It can be further used independently of the\nunderlying technology – of course we need some technology glue to wire the aspects, but this must generally be done only\nonce.",[18,6034,6035,6036,6041],{},"Many public APIs follow the REST pattern today. So we decided to go with Jersey first as it’s a great framework for\nbuilding enterprise REST services. Jersey uses HK2 internally and you can vary almost every part at runtime with a\nfluent java API – If you know Google Guice then you might get an impression now. HK2 supports AOP through the libraries\nfrom the AOP Alliance and you can hook this process easily following the guidelines of\nfrom ",[22,6037,6040],{"href":6038,"rel":6039},"https://hk2.java.net/2.2.0/aop-example.html",[26],"aop-example",". That’s a pretty good news and a mighty joinpoint for\nmetrics, a quality library to gather runtime statistics.",[18,6043,6044],{},"Right now, our recipe contains Jersey, Metrics, AOP, and Java annotations as markup and if we plug everything together\nwe achieve something like this:",[492,6046,6048],{"className":867,"code":6047,"language":869,"meta":34,"style":34}," @GET\n @Metric (\n timers = @Timer,\n histograms = @Histogram (value = \"#size\", measure = BambooResponseSize.class),\n counters = @Counter (value = \"{color}\", kind = Kind.Error)\n )\n @Path (\"{name}\")\n public String echo (@PathParam (\"name\") String name, @QueryParam (\"locale\") String locale, @DefaultValue (\"Green\") @QueryParam (\"color\") Color color) {\n validate (color);\n return service.call (name + \"::\" + locale);\n }\n\n",[405,6049,6050,6055,6060,6065,6070,6075,6080,6085,6090,6095,6100],{"__ignoreMap":34},[500,6051,6052],{"class":502,"line":503},[500,6053,6054],{}," @GET\n",[500,6056,6057],{"class":502,"line":98},[500,6058,6059],{}," @Metric (\n",[500,6061,6062],{"class":502,"line":249},[500,6063,6064],{}," timers = @Timer,\n",[500,6066,6067],{"class":502,"line":583},[500,6068,6069],{}," histograms = @Histogram (value = \"#size\", measure = BambooResponseSize.class),\n",[500,6071,6072],{"class":502,"line":615},[500,6073,6074],{}," counters = @Counter (value = \"{color}\", kind = Kind.Error)\n",[500,6076,6077],{"class":502,"line":621},[500,6078,6079],{}," )\n",[500,6081,6082],{"class":502,"line":631},[500,6083,6084],{}," @Path (\"{name}\")\n",[500,6086,6087],{"class":502,"line":641},[500,6088,6089],{}," public String echo (@PathParam (\"name\") String name, @QueryParam (\"locale\") String locale, @DefaultValue (\"Green\") @QueryParam (\"color\") Color color) {\n",[500,6091,6092],{"class":502,"line":647},[500,6093,6094],{}," validate (color);\n",[500,6096,6097],{"class":502,"line":805},[500,6098,6099],{}," return service.call (name + \"::\" + locale);\n",[500,6101,6102],{"class":502,"line":810},[500,6103,940],{},[18,6105,6106],{},"One nice thing to mention is, that everything managed by HK2 can be annotated and therefore measured – including\nresource methods and services from the DI container – fine grained control at method level :).",[18,6108,6109,6110,6113],{},"So what do I mean with ",[168,6111,6112],{},"a partition at runtime"," then?",[18,6115,6116],{},"You can find some the JAXRS annotations in the previous example, e.g. PathParam, QueryParam and of course, Jersey knows\nthe interpretation of the parameters, but do we know as well? Yes and that’s pretty awesome, as it allows us to",[343,6118,6119,6122,6125,6128,6131],{},[346,6120,6121],{},"build metrics which are partitioned by customer levels : .., silver, gold, platinum",[346,6123,6124],{},"build metrics for versioned APIs: /v1/, …, /v9/ — anyone using v1, costs?",[346,6126,6127],{},"build metrics to track clients by geography, cookie, header",[346,6129,6130],{},"build metrics that measure errors only",[346,6132,6133],{},"build metrics for if-you-can-name-it-you-can-measure-it things.",[18,6135,6136],{},[168,6137,6138,6139],{},"A conversion takes place prior, so you can run every custom evaluation\nbeforehand: ",[22,6140,6143],{"href":6141,"rel":6142},"https://jersey.java.net/documentation/latest/user-guide.html#d0e2152",[26],"userguide",[18,6145,6146,6147,6150],{},"And thats’ what I really like the most 🙂 – You have access to runtime values from ALL services, resources managed by\nJersey/HK2 to configure the ",[469,6148,6149],{},"usecase"," you want.",[18,6152,6153,6154,6157],{},"If you like to contribute or participate on items mentioned on the roadmap or issue something, thats not on the roadmap\n🙂 then feel free to visit us at ",[22,6155,3899],{"href":6019,"rel":6156},[26],", or even if you want to try out the example\nproject to get a first impression.",[18,6159,6160],{},"Feedback is highly welcome and many thanks from me to the developers of Jersey/HK2 and Metrics for their great work –\nnice piece of software.",[1017,6162,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":6164},[],[1029],"2015-07-29T09:02:17","If you nowadays visit a conference, you still might get into contact with sessions where people are talking about\\nmonitoring or at least some aspects of it and ALM (application lifecycle management) is a really important discipline a\\nteam or project should should take into account right from the beginning and no, this doesn’t mean that you should trim\\nor optimize prematurely, but to have an eye on it. Next to the developers and operators we can identify many more\\nstakeholders who are interested in the data, but generally they prefer a different view on the data.","https://synyx.de/blog/how-to-monitor-jaxrsjersey-applications/",{},"/blog/how-to-monitor-jaxrsjersey-applications",{"title":5993,"description":6002},"blog/how-to-monitor-jaxrsjersey-applications",[6174,6175,869,6176,6177,6178,6179],"aop","hk2","jaxrs","jersey","metrics","rest","If you nowadays visit a conference, you still might get into contact with sessions where people are talking about monitoring or at least some aspects of it and ALM (application…","9VLoatX0lXioeJDq5DXTaDizrmyuyEy9Kq7czFYrBAU",{"id":6183,"title":6184,"author":6185,"body":6187,"category":6369,"date":6370,"description":6371,"extension":104,"link":6372,"meta":6373,"navigation":107,"path":6374,"seo":6375,"slug":6191,"stem":6377,"tags":6378,"teaser":6386,"__hash__":6387},"blog/blog/devoxx-poland-2015-summary.md","Devoxx Poland 2015 Summary",[6186],"szulc",{"type":11,"value":6188,"toc":6367},[6189,6192,6213,6219,6222,6227,6230,6249,6255,6260,6263,6266,6269,6275,6280,6283,6286,6292,6297,6300,6303,6308,6311,6314,6319,6322,6325,6328,6333,6336,6339,6342,6345,6348,6352,6355,6364],[14,6190,6184],{"id":6191},"devoxx-poland-2015-summary",[18,6193,6194,6195,6200,6201,6206,6207,6212],{},"So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\noutstanding beautiful city. It is for the first time, when ",[22,6196,6199],{"href":6197,"rel":6198},"http://devoxx.pl",[26],"Devoxx Poland"," (previously known\nas ",[22,6202,6205],{"href":6203,"rel":6204},"http://2014.33degree.org",[26],"33rd Degree","), one of the most recognizable European Java Conference took place in Krakow,\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\nWednesday last week in the ",[22,6208,6211],{"href":6209,"rel":6210},"http://www.icekrakow.pl/",[26],"ICE Conference Center",", which is located directly by the Vistula\nriver, with beautiful view over the Wawel Royal Castle.",[18,6214,6215],{},[32,6216],{"alt":6217,"src":6218},"\"Inside the ICE Conference Center \"","https://media.synyx.de/uploads//2015/06/IMG_20150624_110556.jpg",[18,6220,6221],{},"Now it’s time to write some short summary. Below is my personal list of conclusions and analysis about the current state\nof Java industry and where it is going to, based on that what I’ve heard and seen during the conference.",[18,6223,6224],{},[469,6225,6226],{},"1. The rise of Microservices",[18,6228,6229],{},"You might say – “yep, of course”. It is obvious for everyone who is following the development of Java & Web Ecosystem\nthat microservices are the hottest and fancies “new” “technology” of the year 2015. I counted that around 10 talks (\nfrom around 100 full-time presentations) were completely dedicated to them. Another couple or so discussed tools\nemerged to make them easier to monitor, deploy and deal with them. And nearly every other talk mentioned them.",[18,6231,6232,6233,6236,6237,6242,6243,6248],{},"I think we are in the peak phase. I remember two years ago at",[469,6234,6235],{},"GeeCON 2013","as I saw the\nfirst ",[22,6238,6241],{"href":6239,"rel":6240},"http://2013.geecon.org/speakers/sam-newman.html",[26],"talk"," about it. Now it explodes, and you could hear at Devoxx\neverything about it:from theory and principles, through architecture, best practices, tools supporting it, monitoring,\ndevops, to live coding demos. Most of this talks were very enthusiastic about it, although in the next few months I\nexpect some more skeptical or at least balanced talks. There was\nactually ",[22,6244,6247],{"href":6245,"rel":6246},"https://web.archive.org/web/20150503201315/http://cfp.devoxx.pl:80/2015/talk/MZA-9564/Modularity_in_post_microservice_world",[26],"one","\nlike this.",[18,6250,6251],{},[32,6252],{"alt":6253,"src":6254},"\"Microservices Live-Coding Demo\"","https://media.synyx.de/uploads//2015/06/IMG_20150623_154822.jpg",[18,6256,6257],{},[469,6258,6259],{},"2. Reactive and Resilient by default",[18,6261,6262],{},"The second most important topic at Devoxx was the resiliency and reactive programming, together with durability,\nasynchronous programming, circuit breaking and back pressure, which are all actually strongly connected with\nmicroservices.",[18,6264,6265],{},"Conclusion from the talks I have seen is quite obvious: if something can crash, it eventually will, most probably in the\nworst moment. That is why resiliency and recovery mechanisms are so important and should be not only a feature, but a\nmust, especially in the era of microservices.",[18,6267,6268],{},"The application (or actually the whole container or server) should be able to crash at any time and the supervisor\nshould take care of it. The other parts of the system should be able to continue their operation normally almost as if\nnothing had happened, circuit-breaking the failed system and using fall-back mechanisms, providing some simplified\ndata from other sources. And as soon the failed part of the system has been recovered, it should automatically back to\nnormal.",[18,6270,6271],{},[32,6272],{"alt":6273,"src":6274},"\"Main Room\"","https://media.synyx.de/uploads//2015/06/PANO_20150622_155838.jpg",[18,6276,6277],{},[469,6278,6279],{},"3. Functional Programming breaks (slowly) though",[18,6281,6282],{},"Third most popular and still very hot topic is functional programming (or rather, a soft of FP). Thank to lambda\nexpressions, streams API, CompletableFutur, (semi)closures and tools like RxJava programmers slowly adopt more and more\nfunctional concepts and start to think of computation rather as a pipeline processing of immutable data instead of\nthinking of it as a mutating of objects’ state.",[18,6284,6285],{},"Furthermore, more and more developers found it increasingly important to write the code in such a way that there is no\nmutable state, including code at the method-level. I guess this is also a side-effect of using the IoC Containers,\nwhere many objects are simply the global singletons, and introducing the state in it would case a serious performance\nproblems and bugs.",[18,6287,6288],{},[32,6289],{"alt":6290,"src":6291},"\"View over Wawel Castle from ICE Center\"","https://media.synyx.de/uploads//2015/06/PANO_20150624_174232.jpg",[18,6293,6294],{},[469,6295,6296],{},"4. Java 8 throttles the rise of the new languages",[18,6298,6299],{},"Two, three years ago, if you attend a Java conference, one of the most important thing which would be discussed was “Is\nthe Scala/Groovy/xyz the next Java?” or “What are the Java alternatives”. You won’t here it anymore. In some cases you\nwould hear that Scala/Groovy/xyz is better for implementing some kind of stuff like mathematical computations or data\npipeline processing or so. Or that some languages are better at concurrency and parallelism. Or that some languages are\nbetter for writing tests.",[18,6301,6302],{},"But there is no more doubt that Java will be soon replaced by other languages whatsoever, because with Java 8 and many (\nespecially “reactive”) mature libraries Java is simply good enough for most cases. At least for now.",[18,6304,6305],{},[469,6306,6307],{},"5. Java-Community focus on backend",[18,6309,6310],{},"Again, two, three years ago there very many talks about Mobile, Android, Web, Nodejs frameworks, new Javascript\nclient-side frameworks, and so called “Full Stack Developer”. In the past years on the java-Conference you can attend\nto many diverse talks – from Android Programming, through NodeJS and Javascript-Backend solutions and tool, to all the\nJavascript-Frontend stuff like AngularJS, EmberJS etc. It is not the case any more.",[18,6312,6313],{},"They all moved out to their own separate conferences, because no one doubts anymore that anyone can be a good Java (\nBackend) Developer, a good JS-Frontend Developer and Mobile Developer at once. There are simple to many problems to\nsolve on the “Backend”-Side, so one has to focus on Java-Code and (probably not so long and more) on “Data”.",[18,6315,6316],{},[469,6317,6318],{},"6. Big Data and NoSQL are here",[18,6320,6321],{},"Big Data and NoSQL were of course present, but it is not the same “Big Data” and the same NoSQL which it was for 3-4\nyears. Today we are not talking about what it is, what is the theory, etc. Today it is obvious for everyone, that there\nis nothing like “Big Data” and “NoSQL” databases.",[18,6323,6324],{},"There are only databases which better scale and handle some particular amount and type of data in particular\ncircumstances. That’s it, and the only thing we should do is learn how to recognize which data better fit to be stored\nin database X or Y, and how to use it properly. And the conference showed exactly this, that there are no SQL and NoSQL\ndatabases, but only that handle better data of the given characteristic.",[18,6326,6327],{},"There are simply databases which doing one thing better than another, for example better handle transactions and\nrelations between data, or scale very well but support no relations, or maybe are design to store and query text\ndocuments or graphs of objects.",[18,6329,6330],{},[469,6331,6332],{},"7. Spring is all what you need",[18,6334,6335],{},"It was probably for the first time I didn’t see any talk about alternative approach to Spring-Based Technologies (Java\nEE excluding).",[18,6337,6338],{},"This year there was nothing about any new or old frameworks. No Play Framework, dropwizard. No other smaller fancy\ntechnologies like RatPack or Spark Framework. Nothing. Zero. It has been always at least a couple talks about different\nframeworks and alternative approaches. Not this time. There wasn’t even Grails.",[18,6340,6341],{},"The same applies for the data-persistence solutions like ORMs for instance.",[18,6343,6344],{},"No new features in Hibernate, JPA. Nothing about jOOQ or myBatis/iBatis. Now it’s all about Spring Data. There is Spring\nData JPA, Spring Data MongoDB. Spring Data Neo4J, Cassandra, Redis, Elasticsearch, any more. There are also Spring\nsolutions for Big Data – Spring XD and for the microservices aka cloud stuff – Spring Cloud.",[18,6346,6347],{},"Spring is the only thing which you seems to need. Thus I’m waiting for the “Spring Developer” job titles instead of\n“Java Developer”, just like “SharePoint Developer” or “Liferay Developer” already.",[18,6349,6350],{},[469,6351,2905],{},[18,6353,6354],{},"Despite of many new buzzwords and “new” technologies, there was not technological revolution, not even close. Maybe\nmicroservices will revolutionize the way we are designing systems but they will do it not because they are a\nrevolutionary technology, but rather by combining many other technologies together instead of introducing something what\nis really new.",[18,6356,6357,6358,6363],{},"I think it might be true that the Java Industry and the IT world in general is in\nthe",[22,6359,6362],{"href":6360,"rel":6361},"https://vimeo.com/130981099#t=2m56s",[26],"inflection point",". New technologies aren’t a game-changer and every new\ntechnology needs more and more time to spread across the industry, so it feels like a little bit stagnant.",[18,6365,6366],{},"I have such a feeling that we have now a little bit time to catch our breath, right after the Cloud, Mobile, Big Data,\nAsynchronous, Functional and DevOps era and to prepare ourselves for the Next Big Thing, which is probably waiting for\nus around the corner and will pop up in the least expected moment.",{"title":34,"searchDepth":98,"depth":98,"links":6368},[],[1029],"2015-07-02T10:34:54","So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\\noutstanding beautiful city. It is for the first time, when Devoxx Poland (previously known\\nas 33rd Degree), one of the most recognizable European Java Conference took place in Krakow,\\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\\nWednesday last week in the ICE Conference Center, which is located directly by the Vistula\\nriver, with beautiful view over the Wawel Royal Castle.","https://synyx.de/blog/devoxx-poland-2015-summary/",{},"/blog/devoxx-poland-2015-summary",{"title":6184,"description":6376},"So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\noutstanding beautiful city. It is for the first time, when Devoxx Poland (previously known\nas 33rd Degree), one of the most recognizable European Java Conference took place in Krakow,\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\nWednesday last week in the ICE Conference Center, which is located directly by the Vistula\nriver, with beautiful view over the Wawel Royal Castle.","blog/devoxx-poland-2015-summary",[6379,6380,6381,869,670,6382,6383,6384,6385,3355],"devoxx","devoxx-conference","devoxx-poland","microservices","reactive","resilent","rxjava","So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one outstanding beautiful city. It is for the first time, when Devoxx…","QWK3ox8PnfXTyuA0bTKcDvbny5tTEOTpJwT99Bdaw6g",{"id":6389,"title":6390,"author":6391,"body":6393,"category":6453,"date":6454,"description":6455,"extension":104,"link":6456,"meta":6457,"navigation":107,"path":6458,"seo":6459,"slug":6397,"stem":6460,"tags":6461,"teaser":6463,"__hash__":6464},"blog/blog/javaland-2015.md","Javaland – 2015",[6392],"arrasz",{"type":11,"value":6394,"toc":6451},[6395,6398,6401,6404,6407,6410,6413,6427,6430,6433,6436,6439,6442,6445,6448],[14,6396,6390],{"id":6397},"javaland-2015",[18,6399,6400],{},"Dieses Jahr hatte ich endlich einmal die Gelegenheit einen Tag auf der Javaland, einer recht neuen Konferenz im\ndeutschsprachigen Raum, zu verbringen. Spannend ist hier vor allem die Location, das Phantasialand in Brühl. Ich war\nsehr gespannt, ob die Location praktikabel ist oder nicht… Natürlich ist das Socializing bei solchen Konferenzen auch\nein wichtiger Aspekt 🙂 Auch dies versprach auf der Javaland, durch die iJUG mitorganisiert, sehr spannend zu werden. Zu\nguter Letzt war es auch so, dass meine halbe Twitter Timeline schon seit Tagen viel @javalandconf im Angebot hatte 🙂",[18,6402,6403],{},"Und so begab es sich, dass ich mich wieder einmal zu unmenschlichen Zeiten morgens am Karlsruher Bahnhof mit ein paar\nKollegen auf den Weg nach Brühl bei Köln aufmachte.",[18,6405,6406],{},"Mein erster Talk an diesem Tag war Michael Hunger, der über die Neo4j sprach. Eigentlich ist das für mich nichts Neues,\nwir arbeiten selbst schon einige Zeit mit Neo4j, jedoch erhoffte ich mir Neuigkeiten aus der Entwicklung und ich wurde\nnicht enttäuscht. Nicht nur, das NeoTechnologies weiter an Cypher (der Neo4J Abfrage Sprache) feilt, sie versprechen\nmittlerweile auch, dass Cypher schneller als die Java-API ist. Auch an der allgemeinen Performance der Neo4J wurde\ngefeilt und Michael versprach mit dem nächsten Release eine Geschwindigkeitssteigerung um den Faktor 5. Meiner Meinung\nnach sind das super Aussichten und es beweist, dass man derzeit mit der Entscheidung für den Einsatz einer Neo4J sehr\ngut aufgestellt ist.",[18,6408,6409],{},"Der zweite Talk den ich hörte war von Florian Hopf, einem Ex-synyxer, und somit war ich sehr gespannt, was er Neues zu\nElasticSearch zu berichten hatte. Letztlich verstand er es hervorragend, verschiedene Anwendungsfälle aufzuzeigen und\nsomit die Augen für ganz andere Einsatzszenarien von ElasticSearch zu öffnen. Spannend auch der Vergleich zwischen dem\nELK (ElasticSearch, Logstash, Kibana) Stack und Graylog als monolithischere Alternative. Fazit: spannender, informativer\nund abwechslungsreicher Talk, obwohl Flo krank angereisst ist!",[18,6411,6412],{},"Danach habe ich mir den Devoxx4Kids Ableger der JavaLand einmal genauer angeschaut und meine Kollegen von synyx besucht,\nwelche den Event mit veranstaltet haben.",[18,6414,6415,6416,6420,6421,6426],{},"14 Mädchen und Jungen der dritten und vierten Klasse der Max & Moritz Grundschule in St. Augustin hatten das erste Mal\ndie Gelegenheit, an drei unterschiedlichen Workshops (Quadcopter, Tinkerforge und Scratch) teilzunehmen. Sie erlernten\nspielerisch den Umgang mit dem Raspberry Pi, einen Mini-Computer, und konnten erste Einblicke in die unterschiedlichen\nMöglichkeiten der Programmierung erhalten. Die Kids hatten sehr viel Spaß, wenn auch das Geräusch der Achterbahnen ab\nund zu die Aufmerksamkeit auf sich zog. Die ",[22,6417,2557],{"href":6418,"rel":6419},"http://www.devoxx4kids.org/deutschland",[26],"\nund ",[22,6422,6425],{"href":6423,"rel":6424},"http://www.tinkerforge.com",[26],"Tinkerforge"," freuen sich schon auf ein weiteres Event im nächsten Jahr.",[18,6428,6429],{},"Nach dem Mittagessen ging es dann mit „Pipelines Zeichnen ist nicht schwer, Pipelines bauen dagegen sehr“ weiter,\nwelches mir leider zu Feature lastig war. Sicherlich wusste der Speaker sehr genau, was er da vortrug, aber letztlich\nkann ich mir eine Vergleichsmatrix zwischen verschiedenen Systemen zur Softwaredelivery selbst im Internet anschauen,\nich habe mir mehr Real World Problems versprochen und habe somit die restliche Zeit mit Networking verbracht 🙂 Für\nKollegen, denen die Continous Gedanken neu sind, war es sicherlich ein sinnvoller Vortrag.",[18,6431,6432],{},"Als nächsten und für mich dann auch letzten kompletten Vortag stand Distributed Log Aggregation und Metrics auf dem\nProgramm. Hier war ich natürlich besonders gespannt, da ich selbst vor einigen Monaten ein paar Artikel rund um diese\nThemen im JavaMagazin veröffentlicht hatte.",[18,6434,6435],{},"Der Vortrag wurde von Tammo van Heesen, ein damaliger Mitschreiber zu diesem Themengebiet im JavaMagazin, zusammen mit\nAlex Heusingfeld, welcher auch beinahe mal synyxer geworden wäre, gehalten.",[18,6437,6438],{},"Kurz und knapp gesagt war es ein spannender, informativer und unterhaltsamer und sehr guter Vortrag, der meine damaligen\nAussagen zum Großteil bestätigt.",[18,6440,6441],{},"Zu guter letzt wollte ich mir eigentlich noch ein Round-Up zu JSR371 MVC in JavaEE8 abholen. Allerdings gab ich das\nnach kurzer Zeit, aufgrund der für mich nicht schlüssigen Gründe FÜR den JSR, auf. Ich denke hier sollten die führenden\nKollegen noch ein paar mehr Runden drehen, ansonsten wird sich das an der Community vorbei entwickeln.",[18,6443,6444],{},"Das Networking kam auf der Javaland leider etwas zu kurz, da die Wege zwischen den Sessions recht weit sein können,\ninsbesondere wenn man sich auch die Devoxx4Kids mit anschauen wollte, was natürlich aus synyx Sicht besonders spannend\nwar 🙂 Leider waren aber eben genau die Devoxx4Kids Sessions komplett außerhalb des Phantasialands in einem Hotel\nuntergebracht, sodass zum einen die Wege enorm weit waren und zum anderen leider die Sichtbarkeit für meinen Geschmack\nVIEL zu gering ausgefallen ist. Ich hoffe dass dies beim nächsten Mal besser wird.",[18,6446,6447],{},"Um das Bild vollends abzurunden: das Catering war durchaus gelungen, auch wenn hier die Vegetarier/Vegane Freunde ein\nschweres Los durch lange Wartezeiten hatten. Sogar an die Sojamilch an allen Kaffeeständen wurde gedacht und das ist\ngroßartig! Hier können sich andere Konferenzen durchaus eine Scheibe abschneiden.",[18,6449,6450],{},"Somit würde ich sagen, bis 2016 im Javaland!",{"title":34,"searchDepth":98,"depth":98,"links":6452},[],[256],"2015-04-15T18:31:50","Dieses Jahr hatte ich endlich einmal die Gelegenheit einen Tag auf der Javaland, einer recht neuen Konferenz im\\ndeutschsprachigen Raum, zu verbringen. Spannend ist hier vor allem die Location, das Phantasialand in Brühl. Ich war\\nsehr gespannt, ob die Location praktikabel ist oder nicht… Natürlich ist das Socializing bei solchen Konferenzen auch\\nein wichtiger Aspekt 🙂 Auch dies versprach auf der Javaland, durch die iJUG mitorganisiert, sehr spannend zu werden. Zu\\nguter Letzt war es auch so, dass meine halbe Twitter Timeline schon seit Tagen viel @javalandconf im Angebot hatte 🙂","https://synyx.de/blog/javaland-2015/",{},"/blog/javaland-2015",{"title":6390,"description":6400},"blog/javaland-2015",[2702,266,268,6462],"phantasialand","Dieses Jahr hatte ich endlich einmal die Gelegenheit einen Tag auf der Javaland, einer recht neuen Konferenz im deutschsprachigen Raum, zu verbringen. Spannend ist hier vor allem die Location, das…","EuATM86YB9ssWiUT88o8h7JFGvfN8FZuhEPZO3Kr9wU",{"id":6466,"title":6467,"author":6468,"body":6470,"category":6721,"date":6723,"description":6724,"extension":104,"link":6725,"meta":6726,"navigation":107,"path":6727,"seo":6728,"slug":6474,"stem":6730,"tags":6731,"teaser":6734,"__hash__":6735},"blog/blog/javascript-linting-tool-evaluation.md","Javascript Linting Tool Evaluation",[6469,2544],"mueller",{"type":11,"value":6471,"toc":6712},[6472,6475,6490,6515,6524,6527,6531,6599,6602,6605,6618,6633,6636,6653,6656,6659,6670,6674,6677,6680,6683,6694,6698,6701,6707,6709],[14,6473,6467],{"id":6474},"javascript-linting-tool-evaluation",[18,6476,6477,6478,6483,6484,6489],{},"In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\nlegendary ‘",[22,6479,6482],{"href":6480,"rel":6481},"http://www.javaposse.com",[26],"The Java Posse","‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\ndevelopment teams across ",[22,6485,6488],{"href":6486,"rel":6487},"http://www.synyx.de",[26],"synyx",", using it never felt 100% comfortable. A quick Google search left\nus with three alternatives:",[343,6491,6492,6500,6507],{},[346,6493,6494,6499],{},[22,6495,6498],{"href":6496,"rel":6497},"http://jslint.com",[26],"JSLint"," by Doug Crockford himself",[346,6501,6502],{},[22,6503,6506],{"href":6504,"rel":6505},"https://developers.google.com/closure/utilities/",[26],"Closure Linter by Google",[346,6508,6509,6514],{},[22,6510,6513],{"href":6511,"rel":6512},"http://eslint.org",[26],"ESLint",", the new kid on the block",[18,6516,6517,6518,6523],{},"…as well as ",[22,6519,6522],{"href":6520,"rel":6521},"http://jshint.com/",[26],"JSHint"," itself, of course.",[18,6525,6526],{},"We drew up a quick spreadsheet for evaluating the tools and came up with the following.",[335,6528,6530],{"id":6529},"criteria","Criteria",[343,6532,6533,6539,6545,6551,6557,6563,6569,6575,6581,6587,6593],{},[346,6534,6535,6538],{},[469,6536,6537],{},"Performance"," How long does it take to run over our example project, a single page webapp with a couple of thousands\nof JavaScript LOC?",[346,6540,6541,6544],{},[469,6542,6543],{},"Licensing"," Does the license meet our requirements (and those of our customers, of course)?",[346,6546,6547,6550],{},[469,6548,6549],{},"Project health/adoption"," How healthy is the project? Is it on Github, and is it well maintained?",[346,6552,6553,6556],{},[469,6554,6555],{},"Completeness of configurations"," Does the tool cover all our use-cases for a linting tool?",[346,6558,6559,6562],{},[469,6560,6561],{},"Productivity (rule set creation / project setup)"," When creating a new project, is it difficult to create a matching\nruleset? Does the tool come with a reasonable default rule set, or do you need to set up all the checks yourself?",[346,6564,6565,6568],{},[469,6566,6567],{},"Productivity (active software development)"," During active development, does the tool assist the developer in\nwriting quality code, or does it bully you to the point where you’d rather abolish using a linting tool at all?",[346,6570,6571,6574],{},[469,6572,6573],{},"Quality of Documentation/Tutorials/Self Help"," How good is the project documentation? When the tool breaks the build\nwith a certain error message, how difficult is it to find reliable information on the error in question (why does it\noccur, why is it a bad practice, how to fix it)?",[346,6576,6577,6580],{},[469,6578,6579],{},"Ability to integrate with existing projects"," Is it possible to integrate the linting tool in an existing projects\nwithout making changes to the project to comply to the rules?",[346,6582,6583,6586],{},[469,6584,6585],{},"Integration with build tool"," Is it possible to integrate the linting tool into your build chain to receive direct\nfeedback?",[346,6588,6589,6592],{},[469,6590,6591],{},"ES6 support"," How well does the project support future versions of the language?",[346,6594,6595,6598],{},[469,6596,6597],{},"Pluggable"," Is it possible to extend the given rule set with custom checks?",[335,6600,6522],{"id":6601},"jshint",[18,6603,6604],{},"The first tool we looked at was the already-familiar JSHint. We already knew what was bothering us about it:",[343,6606,6607,6615],{},[346,6608,6609,6610,6614],{},"Its hard to find the documentation for a certain error message. While the error messages itself are mostly\nself-explanatory, it can be somewhat difficult to find out how to deactivate or customize a certain rule. For\nexample, ",[22,6611,6612],{"href":6612,"rel":6613},"http://jshint.com/docs/options/",[26]," JSHint has both ‘enforcing’ and ‘relaxing’ rules. While setting a ‘enforcing’\nrule to true turns it on, setting a ‘relaxing’ rule to true deactivates it.",[346,6616,6617],{},"More often than not, using JSHint can be frustrating. For example, we had ‘maxdepth’",[18,6619,6620,6621,6624,6625,6628,6629,6632],{},"set to 3, meaning a maximum of three nested blocks of code was allowed. In case one of those blocks was a ‘",[168,6622,6623],{},"for … in","‘\nstatement, JSHint would (correctly) complain that its body should be wrapped in an ‘",[168,6626,6627],{},"if(obj.hasOwnProperty(key))","\n‘-check to filter out unwanted properties. However, doing so meant introducing another nested block, and if that pushed\nthe total depth beyond ‘",[168,6630,6631],{},"maxdepth","‘, JSHint would fail the build. The solution was usually to introduce private helper\nfunctions, which can make otherwise trivial code difficult to read (since you have to skip blocks of code).",[18,6634,6635],{},"Of course, that is not really the fault of JSHint (seeing that it only did what it was told to do), but it was a rather\nbig annoyance that caused us to re-evaluate our JavaScript linting practices in the first place.",[343,6637,6638],{},[346,6639,6640,6641,6646,6647,6652],{},"Being a fork of JSLint, JSHint has the same license containing the\ninfamous ",[22,6642,6645],{"href":6643,"rel":6644,"title":6645},"http://en.wikipedia.org/wiki/JSLint#License",[26],"JSLint License"," ‘Good, not evil’ statement.\nWhile we understand its humorous intent (and being\na ",[22,6648,6651],{"href":6649,"rel":6650,"title":6651},"https://synyx.de/unternehmen/verantwortung_csr/",[26],"fairly social responsible company",",\nwe wholeheartedly support it), we were worried that some corporate lawyer might not approve of our use of a tool bound\nto such a license.",[335,6654,6498],{"id":6655},"jslint",[18,6657,6658],{},"After JSHint we decided to evaluate the old guy in the gang, JSLint. It was the first linting tool for JavaScript, and\nit feels like that. From our opinion JSLint has two major problems, besides the license (see JSHint):",[343,6660,6661,6664,6667],{},[346,6662,6663],{},"The website of JSLint is very old-school and does not contain any explanations of the ruleset or any information to\nget a link between the error messages provided by JSLint and the problem in the code. So you have to search through\nthe internet to find any third-party explanation that will help you to fix the problem.",[346,6665,6666],{},"Some of the rules of JSLint are, at least, strangely named. There is a rule to forbid (or allow, if deactivated)\n‘stupidity’. The project’s web page does not provide any explanation (again) – resorting to Google, we found out that\n‘stupidity’ referred to the usage of synchronous functions in Node.js’s file system module.",[346,6668,6669],{},"The rule set is very strict and, when you look through the GitHub issues and pull requests, it is very hard to\nparticipate in the JSLint project. That’s why the community is very small and there are a lot more people active in\nJSHint and other projects and bring in their ideas there. Maybe that is why JSLint does not provide a rule similar to\n‘latedef’ from JSHint or ‘no-use-before-define’ from ESLint. Without this rule it is very hard to structure your\ncode with private named-functions at the end without assigning the function to a variable at the start (and that\nwould not be what we want).",[335,6671,6673],{"id":6672},"closure-linter","Closure Linter",[18,6675,6676],{},"The Closure Linter is part of Google’s Closure tool set. It was designed for internal use and provides very little\noptions for customization. Since following the rules enforced by it seems to be mandatory within Google, that is\ncertainly an acceptable practice. However, since it is (for example) not possible to change the default maximum line\nlength of 80 characters, we quickly decided not to look into the tool any more.",[335,6678,6513],{"id":6679},"eslint",[18,6681,6682],{},"When looking at ESLint, we were quick to decide that we might be looking at a potential winner:",[343,6684,6685,6688,6691],{},[346,6686,6687],{},"While the project is (by far) the youngest (or as others might put it: the least mature) of the four tools we looked\nat, it is also the best maintained. 100+ contributors on Github and a roughly monthly release schedule speak for\nthemselves.",[346,6689,6690],{},"Applying it to our example web app, we were surprised to find out that it was sufficient to write about ten lines of\nconfiguration to perform the same amount of checks that required around a hundred lines in JSHint. Of course, that\nmight only mean that we have the same idea of quality JavaScript code as the tool’s authors, but nevertheless it meant\nthat the tool would be quite easy to adopt into our development process.",[346,6692,6693],{},"The output is quite handy: It prints both a one-line human readable error message as well as an error code for a\nquick lookup in the documentation.",[335,6695,6697],{"id":6696},"evaluation","Evaluation",[18,6699,6700],{},"The criteria from above has been weighted from five to 15, from not important to important, and the tools got a 0, 0.5\nor 1 if it does not, almost or absolute fulfill the criterion.",[18,6702,6703],{},[32,6704],{"alt":6705,"src":6706},"\"jsLinting\"","https://media.synyx.de/uploads//2015/02/jsLinting2.png",[335,6708,2905],{"id":2904},[18,6710,6711],{},"As you can see in the image above, ESLint proved to be the winner of our evaluation. We decided that its major flaw, the\npotential immaturity, was acceptable to us since by its nature, it would only be used during (internal) development. The\nease of use, both because of the robust and reasonable default rule set and the high-quality documentation, outweighed\nany concern by far. We are looking forward to adopt ESLint into our development tool chain over the coming weeks and\nmonths!",{"title":34,"searchDepth":98,"depth":98,"links":6713},[6714,6715,6716,6717,6718,6719,6720],{"id":6529,"depth":98,"text":6530},{"id":6601,"depth":98,"text":6522},{"id":6655,"depth":98,"text":6498},{"id":6672,"depth":98,"text":6673},{"id":6679,"depth":98,"text":6513},{"id":6696,"depth":98,"text":6697},{"id":2904,"depth":98,"text":2905},[1029,6722],"open-source-blog","2015-02-03T09:45:38","In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\\nlegendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\\ndevelopment teams across synyx, using it never felt 100% comfortable. A quick Google search left\\nus with three alternatives:","https://synyx.de/blog/javascript-linting-tool-evaluation/",{},"/blog/javascript-linting-tool-evaluation",{"title":6467,"description":6729},"In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\nlegendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\ndevelopment teams across synyx, using it never felt 100% comfortable. A quick Google search left\nus with three alternatives:","blog/javascript-linting-tool-evaluation",[1039,6679,6696,670,3539,6601,6655,6732,6733],"linting","software-development","In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the legendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate alternatives to…","QcXdToJb-JTf6znqD4o0GbdwGfu2XcNA6y-tOvH75ck",{"id":6737,"title":6738,"author":6739,"body":6741,"category":7063,"date":7064,"description":7065,"extension":104,"link":7066,"meta":7067,"navigation":107,"path":7068,"seo":7069,"slug":6745,"stem":7070,"tags":7071,"teaser":7077,"__hash__":7078},"blog/blog/the-qt-framework-solid-fun-in-many-languages.md","The Qt framework: solid fun in many languages",[6740],"posch",{"type":11,"value":6742,"toc":7061},[6743,6746,6749,6769,6775,6778,6781,6785,6788,6796,6802,6805,6813,6816,6820,6823,6831,6837,6840,6848,6851,6854,6946,6954,6958,6966,6969,6978,6987,6996,7005,7014,7023,7032,7041,7050,7059],[14,6744,6738],{"id":6745},"the-qt-framework-solid-fun-in-many-languages",[18,6747,6748],{},"Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities\nit’s one of the most-used frameworks for application development. For those who don’t know what Qt is or what it does:\nit’s a comprehensive LGPL-licensed framework providing cross-platform support for GUI, network, multimedia, database,\nsensors, graphics (OpenGL) and many other features. In this article I would like to give a quick overview of these.",[18,6750,6751,6752,180,6755,180,6759,6763,6764,6768],{},"While written in C++, Qt has many language bindings",[22,6753,4080],{"href":6754},"#sdfootnote1sym",[22,6756,6758],{"href":6757},"#sdfootnote2sym","2",[22,6760,6762],{"href":6761},"#sdfootnote3sym","3",",\nincluding for Python, Perl, Ada, Ruby, Java, BASIC, Go, C#, PHP, Lua and Haskell. Any application written in any of\nthese languages and using the Qt framework can be deployed unmodified on any of the supported\nplatforms",[22,6765,6767],{"href":6766},"#sdfootnote4sym","4"," – including all major desktop and mobile platforms – which makes it a popular framework\nfor many big organizations and companies. Some well-known applications written using Qt include Autodesk Maya, Altera\nQuartus, KDE, Google Earth, Skype, Spotify (Linux), Virtualbox and VLC.",[18,6770,6771],{},[32,6772],{"alt":6773,"src":6774},"\"qt_imagecomposer_qt-creator\"","https://media.synyx.de/uploads//2014/09/qt_imagecomposer_qt-creator.jpg",[18,6776,6777],{},"Screenshot 1: Image Composition sample application running on top of Qt Creator IDE.",[18,6779,6780],{},"In addition to the straight Qt framework there is also the Qt Modeling Language (QML) component which can be used to\nrapidly create user interface-centric applications in a JavaScript-based, declarative language. It’s commonly used for\nmobile and embedded applications. A basic QML application can be enhanced using JavaScript code and feature anything\nfrom UI controls to a complete web browser widget (using the WebKit-based module).",[14,6782,6784],{"id":6783},"getting-started","Getting started",[18,6786,6787],{},"When I started using Qt in 2010 Qt 4.7 was the standard. Since then Qt has grown into its current form at version 5.3,\nwith a strong focus on JavaScript and mobile development (using the Qt Quick module, which defines QML), while the\noriginal C++ API also got a makeover. This didn’t change any fundamentals, however, mostly improving library\norganization and features such as accessibility in GUIs.",[18,6789,6790,6791,6795],{},"To quickly build a GUI application, one can use the provided Qt Creator IDE, which includes all of the tools to make any\ntype of application, including non-Qt-based ones. If one wanted to for example create a browser using the Webkit\nbrowser engine, a single class implementation would suffice, as in Qt’s Fancy Browser example",[22,6792,6794],{"href":6793},"#sdfootnote5sym","5",", which\ngoes one step further and even loads a JQuery instance into the JavaScript runtime to perform HTML manipulation.",[18,6797,6798],{},[32,6799],{"alt":6800,"src":6801},"\"qt_fancybrowser\"","https://media.synyx.de/uploads//2014/09/qt_fancybrowser.jpg",[18,6803,6804],{},"Screenshot 2: Fancy Browser example application.",[18,6806,6807,6808,6812],{},"For a hobby project I took this basic concept and made a more full-featured browser",[22,6809,6811],{"href":6810},"#sdfootnote6sym","6",", writing a\ncustom cookie handler among other extensions to the basic Qt classes. With the foundation Qt provides it’s very easy to\nrapidly get started on a project, or to quickly prototype a concept without wasting hours on implementation details.",[18,6814,6815],{},"Whether one uses C++, Python, Ada or another language for which a complete wrapper exists, the basic principle doesn’t\nchange in implementing a Qt-based application. One always uses the same API and same concepts, just molded to fit the\nimplementing language.",[14,6817,6819],{"id":6818},"enter-qml","Enter QML",[18,6821,6822],{},"Even to long-time users of C++/Qt QML can seem quite confusing at first, mostly because of the confusion over what\nQML is and isn’t. In essence QML (Qt Modeling Language) is the name of the modeling language: a descriptive language\nusing which one can define user interface elements and their behavior. QML is part of Qt Quick, the UI creation kit\nwhich itself is part of the Qt framework. Finally, the runtime for QML is called Qt Declarative.",[18,6824,6825,6826,6830],{},"Places where QML is used include (outside of mobile/embedded) KDE and the Unity UI (as of version 8",[22,6827,6829],{"href":6828},"#sdfootnote7sym","7",")\nwhich is used by Ubuntu. The main motivations behind the use of a QML-based UI seem to revolve around the language and\nplatform agnostic nature of it. All one needs is the QML runtime whereby one can add JavaScript and C++ code for\nfurther functionality. Unity 8 uses QML to ease the cross-platform deployment across desktop and mobile devices (\nrunning Ubuntu Touch).",[18,6832,6833],{},[32,6834],{"alt":6835,"src":6836},"\"Qt PhotoViewer sample\"","https://media.synyx.de/uploads//2014/09/qt_photo_viewer.jpg",[18,6838,6839],{},"Screenshot 3: Photo Viewer example. QML with minimal JavaScript.",[18,6841,6842,6843,6847],{},"The Photo Viewer QML example application",[22,6844,6846],{"href":6845},"#sdfootnote8sym","8"," on the Qt site is a good example of how much one can do\nwith just QML: this application allows one to define all views of the application with transitions, widgets and the\nXML-based model which retrieves image URLs from the Flickr public API. The JavaScript file is just used for some minor\nutility functions.",[18,6849,6850],{},"In theory one could extend the JavaScript side to include more or additional logic, and use a C++ extension for\nexample image processing or similar functionality. Where one puts the logic and which features are included would be\ndetermined by the available resources and intended languages. One can also use QML with just C++, or pure QML with no\nadditional languages. Many QML applications can be readily deployed on a mobile device as well.",[18,6852,6853],{},"QML isn’t just about static content either. Using Qt’s multimedia features one can for example quickly set up a video\nplayer:",[492,6855,6857],{"className":3047,"code":6856,"language":3049,"meta":34,"style":34},"import QtQuick 2.0\nimport QtMultimedia 5.0\nVideo {\n id: video\n width : 800\n height : 600\n source: \"video.avi\"\n \u003Ca class=\"broken_link\" href=\"http://qt-project.org/doc/qt-5/qml-qtquick-mousearea.html\">MouseArea\u003C/a> {\n anchors.fill: parent\n onClicked: {\n video.play()\n }\n }\n focus: true\n Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()\n Keys.onLeftPressed: video.seek(video.position - 5000)\n Keys.onRightPressed: video.seek(video.position + 5000)\n}\n",[405,6858,6859,6864,6869,6874,6879,6884,6889,6894,6899,6904,6909,6914,6918,6922,6927,6932,6937,6942],{"__ignoreMap":34},[500,6860,6861],{"class":502,"line":503},[500,6862,6863],{},"import QtQuick 2.0\n",[500,6865,6866],{"class":502,"line":98},[500,6867,6868],{},"import QtMultimedia 5.0\n",[500,6870,6871],{"class":502,"line":249},[500,6872,6873],{},"Video {\n",[500,6875,6876],{"class":502,"line":583},[500,6877,6878],{}," id: video\n",[500,6880,6881],{"class":502,"line":615},[500,6882,6883],{}," width : 800\n",[500,6885,6886],{"class":502,"line":621},[500,6887,6888],{}," height : 600\n",[500,6890,6891],{"class":502,"line":631},[500,6892,6893],{}," source: \"video.avi\"\n",[500,6895,6896],{"class":502,"line":641},[500,6897,6898],{}," \u003Ca class=\"broken_link\" href=\"http://qt-project.org/doc/qt-5/qml-qtquick-mousearea.html\">MouseArea\u003C/a> {\n",[500,6900,6901],{"class":502,"line":647},[500,6902,6903],{}," anchors.fill: parent\n",[500,6905,6906],{"class":502,"line":805},[500,6907,6908],{}," onClicked: {\n",[500,6910,6911],{"class":502,"line":810},[500,6912,6913],{}," video.play()\n",[500,6915,6916],{"class":502,"line":826},[500,6917,1527],{},[500,6919,6920],{"class":502,"line":838},[500,6921,940],{},[500,6923,6924],{"class":502,"line":937},[500,6925,6926],{}," focus: true\n",[500,6928,6929],{"class":502,"line":943},[500,6930,6931],{}," Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()\n",[500,6933,6934],{"class":502,"line":948},[500,6935,6936],{}," Keys.onLeftPressed: video.seek(video.position - 5000)\n",[500,6938,6939],{"class":502,"line":954},[500,6940,6941],{}," Keys.onRightPressed: video.seek(video.position + 5000)\n",[500,6943,6944],{"class":502,"line":1898},[500,6945,802],{},[18,6947,6948,6949,6953],{},"This sample, taken from the Qt Video QML type documentation",[22,6950,6952],{"href":6951},"#sdfootnote9sym","9"," shows just how easy it is to set up a\nresponsive user interface with QML and to add elements which not only respond to user inputs, but can use video and\naudio as well.",[14,6955,6957],{"id":6956},"wrapping-up","Wrapping up",[18,6959,6960,6961,6965],{},"This article has barely scratched the surface of what Qt is capable of. The multi-threading, networking, multimedia,\ngraphics acceleration, storage-related and many other features are at least as interesting. Using the many sample\napplications on the Qt site",[22,6962,6964],{"href":6963},"#sdfootnote10sym","10"," it’s easy to get an idea of the possibilities, however. Simply\ndownload the current version of the libraries together with Qt Creator and browse through the examples in the Welcome\ntab of the IDE, or check them out online.",[18,6967,6968],{},"Finally, if anyone reading has experience with any of the language wrappers for Qt, please leave a comment. I’d be very\ninterested in hearing how well they work.",[18,6970,6971,6974],{},[22,6972,4080],{"href":6973},"#sdfootnote1anc",[22,6975,6976],{"href":6976,"rel":6977},"http://qt-project.org/wiki/Category:LanguageBindings",[26],[18,6979,6980,6983],{},[22,6981,6758],{"href":6982},"#sdfootnote2anc",[22,6984,6985],{"href":6985,"rel":6986},"http://en.wikipedia.org/wiki/List_of_language_bindings_for_Qt_4",[26],[18,6988,6989,6992],{},[22,6990,6762],{"href":6991},"#sdfootnote3anc",[22,6993,6994],{"href":6994,"rel":6995},"http://en.wikipedia.org/wiki/List_of_language_bindings_for_Qt_5",[26],[18,6997,6998,7001],{},[22,6999,6767],{"href":7000},"#sdfootnote4anc",[22,7002,7003],{"href":7003,"rel":7004},"http://qt-project.org/doc/qt-5/supported-platforms.html",[26],[18,7006,7007,7010],{},[22,7008,6794],{"href":7009},"#sdfootnote5anc",[22,7011,7012],{"href":7012,"rel":7013},"http://qt-project.org/doc/qt-5/qtwebkitexamples-webkitwidgets-fancybrowser-example.html",[26],[18,7015,7016,7019],{},[22,7017,6811],{"href":7018},"#sdfootnote6anc",[22,7020,7021],{"href":7021,"rel":7022},"http://mayaposch.com/wildfox.php",[26],[18,7024,7025,7028],{},[22,7026,6829],{"href":7027},"#sdfootnote7anc",[22,7029,7030],{"href":7030,"rel":7031},"https://unity.ubuntu.com/getinvolved/development/unity8/",[26],[18,7033,7034,7037],{},[22,7035,6846],{"href":7036},"#sdfootnote8anc",[22,7038,7039],{"href":7039,"rel":7040},"http://qt-project.org/doc/qt-5/qtquick-demos-photoviewer-example.html",[26],[18,7042,7043,7046],{},[22,7044,6952],{"href":7045},"#sdfootnote9anc",[22,7047,7048],{"href":7048,"rel":7049},"http://qt-project.org/doc/qt-5/qml-qtmultimedia-video.html",[26],[18,7051,7052,7055],{},[22,7053,6964],{"href":7054},"#sdfootnote10anc",[22,7056,7057],{"href":7057,"rel":7058},"http://qt-project.org/",[26],[1017,7060,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":7062},[],[1029],"2014-09-18T17:15:03","Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities\\nit’s one of the most-used frameworks for application development. For those who don’t know what Qt is or what it does:\\nit’s a comprehensive LGPL-licensed framework providing cross-platform support for GUI, network, multimedia, database,\\nsensors, graphics (OpenGL) and many other features. In this article I would like to give a quick overview of these.","https://synyx.de/blog/the-qt-framework-solid-fun-in-many-languages/",{},"/blog/the-qt-framework-solid-fun-in-many-languages",{"title":6738,"description":6748},"blog/the-qt-framework-solid-fun-in-many-languages",[7072,7073,670,1684,7074,7075,7076],"c","cross-platform","qml","qt","qt-quick","Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities it’s one of the most-used frameworks for application development. For those who…","pEQ_OlddqNbA7CWP-ltLaMJp7EGQfBl6i6zBkOADnbs",{"id":7080,"title":7081,"author":7082,"body":7084,"category":7243,"date":7244,"description":7093,"extension":104,"link":7245,"meta":7246,"navigation":107,"path":7247,"seo":7248,"slug":7088,"stem":7249,"tags":7250,"teaser":7252,"__hash__":7253},"blog/blog/javaforum-stuttgart-2014.md","JavaForum Stuttgart 2014",[7083],"heib",{"type":11,"value":7085,"toc":7241},[7086,7089,7094,7097,7100,7105,7108,7113,7122,7127,7130,7135,7138,7143,7158,7163,7172,7177,7180,7185,7194,7199,7202,7207,7216,7221,7230,7235,7238],[14,7087,7081],{"id":7088},"javaforum-stuttgart-2014",[18,7090,7091],{},[469,7092,7093],{},"Warum in die Ferne schweifen, wenn das Gute doch so nah ist?",[18,7095,7096],{},"Das dachten sich dieses Jahr auch vier Entwickler von synyx, und machten sich daher am 17.07. auf nach Stuttgart zum\nJavaForum der Java User Group Stuttgart (JUGS).",[18,7098,7099],{},"Die Vorteile liegen auf der Hand: kurze An-/ Abreise, große Auswahl an guten Vorträgen, super Verpflegung, und das\nalles zu einem relativ günstigen Preis.",[18,7101,7102],{},[469,7103,7104],{},"08:05 Uhr, Hauptbahnhof Stuttgart",[18,7106,7107],{},"Alle da? Wo müssen wir hin? Raus aus dem Bahnhof, der Masse nach zum Kongresszentrum Liederhalle. Zehn Minuten laufen\nsind bestimmt nicht verkehrt – sitzen werden wir heute noch genug. Der Checkin zum JavaForum funktioniert dank vorher\nzugeschickter Barcode-Scheckkarte schnell und problemlos. Dann erst mal orientieren: Wo gibts was? Kaffee? Ah, da.\nPraktischerweise gibts auch Brezeln – also Zeit für ein zweites Frühstück.",[18,7109,7110],{},[469,7111,7112],{},"8:45 Uhr, Hegel-Saal",[18,7114,7115,7116,7121],{},"Zum Warm-Up: Thorsten Maier\nmit “",[22,7117,7120],{"href":7118,"rel":7119},"http://www.java-forum-stuttgart.de/_data/D1_2014.pdf",[26],"Effektiver Einsatz von Code-Reviews","“. Keine Tool-Show (\npuh, Glück gehabt), dafür umso mehr praktische Infos rund um die Reviews. Warum wollen wir überhaupt Reviews machen? Und\nwie führt man diese praktisch durch? Insgesamt sehr praxisnah und einfach umzusetzen. Das werden wir nächste Woche\ngleich mal ausprobieren.",[18,7123,7124],{},[469,7125,7126],{},"9:50 Uhr, Raum Sylt",[18,7128,7129],{},"Nach etwas suchen den Tagungsraum im obersten Stockwerk gefunden. Mit “Leittechnik für Bahnsysteme mit Eclipse” von\nChristian Scholz geht es hier mal um etwas womit man im Entwickleralltag eher nie zu tun hat, es aber fast jeden Tag (\npassiv) nutzt. Da die Domaine sehr spannend klang dachte ich wäre hier eine Horizonterweiterung bestimmt auch nicht\nschlecht. Leider ist der Vortrag nicht ganz so gut wie erwartet – insgesamt habe ich den Eindruck dass es etwas\ndurcheinander ist, sicherlich auch bedingt durch viele Fachbegriffe sowie den englischsprachigen Foliensatz (weniger\nwäre hier sicher mehr gewesen). Mag sein dass der gute Mann einfach auch nicht mehr (Details) erzählen durfte…",[18,7131,7132],{},[469,7133,7134],{},"10:40 Uhr, Foyer",[18,7136,7137],{},"Wo gibt es die Croissants mit denen hier alle rum laufen?",[18,7139,7140],{},[469,7141,7142],{},"11:10 Uhr, Schiller-Saal",[18,7144,7145,7146,7151,7152,7157],{},"Im dritten Block geht es\num “",[22,7147,7150],{"href":7148,"rel":7149},"http://www.java-forum-stuttgart.de/_data/A3_2014.pdf",[26],"Enterprise integration Patterns Revisited","“. Kai Wähner trägt\ndas Thema sehr kurzweilig vor – die 45 Minuten fliegen recht dahin. Die wichtigsten Kernpunkte: letztlich hat wohl schon\njeder EAIs verwendet – wenn auch unbewusst. Daher:\ndas ",[22,7153,7156],{"href":7154,"rel":7155},"http://www.amazon.de/Enterprise-Integration-Patterns-Designing-Deploying/dp/0321200683/ref=sr_1_1?ie=UTF8",[26],"EAI Buch von Hohpe und Woolf","\nlesen, und dann die Patterns nicht selbst implementieren, sondern fertige Frameworks bzw. Tools verwenden.",[18,7159,7160],{},[469,7161,7162],{},"12:15 Uhr, Beethoven-Saal",[18,7164,7165,7166,7171],{},"Vor der Mittagspause noch was neues zu Java 8\nlernen: “",[22,7167,7170],{"href":7168,"rel":7169},"http://www.java-forum-stuttgart.de/_data/E4_2014.pdf",[26],"Lambdas, Collections und Streams","“. Michael Wiedeking\nträgt sehr unterhaltsam vor wie man mit Lambda Ausdrücken und Streams in Java 8 sehr elegant tolle Sachen machen kann.\nDas ganze in einem wahnsinns schnellen Tempo. Ich weiß zwar jetzt was geht – damit das wirklich anwendbar wird, muss ich\nmich wohl aber erst mal selbst hin setzen und etwas damit herumspielen. Trotz der Geschwindigkeit des Vortrages war es\nbisher wohl der beste (und auch unterhaltsamste) Vortrag des Tages.",[18,7173,7174],{},[469,7175,7176],{},"13:00 Uhr, Foyer",[18,7178,7179],{},"Mittagessen – also schön in die Schlange am Buffet einreihen. Die Auswahl ist groß, man weiß garnicht was man angesichts\ndes Angebots auf den (dafür) viel zu kleinen Teller laden soll. Aber egal wofür man sich entscheidet: alles sehr lecker!\nVorspeise, Hauptgang, Nachspeise – Zeit während der Mittagspause noch etwas den Sonnenschein draußen zu genießen und\nsich zu bewegen.",[18,7181,7182],{},[469,7183,7184],{},"14:30 Uhr, Raum Usedom",[18,7186,7187,7188,7193],{},"“",[22,7189,7192],{"href":7190,"rel":7191},"http://www.java-forum-stuttgart.de/_data/E5_2014.pdf",[26],"Slim Fast","“. Heiko Rupp scheint angesicht des vielen Essens auch\nschon bedenken im Bezug auf seine Figur zu haben. Allerdings soll es hier nicht darum gehen wie wir abnehmen können,\nsondern wie der benötigte Heap unserer Anwendung schlanker werden kann. Gezeigt werden einige Tools zur Analyse des\nHeaps (VisualVM, Eclipse MAT), sowie typische Beispiele wo man auf einfache Art viel Speicher sparen kann. Wissen dass\nwir in unseren Projekten sicherlich noch gewinnbringend einsetzen können.",[18,7195,7196],{},[469,7197,7198],{},"15:20 Uhr, Foyer",[18,7200,7201],{},"Bevor dass jemand verhungert: Zeit für lecker Kuchen!",[18,7203,7204],{},[469,7205,7206],{},"15:35 Uhr, Beethoven-Saal",[18,7208,7209,7210,7215],{},"In Zeiten sich täglich überschlagender Meldungen von geklauten Daten und Passwörter so aktuell wie\nnie: “",[22,7211,7214],{"href":7212,"rel":7213},"http://www.java-forum-stuttgart.de/_data/F6_2014.pdf",[26],"Krypto für Java-Entwickler","“, vorgetragen von Dominik\nSchadow. Es geht darum dass man auf jeden Fall auf bewährte Krypto-Verfahren setzen sollte, Java bringt hier\nout-of-the-box schon sehr viel mit. Da dies in der Umsetzung dann allerdings oft eher etwas umständlich ist, gibt es\nauch hier Frameworks welche es dem Entwickler leicht machen Daten ordentlich zu verschlüsseln. Wichtigste Message:\nverschlüsseln ja, dabei auf fertige Bibliotheken und Algorithmen vertrauen und nichts selbst implementieren.",[18,7217,7218],{},[469,7219,7220],{},"16:40 Uhr, Raum Usedom",[18,7222,7223,7224,7229],{},"Zum Endspurt noch einmal hinauf ins oberste Stockwerk\nzu “",[22,7225,7228],{"href":7226,"rel":7227},"http://www.java-forum-stuttgart.de/_data/A7_2014.pdf",[26],"Entwicklung von BigData-Systemen mit Apache Cassandra","“.\nPhilipp Stussak stellt die grundsätzliche Funktionsweise dieser NoSQL Datenbank vor, und zeigt sehr deutlich welche\nVorteile diese gegenüber einer herkömmlichen (relationalen) Datenbank hat. Auf jeden Fall sehr interessant, vor allem\nwenn man sehr viele Daten zu speichern hat.",[18,7231,7232],{},[469,7233,7234],{},"17:40 Uhr, Stuttgart Innenstadt",[18,7236,7237],{},"Auf dem Weg zurück zum Bahnhof. Rückblickend hat sich der Tag in Stuttgart auf jeden Fall gelohnt. Die Vorträge waren\ninsgesamt alle sehr informativ. Vieles wurde sicherlich nur oberflächlich angerissen (mehr ist in der kurzen Zeit aber\nauch kaum möglich), die Vorträge bieten aber jeweils einen guten Startpunkt um selbst tiefer in die einzelnen Themen\neinzusteigen.",[18,7239,7240],{},"JavaForum Stuttgart – nächstes Jahr gerne wieder. Und nicht nur wegen dem Essen 😉",{"title":34,"searchDepth":98,"depth":98,"links":7242},[],[256],"2014-07-22T11:50:17","https://synyx.de/blog/javaforum-stuttgart-2014/",{},"/blog/javaforum-stuttgart-2014",{"title":7081,"description":7093},"blog/javaforum-stuttgart-2014",[113,869,7251,267,268],"javaforum","Warum in die Ferne schweifen, wenn das Gute doch so nah ist? Das dachten sich dieses Jahr auch vier Entwickler von synyx, und machten sich daher am 17.07. auf nach…","LoqlLEnXvn-aE3Wc2m1OLELW-ezLTZNNiILSLwq7qW0",{"id":7255,"title":7256,"author":7257,"body":7258,"category":8370,"date":8371,"description":34,"extension":104,"link":8372,"meta":8373,"navigation":107,"path":8374,"seo":8375,"slug":7262,"stem":8376,"tags":8377,"teaser":8384,"__hash__":8385},"blog/blog/code-gluse.md","Code gluse",[4856],{"type":11,"value":7259,"toc":8364},[7260,7263,7266,7276,7279,7282,7285,7291,7301,7308,7311,7314,7323,7395,7401,7404,7432,7439,7491,7494,7688,7691,7694,7698,7701,7718,7724,7727,7737,7740,7743,7750,7757,7787,7790,7793,7826,7829,7832,7886,7893,7899,7902,7905,8002,8004,8008,8017,8022,8036,8039,8050,8057,8059,8082,8088,8116,8226,8229,8232,8235,8242,8245,8248,8251,8254,8361],[14,7261,7256],{"id":7262},"code-gluse",[133,7264,7256],{"id":7265},"code-gluse-1",[18,7267,7268,7269,7272,7273,7275],{},"Today’s post targets an API, which has been released on Dec. 11, 2006; the ",[405,7270,7271],{},"javax.scripting"," package ",[500,7274,4080],{}," and a lot of\ngood articles that have been written around it.",[18,7277,7278],{},"The intention for this post is not about ‘how to use the scripting packaging’, but about gluse. So what do I mean with\nthe phrase gluse? Gluse is a coinage",[18,7280,7281],{},"for glue and (re)usage. As many of the Java developer know about the plenty of good libraries from maven central /\ngithub and the integration process, a few of them",[18,7283,7284],{},"might ask how to integrate libraries from other languages as well. As many of the every day problems have already bean\naddressed, there is a good chance that someone else has done the job for you and is willing to share.",[18,7286,7287,7288,7290],{},"Sometimes it’s written in pure Java, sometimes in a different language. Let’s see how to integrate the latter\nlibraries. (StackOverflow lists a dozen ",[500,7289,6758],{}," btw.)",[18,7292,7293,7294,7297,7298,7300],{},"The next parts will give you some information in form of three examples. The first and second example will address\nJavascript, as Javascript is getting more and more into the focus of developers and Oracle will ship their new engine\n‘Nashorn’ with the next Java 8 release, while the third example will target a more complex example using JRuby. ",[469,7295,7296],{},"All\nexamples"," can be downloaded from ",[500,7299,6762],{},". So it’s up to you if you want to read the sources in parallel, afterwards, or by\nplaying with the code in your IDE instantly.",[18,7302,7303,7304,7307],{},"All code examples have been written to be compatible with Java 1.6. See the note in example two, when it comes to the\n",[405,7305,7306],{},"bind"," function.",[133,7309,7310],{"id":3354},"Proxy",[18,7312,7313],{},"Lets discuss the first example: We want to replace parts of a string using a regular expression, but hook into the\nprocess of manipulating the matching elements, before the replacement eventually takes place – by adding some new\ncontent or returning something completely different.",[18,7315,7316,7317,7320,7321,707],{},"In Java you would probably end up using the ",[405,7318,7319],{},"java.util.regex.Pattern",", creating a matcher and iterating over the matched\ngroups and so on. There’s nothing wrong about it, but Javascript already defines that kind of behaviour ",[500,7322,6767],{},[492,7324,7326],{"className":668,"code":7325,"language":670,"meta":34,"style":34},"\"first 000 second\".replace(/[a-zA-Z]+/g, function (match) {\n return \"[\" + match.toUpperCase() + \"]\";\n});\n",[405,7327,7328,7365,7391],{"__ignoreMap":34},[500,7329,7330,7333,7335,7338,7340,7343,7346,7349,7351,7354,7356,7358,7360,7363],{"class":502,"line":503},[500,7331,7332],{"class":520},"\"first 000 second\"",[500,7334,707],{"class":506},[500,7336,7337],{"class":513},"replace",[500,7339,713],{"class":506},[500,7341,7342],{"class":520},"/",[500,7344,7345],{"class":703},"[a-zA-Z]",[500,7347,7348],{"class":677},"+",[500,7350,7342],{"class":520},[500,7352,7353],{"class":677},"g",[500,7355,719],{"class":506},[500,7357,3556],{"class":677},[500,7359,725],{"class":506},[500,7361,7362],{"class":728},"match",[500,7364,3688],{"class":506},[500,7366,7367,7369,7372,7375,7378,7381,7384,7386,7389],{"class":502,"line":98},[500,7368,3851],{"class":677},[500,7370,7371],{"class":520}," \"[\"",[500,7373,7374],{"class":677}," +",[500,7376,7377],{"class":506}," match.",[500,7379,7380],{"class":513},"toUpperCase",[500,7382,7383],{"class":506},"() ",[500,7385,7348],{"class":677},[500,7387,7388],{"class":520}," \"]\"",[500,7390,3600],{"class":506},[500,7392,7393],{"class":502,"line":249},[500,7394,841],{"class":506},[18,7396,7397,7398,7400],{},"As both, Rhino and Nashorn, support the javax.script.Invocable type, we will create an interface to address the\nproblem – you’ll find the whole documentation in the mentioned project ",[500,7399,6762],{},", but for the sake of completeness:",[18,7402,7403],{},"Apply the ‘pattern’ on the ‘sequence’ and call the ‘callback’ on each matched element. Either on ‘all’ matching\nelements, or on ‘any’ (first makes sense here).",[492,7405,7407],{"className":867,"code":7406,"language":869,"meta":34,"style":34},"\n public interface Replacement {\n public abstract CharSequence any (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n public abstract CharSequence all (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n }\n\n",[405,7408,7409,7413,7418,7423,7428],{"__ignoreMap":34},[500,7410,7411],{"class":502,"line":503},[500,7412,644],{"emptyLinePlaceholder":107},[500,7414,7415],{"class":502,"line":98},[500,7416,7417],{}," public interface Replacement {\n",[500,7419,7420],{"class":502,"line":249},[500,7421,7422],{}," public abstract CharSequence any (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n",[500,7424,7425],{"class":502,"line":583},[500,7426,7427],{}," public abstract CharSequence all (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n",[500,7429,7430],{"class":502,"line":615},[500,7431,940],{},[18,7433,7434,7435,7438],{},"The final Java code would look like the following (Java 8 users will flavour the new lambda syntax:\n",[405,7436,7437],{},"(match) -> { return \"[\" + match + \"]\"; }","):",[492,7440,7442],{"className":867,"code":7441,"language":869,"meta":34,"style":34},"\n Replacement replacement;\n replacement = replacement ();\n CharSequence enclosed = replacement.all (Pattern.compile (\"\\\\d+\"), \"could you please enclose 1234, 789, 345 with brackets?\", new Function\u003CCharSequence, CharSequence> () {\n @Override\n public CharSequence apply (CharSequence sequence) {\n return \"[\" + sequence + \"]\";\n }\n });\n /* replacement () returns a proxy of the type Replacement, using the shipped js scripting engine. the evaluated script returns an instance, which can be encapsulated using the Invocable#getInterface signature */\n\n",[405,7443,7444,7448,7453,7458,7463,7468,7473,7478,7482,7486],{"__ignoreMap":34},[500,7445,7446],{"class":502,"line":503},[500,7447,644],{"emptyLinePlaceholder":107},[500,7449,7450],{"class":502,"line":98},[500,7451,7452],{}," Replacement replacement;\n",[500,7454,7455],{"class":502,"line":249},[500,7456,7457],{}," replacement = replacement ();\n",[500,7459,7460],{"class":502,"line":583},[500,7461,7462],{}," CharSequence enclosed = replacement.all (Pattern.compile (\"\\\\d+\"), \"could you please enclose 1234, 789, 345 with brackets?\", new Function\u003CCharSequence, CharSequence> () {\n",[500,7464,7465],{"class":502,"line":615},[500,7466,7467],{}," @Override\n",[500,7469,7470],{"class":502,"line":621},[500,7471,7472],{}," public CharSequence apply (CharSequence sequence) {\n",[500,7474,7475],{"class":502,"line":631},[500,7476,7477],{}," return \"[\" + sequence + \"]\";\n",[500,7479,7480],{"class":502,"line":641},[500,7481,1527],{},[500,7483,7484],{"class":502,"line":647},[500,7485,792],{},[500,7487,7488],{"class":502,"line":805},[500,7489,7490],{}," /* replacement () returns a proxy of the type Replacement, using the shipped js scripting engine. the evaluated script returns an instance, which can be encapsulated using the Invocable#getInterface signature */\n",[18,7492,7493],{},"The Javascript implementation would look like:",[492,7495,7497],{"className":668,"code":7496,"language":670,"meta":34,"style":34},"\n(function () {\n function replace (regex, content, callback) {\n ...\n }\n var Replacement = function () {};\n Replacement.prototype.any = function (regex, content, callback) {\n return replace (new RegExp (regex), content, callback);\n };\n Replacement.prototype.all = function (regex, content, callback) {\n return replace (new RegExp (regex, 'g'), content, callback);\n };\n return new Replacement ();\n}) ();\n\n",[405,7498,7499,7503,7511,7535,7540,7544,7559,7592,7610,7615,7646,7667,7671,7683],{"__ignoreMap":34},[500,7500,7501],{"class":502,"line":503},[500,7502,644],{"emptyLinePlaceholder":107},[500,7504,7505,7507,7509],{"class":502,"line":98},[500,7506,713],{"class":506},[500,7508,3556],{"class":677},[500,7510,3559],{"class":506},[500,7512,7513,7516,7519,7521,7524,7526,7528,7530,7533],{"class":502,"line":249},[500,7514,7515],{"class":677}," function",[500,7517,7518],{"class":513}," replace",[500,7520,725],{"class":506},[500,7522,7523],{"class":728},"regex",[500,7525,719],{"class":506},[500,7527,5684],{"class":728},[500,7529,719],{"class":506},[500,7531,7532],{"class":728},"callback",[500,7534,3688],{"class":506},[500,7536,7537],{"class":502,"line":583},[500,7538,7539],{"class":677}," ...\n",[500,7541,7542],{"class":502,"line":615},[500,7543,940],{"class":506},[500,7545,7546,7549,7552,7554,7556],{"class":502,"line":621},[500,7547,7548],{"class":677}," var",[500,7550,7551],{"class":513}," Replacement",[500,7553,3783],{"class":677},[500,7555,3786],{"class":677},[500,7557,7558],{"class":506}," () {};\n",[500,7560,7561,7564,7566,7569,7571,7574,7576,7578,7580,7582,7584,7586,7588,7590],{"class":502,"line":631},[500,7562,7563],{"class":703}," Replacement",[500,7565,707],{"class":506},[500,7567,7568],{"class":703},"prototype",[500,7570,707],{"class":506},[500,7572,7573],{"class":513},"any",[500,7575,3783],{"class":677},[500,7577,3786],{"class":677},[500,7579,725],{"class":506},[500,7581,7523],{"class":728},[500,7583,719],{"class":506},[500,7585,5684],{"class":728},[500,7587,719],{"class":506},[500,7589,7532],{"class":728},[500,7591,3688],{"class":506},[500,7593,7594,7597,7599,7601,7604,7607],{"class":502,"line":641},[500,7595,7596],{"class":677}," return",[500,7598,7518],{"class":513},[500,7600,725],{"class":506},[500,7602,7603],{"class":677},"new",[500,7605,7606],{"class":513}," RegExp",[500,7608,7609],{"class":506}," (regex), content, callback);\n",[500,7611,7612],{"class":502,"line":647},[500,7613,7614],{"class":506}," };\n",[500,7616,7617,7619,7621,7623,7625,7628,7630,7632,7634,7636,7638,7640,7642,7644],{"class":502,"line":805},[500,7618,7563],{"class":703},[500,7620,707],{"class":506},[500,7622,7568],{"class":703},[500,7624,707],{"class":506},[500,7626,7627],{"class":513},"all",[500,7629,3783],{"class":677},[500,7631,3786],{"class":677},[500,7633,725],{"class":506},[500,7635,7523],{"class":728},[500,7637,719],{"class":506},[500,7639,5684],{"class":728},[500,7641,719],{"class":506},[500,7643,7532],{"class":728},[500,7645,3688],{"class":506},[500,7647,7648,7650,7652,7654,7656,7658,7661,7664],{"class":502,"line":810},[500,7649,7596],{"class":677},[500,7651,7518],{"class":513},[500,7653,725],{"class":506},[500,7655,7603],{"class":677},[500,7657,7606],{"class":513},[500,7659,7660],{"class":506}," (regex, ",[500,7662,7663],{"class":520},"'g'",[500,7665,7666],{"class":506},"), content, callback);\n",[500,7668,7669],{"class":502,"line":826},[500,7670,7614],{"class":506},[500,7672,7673,7675,7678,7680],{"class":502,"line":838},[500,7674,3621],{"class":677},[500,7676,7677],{"class":677}," new",[500,7679,7551],{"class":513},[500,7681,7682],{"class":506}," ();\n",[500,7684,7685],{"class":502,"line":937},[500,7686,7687],{"class":506},"}) ();\n",[18,7689,7690],{},"The Java code for this example would probably be less – measured in LOC – but the basic steps needed for an integration\ncan be shown pretty good and two worlds might benefit from your certainly approved works 🙂",[18,7692,7693],{},"One nice feature about this kind of mechanism is, that you can quickly prototype your functionality, while still able to\nchange parts of the implementation using pure Java afterwards.",[133,7695,7697],{"id":7696},"modularity","Modularity",[18,7699,7700],{},"Let’s come to the second example. You may have written a bunch of Javascript files in a modular way or just don’t want\nto put everything in a single file. While the first example showed how to proxy your implementation, the second example\nwill show you a basic approach for dynamically loading further resource and/or code files. The following signature\nshould be provided and accessible from all scripts.",[492,7702,7704],{"className":668,"code":7703,"language":670,"meta":34,"style":34},"require(\"org.geonames.reverse\");\n",[405,7705,7706],{"__ignoreMap":34},[500,7707,7708,7711,7713,7716],{"class":502,"line":503},[500,7709,7710],{"class":513},"require",[500,7712,713],{"class":506},[500,7714,7715],{"class":520},"\"org.geonames.reverse\"",[500,7717,4107],{"class":506},[18,7719,7720,7721,7723],{},"The similarity to requirejs ",[500,7722,6794],{}," is intentional and you may want to extend the signature to be fully compliant, but this\nwill be left for your curiosity 🙂",[18,7725,7726],{},"Loading resources from ‘unknown’, or from ‘at runtime unknown’ sources is by nature critical, as the code is executed in\nthe same JVM which hosts your application as well. Therefore, you should only load resources you really trust.",[18,7728,7729,7730,7732,7733,7736],{},"You could achieve this by verifying the signature of the reviewed files using a PKI ",[500,7731,6811],{}," infrastructure – ",[405,7734,7735],{},"javax.crypto","\nis your friend here and fortunately you can implement this in Java and/or use a security library to accomplish this\ntask.",[18,7738,7739],{},"Simply spoken: Always check the integrity if you provide a way for modifications.",[18,7741,7742],{},"If you are already familiar with the scripting engine API, you might have noticed that require is a function and not an\n‘object’.",[18,7744,7745,7746,7749],{},"Even when functions in Javascript are objects, there is no semantic way to say that ",[168,7747,7748],{},"“this object is a function and can\nbe invoked”"," if you share it between the environment.",[18,7751,7752,7753,7756],{},"There might be support for some engines, but not for the others and ",[405,7754,7755],{},"javax.script"," API is designed for general purpose –\ndepending on engine internal interfaces is not desired.",[492,7758,7760],{"className":668,"code":7759,"language":670,"meta":34,"style":34},"obj.require(\n \"org.geonames.reverse\",\n); /* nah, okay but requires additional knowledge obj. */\n",[405,7761,7762,7772,7779],{"__ignoreMap":34},[500,7763,7764,7767,7769],{"class":502,"line":503},[500,7765,7766],{"class":506},"obj.",[500,7768,7710],{"class":513},[500,7770,7771],{"class":506},"(\n",[500,7773,7774,7777],{"class":502,"line":98},[500,7775,7776],{"class":520}," \"org.geonames.reverse\"",[500,7778,835],{"class":506},[500,7780,7781,7784],{"class":502,"line":249},[500,7782,7783],{"class":506},"); ",[500,7785,7786],{"class":3586},"/* nah, okay but requires additional knowledge obj. */\n",[18,7788,7789],{},"Fortunately there is a solution. You can attach a script context to the evaluation process and reuse the context later\non, but you shouldn’t use the internal context as it could leak if your engine leaks.",[18,7791,7792],{},"Pseudo Algorithm:",[1622,7794,7795,7814,7817,7820,7823],{},[346,7796,7797,7798,719,7801,719,7804,719,7807,7810,7811],{},"create a java function object which can load your resources from the ",[168,7799,7800],{},"classpath",[168,7802,7803],{},"internet",[168,7805,7806],{},"local filesystem",[168,7808,7809],{},"…","\nwith a method signature you know. (a function/SAM object) like: ",[405,7812,7813],{},"void apply (String)",[346,7815,7816],{},"create a script context and attach the object from 1. to it with a variable called ‘whatever’ (really whatever name\nyou like)",[346,7818,7819],{},"evaluate an inline require function before you evaluate your business code, which puts your require function into the\nscope of the context from 2.",[346,7821,7822],{},"evaluate your business codes which relies on the require function with the same scope from 2.",[346,7824,7825],{},"have fun",[18,7827,7828],{},"var require = function (library) { whatever.apply (library); }",[18,7830,7831],{},"The above code would be sufficient, but has some drawbacks as it only works if the ‘whatever’ object is in the correct\nexecution scope and if it provides the correct signature – someone could overwrite the binding or does something you\nsimply don’t want him/her to do. We need some slight improvements to correct this.",[492,7833,7835],{"className":668,"code":7834,"language":670,"meta":34,"style":34},"var require = function (library) {\n this.apply(library);\n}.bind(whatever);\ndelete whatever;\n",[405,7836,7837,7855,7868,7878],{"__ignoreMap":34},[500,7838,7839,7842,7844,7846,7848,7850,7853],{"class":502,"line":503},[500,7840,7841],{"class":677},"var",[500,7843,4776],{"class":513},[500,7845,3783],{"class":677},[500,7847,3786],{"class":677},[500,7849,725],{"class":506},[500,7851,7852],{"class":728},"library",[500,7854,3688],{"class":506},[500,7856,7857,7860,7862,7865],{"class":502,"line":98},[500,7858,7859],{"class":703}," this",[500,7861,707],{"class":506},[500,7863,7864],{"class":513},"apply",[500,7866,7867],{"class":506},"(library);\n",[500,7869,7870,7873,7875],{"class":502,"line":249},[500,7871,7872],{"class":506},"}.",[500,7874,7306],{"class":513},[500,7876,7877],{"class":506},"(whatever);\n",[500,7879,7880,7883],{"class":502,"line":583},[500,7881,7882],{"class":677},"delete",[500,7884,7885],{"class":506}," whatever;\n",[2799,7887,7888],{},[18,7889,7890,7891],{},"“The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a\ngiven sequence of arguments preceding any provided when the new function is called.” ",[500,7892,6829],{},[18,7894,7895,7896,7898],{},"If the bind function is not available by your Rhino environment, you may want to look at the implementation from\nPrototype ",[500,7897,6846],{}," et al. and add it manually with the same procedure.",[18,7900,7901],{},"We delete the ‘whatever’ object from the script context afterwards.",[18,7903,7904],{},"You need the following Java code as glue:",[492,7906,7908],{"className":867,"code":7907,"language":869,"meta":34,"style":34},"\n// internal context for variables\nfinal Bindings bindings = scripting.newBindings ();\n bindings.put (Importer, new Function\u003CString, Void> () {\n // load a library 'argument' from the 'lib' directory\n @Override\n public Void apply (String argument) {\n // trusting returns either a valid stream object or throws an 'untrusted code' exception\n String resource;\n resource = String.format (\"/lib/%s.js\", argument);\n scripting.evaluate (trusting (resource), context);\n return null;\n }\n});\ncontext.setBindings (bindings, ScriptContext.ENGINE_SCOPE);\n// add require function to the scope before the application script is loaded\nscripting.evaluate (requirejs (Importer), context);\n// execute the script ultimately\nscripting.evaluate (applicationjs (), context);\n\n",[405,7909,7910,7914,7919,7924,7929,7934,7939,7944,7949,7954,7959,7964,7969,7973,7977,7982,7987,7992,7997],{"__ignoreMap":34},[500,7911,7912],{"class":502,"line":503},[500,7913,644],{"emptyLinePlaceholder":107},[500,7915,7916],{"class":502,"line":98},[500,7917,7918],{},"// internal context for variables\n",[500,7920,7921],{"class":502,"line":249},[500,7922,7923],{},"final Bindings bindings = scripting.newBindings ();\n",[500,7925,7926],{"class":502,"line":583},[500,7927,7928],{}," bindings.put (Importer, new Function\u003CString, Void> () {\n",[500,7930,7931],{"class":502,"line":615},[500,7932,7933],{}," // load a library 'argument' from the 'lib' directory\n",[500,7935,7936],{"class":502,"line":621},[500,7937,7938],{}," @Override\n",[500,7940,7941],{"class":502,"line":631},[500,7942,7943],{}," public Void apply (String argument) {\n",[500,7945,7946],{"class":502,"line":641},[500,7947,7948],{}," // trusting returns either a valid stream object or throws an 'untrusted code' exception\n",[500,7950,7951],{"class":502,"line":647},[500,7952,7953],{}," String resource;\n",[500,7955,7956],{"class":502,"line":805},[500,7957,7958],{}," resource = String.format (\"/lib/%s.js\", argument);\n",[500,7960,7961],{"class":502,"line":810},[500,7962,7963],{}," scripting.evaluate (trusting (resource), context);\n",[500,7965,7966],{"class":502,"line":826},[500,7967,7968],{}," return null;\n",[500,7970,7971],{"class":502,"line":838},[500,7972,940],{},[500,7974,7975],{"class":502,"line":937},[500,7976,841],{},[500,7978,7979],{"class":502,"line":943},[500,7980,7981],{},"context.setBindings (bindings, ScriptContext.ENGINE_SCOPE);\n",[500,7983,7984],{"class":502,"line":948},[500,7985,7986],{},"// add require function to the scope before the application script is loaded\n",[500,7988,7989],{"class":502,"line":954},[500,7990,7991],{},"scripting.evaluate (requirejs (Importer), context);\n",[500,7993,7994],{"class":502,"line":1898},[500,7995,7996],{},"// execute the script ultimately\n",[500,7998,7999],{"class":502,"line":1904},[500,8000,8001],{},"scripting.evaluate (applicationjs (), context);\n",[18,8003,5221],{},[133,8005,8007],{"id":8006},"reporting","Reporting",[18,8009,8010,8011,8013,8014,8016],{},"In the last example I want to explain how to integrate a script engine, which is not shipped by default – JRuby ",[500,8012,6952],{},".\nThe idea behind is to embed code of ruby gems into your application, especially PDFKit ",[500,8015,6964],{}," for this example. PDFKit\ndescribes itself with",[2799,8018,8019],{},[18,8020,8021],{},"“Create PDFs using plain old HTML+CSS. Uses wkhtmltopdf on the back-end which renders HTML using Webkit.”",[18,8023,8024,8025,8028,8029,8032,8033,707],{},"Mostly you don’t want to handle HTML content directly, as your data is often stored in form of a ‘model’. Our solution\nshould therefore target the transformation from: ",[405,8026,8027],{},"Model -> HTML -> PDF",", which can be achieved using e.g. the nice Jade\n",[500,8030,8031],{},"11"," language for the rendering process, especially Jade4j ",[500,8034,8035],{},"12",[18,8037,8038],{},"Instead of writing the integration code for wkhtmltopdf, we will base on the work of PDFKit and write some JRuby glue.",[18,8040,8041,8042,8045,8046,8049],{},"If you need some information about ‘gem bundling’ I would recommend the articles/examples from Sieger ",[500,8043,8044],{},"13"," and Harada\n",[500,8047,8048],{},"14"," as a starting point.",[18,8051,8052,8053,8056],{},"You will find a local file-based repository in the project, as I wanted Maven ",[500,8054,8055],{},"15"," to handle all the dependencies, but\nany other repository might work fine. It simply depends on your infrastructure and what suits you best.",[18,8058,7792],{},[1622,8060,8061,8064,8067,8073,8076,8079],{},[346,8062,8063],{},"put jruby-complete on your classpath, as the library ships the jsr223 implementation",[346,8065,8066],{},"put the converted pdfkit bundle on your classpath",[346,8068,8069,8070],{},"put any other needed library on your classpath ",[500,8071,8072],{},"jade4j, guava, …",[346,8074,8075],{},"write some jruby code to instantiate a configured pdfkit object",[346,8077,8078],{},"proxy the returned jruby object from 4. with a java interface",[346,8080,8081],{},"convert a jade (or differently) generated html stream to pdf using the proxy from 5.",[18,8083,8084,8085,8087],{},"I’ll show you the glue for the proxy only. Please download the project under ",[500,8086,6762],{}," if you want to see the remaining\nparts.",[492,8089,8091],{"className":867,"code":8090,"language":869,"meta":34,"style":34},"\npublic interface Pdfy {\n public boolean convert (InputStream streamin, OutputStream streamout);\n public boolean convert (InputStream streamin, OutputStream streamout, Map\u003CString, String> options);\n}\n\n",[405,8092,8093,8097,8102,8107,8112],{"__ignoreMap":34},[500,8094,8095],{"class":502,"line":503},[500,8096,644],{"emptyLinePlaceholder":107},[500,8098,8099],{"class":502,"line":98},[500,8100,8101],{},"public interface Pdfy {\n",[500,8103,8104],{"class":502,"line":249},[500,8105,8106],{}," public boolean convert (InputStream streamin, OutputStream streamout);\n",[500,8108,8109],{"class":502,"line":583},[500,8110,8111],{}," public boolean convert (InputStream streamin, OutputStream streamout, Map\u003CString, String> options);\n",[500,8113,8114],{"class":502,"line":615},[500,8115,802],{},[492,8117,8121],{"className":8118,"code":8119,"language":8120,"meta":34,"style":34},"language-ruby shiki shiki-themes github-light github-dark","\nclass Pdfy\n def initialize(stylesheet)\n @stylesheet = stylesheet\n end\n def convert(streamin, streamout, options = {})\n begin\n html = streamin.to_io.read\n kit = PDFKit.new(html, options)\n if @stylesheet\n kit.stylesheets \u003C\u003C @stylesheet\n end\n out = streamout.to_io\n out.binmode \u003C\u003C kit.to_pdf\n out.flush\n rescue\n return false\n end\n true\n end\nend\n\n","ruby",[405,8122,8123,8127,8132,8137,8142,8147,8152,8157,8162,8167,8172,8177,8182,8187,8192,8197,8202,8207,8212,8217,8221],{"__ignoreMap":34},[500,8124,8125],{"class":502,"line":503},[500,8126,644],{"emptyLinePlaceholder":107},[500,8128,8129],{"class":502,"line":98},[500,8130,8131],{},"class Pdfy\n",[500,8133,8134],{"class":502,"line":249},[500,8135,8136],{}," def initialize(stylesheet)\n",[500,8138,8139],{"class":502,"line":583},[500,8140,8141],{}," @stylesheet = stylesheet\n",[500,8143,8144],{"class":502,"line":615},[500,8145,8146],{}," end\n",[500,8148,8149],{"class":502,"line":621},[500,8150,8151],{}," def convert(streamin, streamout, options = {})\n",[500,8153,8154],{"class":502,"line":631},[500,8155,8156],{}," begin\n",[500,8158,8159],{"class":502,"line":641},[500,8160,8161],{}," html = streamin.to_io.read\n",[500,8163,8164],{"class":502,"line":647},[500,8165,8166],{}," kit = PDFKit.new(html, options)\n",[500,8168,8169],{"class":502,"line":805},[500,8170,8171],{}," if @stylesheet\n",[500,8173,8174],{"class":502,"line":810},[500,8175,8176],{}," kit.stylesheets \u003C\u003C @stylesheet\n",[500,8178,8179],{"class":502,"line":826},[500,8180,8181],{}," end\n",[500,8183,8184],{"class":502,"line":838},[500,8185,8186],{}," out = streamout.to_io\n",[500,8188,8189],{"class":502,"line":937},[500,8190,8191],{}," out.binmode \u003C\u003C kit.to_pdf\n",[500,8193,8194],{"class":502,"line":943},[500,8195,8196],{}," out.flush\n",[500,8198,8199],{"class":502,"line":948},[500,8200,8201],{}," rescue\n",[500,8203,8204],{"class":502,"line":954},[500,8205,8206],{}," return false\n",[500,8208,8209],{"class":502,"line":1898},[500,8210,8211],{}," end\n",[500,8213,8214],{"class":502,"line":1904},[500,8215,8216],{}," true\n",[500,8218,8219],{"class":502,"line":1910},[500,8220,8146],{},[500,8222,8223],{"class":502,"line":1916},[500,8224,8225],{},"end\n",[18,8227,8228],{},"Maven will produce an assembly as zip file, which can be extracted elsewhere with a shell script for windows and *nix\nbased systems.",[18,8230,8231],{},"You need to provide the full qualified path for wkhtmltopdf as first argument and the full qualified path of the output\nfile with file extension as second argument.",[18,8233,8234],{},"I did not implement any special CLI handling for this prototype.",[18,8236,8237,8238,8241],{},"You need to install wkhtmltopdf ",[500,8239,8240],{},"16"," as a consequence. I installed wkhtmltopdf 0.11.0 rc2 on windows 7 x64 and\nwkhtmltopdf 0.9.9 on ubuntu 13.10 x64 (virtualization).",[18,8243,8244],{},"Even if writing some boilerplate is not that interesting, writing less boilerplate is! So instead of writing your own\nwheel, you might want to spend your energy on making another wheel feel more rounded.",[18,8246,8247],{},"Whether a script engine can be used in your production environment depends on your configuration, of course, but writing\nsome glue to reuse another solutions might be worth thinking about it.",[18,8249,8250],{},"The effort could be less in comparison to a full rewrite. Stick to a separation of interfaces and implementations and\nlet the environment decide.",[18,8252,8253],{},"The devs. from Rhino/Nashorn/JRuby did quite a good job! As well as the devs. from the mentioned libraries. You should\ncompile the project with Java 1.6(!), 1.7 and 1.8 and look at the results.",[18,8255,8256,8259,8263,8266,8270,8273,8277,8280,8284,8287,8291,8294,8298,8301,8305,8308,8312,8315,8319,8321,8325,8327,8331,8333,8337,8339,8343,8345,8349,8351,8355,8357],{},[500,8257,8258],{}," 1",[22,8260,8261],{"href":8261,"rel":8262},"http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform",[26],[500,8264,8265],{}," 2",[22,8267,8268],{"href":8268,"rel":8269},"http://stackoverflow.com/questions/11838369/where-can-i-find-a-list-of-available-jsr-223-scripting-languages",[26],[500,8271,8272],{}," 3",[22,8274,8275],{"href":8275,"rel":8276},"https://media.synyx.de/uploads//2014/01/synyx.sample.zip",[26],[500,8278,8279],{}," 4",[22,8281,8282],{"href":8282,"rel":8283},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace",[26],[500,8285,8286],{}," 5",[22,8288,8289],{"href":8289,"rel":8290},"http://requirejs.org/",[26],[500,8292,8293],{}," 6",[22,8295,8296],{"href":8296,"rel":8297},"http://de.wikipedia.org/wiki/Public-Key-Infrastruktur",[26],[500,8299,8300],{}," 7",[22,8302,8303],{"href":8303,"rel":8304},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind",[26],[500,8306,8307],{}," 8",[22,8309,8310],{"href":8310,"rel":8311},"http://prototypejs.org/doc/latest/language/Function/prototype/bind/",[26],[500,8313,8314],{}," 9",[22,8316,8317],{"href":8317,"rel":8318},"http://www.jruby.org/",[26],[500,8320,6964],{},[22,8322,8323],{"href":8323,"rel":8324},"https://github.com/pdfkit/pdfkit",[26],[500,8326,8031],{},[22,8328,8329],{"href":8329,"rel":8330},"http://jade-lang.com/",[26],[500,8332,8035],{},[22,8334,8335],{"href":8335,"rel":8336},"https://github.com/neuland/jade4j",[26],[500,8338,8044],{},[22,8340,8341],{"href":8341,"rel":8342},"http://blog.nicksieger.com/articles/2009/01/10/jruby-1-1-6-gems-in-a-jar/",[26],[500,8344,8048],{},[22,8346,8347],{"href":8347,"rel":8348},"http://yokolet.blogspot.de/2010/10/gems-in-jar-with-redbridge.html",[26],[500,8350,8055],{},[22,8352,8353],{"href":8353,"rel":8354},"http://maven.apache.org/",[26],[500,8356,8240],{},[22,8358,8359],{"href":8359,"rel":8360},"https://code.google.com/p/wkhtmltopdf/",[26],[1017,8362,8363],{},"html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":34,"searchDepth":98,"depth":98,"links":8365},[8366,8367,8368,8369],{"id":7265,"depth":249,"text":7256},{"id":3354,"depth":249,"text":7310},{"id":7696,"depth":249,"text":7697},{"id":8006,"depth":249,"text":8007},[1029],"2014-01-22T10:47:14","https://synyx.de/blog/code-gluse/",{},"/blog/code-gluse",{"title":7256,"description":34},"blog/code-gluse",[869,670,8378,8379,8380,8381,8382,8383],"jruby","jsr-223","mozilla-rhino","oracle-nashorn","pdfkit","wkhtmltopdf","Code gluse Today’s post targets an API, which has been released on Dec. 11, 2006; the javax.scripting package [1] and a lot of good articles that have been written around…","M4C3LMe5gjOAJ7fTRdyMF5pjkdkf_FbgJp5K0AK4qnA",{"id":8387,"title":8388,"author":8389,"body":8390,"category":8635,"date":8636,"description":8637,"extension":104,"link":8638,"meta":8639,"navigation":107,"path":8640,"seo":8641,"slug":8394,"stem":8643,"tags":8644,"teaser":8650,"__hash__":8651},"blog/blog/my-first-time-on-a-conference-with-synyx.md","My first time on a conference with synyx",[2544],{"type":11,"value":8391,"toc":8633},[8392,8395,8402,8409,8412,8415,8430,8444,8459,8491,8532,8547,8550,8565,8584,8587,8590,8607,8610],[14,8393,8388],{"id":8394},"my-first-time-on-a-conference-with-synyx",[18,8396,8397,8398,8401],{},"I am a fresh employee at synyx. I started early in 2013 to work for the company and the",[168,8399,8400],{},"Devoxx 2013"," in Antwerp was my\nfirst conference with my new colleagues. Everything started around august when a colleague asked „Whoooo want to go to\nthe Devoxx in November. Are you in? And you?“, he was so enthusiastic that I thought: “Hm first conference with these\nguys? One week with them in another country? And that on my birthday? Let’s do it!”.",[18,8403,8404,8405,8408],{},"Till November nothing big happens. I searched through the Devoxx site for speakers and talks, but not so much. I wanted\nto be free and just do what I want to do on the Devoxx and be ",[168,8406,8407],{},"agile",", of course. Then, one week before the Devoxx, I\ntalked to my colleague to know when the train leaves, where we are going to sleep, where to get the tickets and so on.\nAt this point I realized that synyx has everything organized and well planed. Good guy synyx!",[18,8410,8411],{},"When we arrived at the main station in Frankfurt the first cuba libre was ready to go. Of course with ice and lime. Yes\nwe had ice. We also shared some with friends we met before. We chatted on the train about talks, speakers, the company,\nfuture projects, our new office and challenges in some of our projects. It was fun to speak about things that bother you\nin this different atmosphere than our office or meeting rooms. It was very relaxing and fun.",[18,8413,8414],{},"The next day was the first day on the Devoxx. We started early to get our access keys and traveled there via subway. Its\na nice subway with a ‘nice’ smell, something like mushrooms.",[18,8416,8417,8418,5670,8423,1368,8426,8429],{},"One of the first talks I listened to was ",[22,8419,8422],{"href":8420,"rel":8421},"http://fhornain.wordpress.com/2013/11/12/devoxx13-java-ee-7-whats-new-in-the-java-ee-platform/http://",[26],"**Java EE 7: What’s New in the Java EE Platform\n**",[168,8424,8425],{},"Argun\nGupta",[168,8427,8428],{},"Antonio Goncalves"," talked about new features of Java EE7. It was very interesting and a clear presentation\nwith good speakers. They talked about 3 hours, so it was good that they also did a lot of jokes on the stage especially\nwhen questions arrived like “why do you do not use spring? It has every feature you represent since two years.” Good\nSpeakers, good presentation. I learned a lot about Java EE in general.",[18,8431,8432,8435,8436,8443],{},[168,8433,8434],{},"David Gageot"," showed us in a quick 30 minutes live coding session called ",[22,8437,8440],{"href":8438,"rel":8439},"http://www.devoxx.be/dv13-david-gageot.html?presId=3108",[26],[469,8441,8442],{},"From Legacy to Cloud Under an Hour – Live\nCoding"," how easy it is to take old nasty unreadable code with\nabout a million of if/else statements and other dirty stuff in it and a good IDEA to get a nice clean code without\nduplicates and all the if/else and put it on the cloud at the end. Very impressive to see that in 30 minutes. He must\nhave trained a lot! Notice to me: Take time for your IDEA and learn what you can do with it, even if you think you know\na lot.",[18,8445,8446,8447,8450,8451,8458],{},"At the second day I was excited about the talk from ",[168,8448,8449],{},"José Paumard"," called ",[22,8452,8455],{"href":8453,"rel":8454},"http://www.devoxx.be/dv13-jos-paumard.html?presId=3540",[26],[469,8456,8457],{},"Autumn Collection: from iterable to\nlambdas, streams and collectors"," because this stuff you are\nusing and will use all the time when programming. It gave a real clear view on lambdas, streams and what you can do with\nit. The syntax looked quite nice, if you take care of it. Do not start to use it for everything and make things more\ncomplicated than they already are. Let’s see how it will be in future projects with Java 8. It feels like parallel\nprogramming will be made available and easier for a wider mass of developers.",[18,8460,8461,8462,8469,8470,8473,8474,719,8479,8484,8485,8490],{},"The next talk I watched was ",[22,8463,8466],{"href":8464,"rel":8465},"http://www.devoxx.be/dv13-matt-raible.html?presId=3648",[26],[469,8467,8468],{},"The Modern Java Web Developer",".\n",[168,8471,8472],{},"Matt Raible"," is a very good speaker with a lot of self irony and sarcasm. Which makes the talk even more interesting.\nHe talked about the modern solutions of web development\nlike ",[32,8475],{"alt":8476,"src":8477,"title":8478},"\"20131112_133433\"","http://angularjs.org/","Angular JS",[22,8480,8483],{"href":8481,"rel":8482},"http://jquery.com/",[26],"jQuery",",\nCSS3, ",[22,8486,8489],{"href":8487,"rel":8488},"http://www.html5rocks.com/en/",[26],"HTML5"," and so on. It was a great overview over the preferred technologies for web\ndevelopers. At the end of his talk the showed us a video of him coding a complete dashboard application at one\nafternoon. Of course with a lot of copy paste. But it was nice to see the problems he got stuck, how he fixed it and how\npowerful and easy to use modern web frameworks and libraries are.",[18,8492,8493,8494,8497,8498,8505,8506,8509,8510,8513,8514,180,8517,1368,8522,8527,8528,8531],{},"When I wrote this blog entry about the third day I could not remember what talks were really good at that day, besides\nthe talks of ",[168,8495,8496],{},"Martijn Verburg",". The problem was not that they were bad, but maybe not so forceful. On the other hand I\nwatched ",[22,8499,8502],{"href":8500,"rel":8501},"http://www.devoxx.be/dv13-chet-haase.html?presId=3193",[26],[469,8503,8504],{},"Patterns Shmatterns"," by ",[168,8507,8508],{},"Chet Haase",". And maybe that\nmakes me forget all the other talks. It was a ",[168,8511,8512],{},"fun quickie"," about patterns and their ‘pendant’. Great show, great funny\nface at the front. Definitely one talk to watch between other real talks to relax. The talks from ",[168,8515,8516],{},"Martijn Verburg:",[22,8518,8521],{"href":8519,"rel":8520},"http://www.devoxx.be/dv13-martijn-verburg.html?presId=3214",[26],"*\n*The Habits of Highly Effective Technical Teams**",[22,8523,8526],{"href":8524,"rel":8525},"http://www.devoxx.be/dv13-martijn-verburg.html?presId=3223",[26],"*\n*The Bleeding Edge**"," were both very good. The first named\ntalk was one of my highlights on the Devoxx, because it forced me to think about companies and how they treat their\nemployees and how they should treat them. For example why companies do not gave their employees rights to the productive\nsystem for the project they work on ",[168,8529,8530],{},"and so on and so forth",". A lot of questions where thrown into the room which I\nnever really asked myself and their was one moment where I realized that synyx is making a good job. Definitely a talk\nemployees and employer should watch and think about their process.",[18,8533,8534,8538,8539,8542,8543,8546],{},[32,8535],{"alt":8536,"src":8537},"\"20131111_231326\"","https://media.synyx.de/uploads//2013/12/20131111_231326.jpg","\nThis was not only a effective day at the Devoxx. it was also my birthday. So we went for lunch this evening with\neveryone and then went into a student bar somewhere in Antwerp. It was really funny to go into a small student bar with\nround about 13 people on an evening where they had a 90’s party. Of course we knew all the great 90’s songs like Barbie\nGirl (O",[168,8540,8541],{},"o). But we were not prepared for it and the girls with their shards, yeah shards. Sometimes it is funny if you\nhave a close look at it. After that ‘crazy’ party we moved on to the _Beer Central"," to taste all their 300+ beer.\nLet’s say we ",[168,8544,8545],{},"almost"," did it. Great but different birthday than the years before. Thanks synyx!",[18,8548,8549],{},"At the last two days of the conference I watched a lot of talks, just one after another and running from room to room.\nTwo of them I do remind very well, because they where my points of lights these days.",[18,8551,8552,8553,8556,8557,8564],{},"First of all another highlight of my Devoxx week was the talk from ",[168,8554,8555],{},"Joey Cieplinski"," with the title ",[22,8558,8561],{"href":8559,"rel":8560},"http://www.devoxx.be/dv13-joe-cieplinski.html?presId=3330",[26],[469,8562,8563],{},"Business\nStrategies for Small Independent Developers"," he talked\nabout premium and none premium applications for mobile devices and his view to make higher price applications. Then to\nconcentrate on the 10% of customers that care about your product and do not see it as a throwaway piece of software to\nbuy for less then 1€. This talk confirmed me in my opinion to create quality software and get what you earn for a good\nproduct and concentrate of the concerns of real customers and their problems. Inspiring talk, thank you for sharing it.",[18,8566,8567,8574,8575,8578,8579,8583],{},[22,8568,8571],{"href":8569,"rel":8570},"http://www.devoxx.be/dv13-alain-regnier.html?presId=3733",[26],[469,8572,8573],{},"Introduction to Google Glass"," from ",[168,8576,8577],{},"Alain Regnier","was my\nlast talk I visited. It was a demonstration about what can be done with the glass and what could be done with it in the\nfuture. Alain also explained a lot on how Google act by spreading the glass and what you have to do to get the\nglass. ",[32,8580],{"alt":8581,"src":8582},"\"20131115_105017\"","https://media.synyx.de/uploads//2013/12/20131115_105017.jpg","\nHe explained how to program with the Mirror API and his hopes of getting the GDK (Glass Development Kit) soon. Google\nGlass seems to be a product which can change parts of our lives, but for this it needs a real case in which it will be\nnecessary to have one. I am excited to see what will be possible in the future.",[18,8585,8586],{},"And at the end a small summary.",[18,8588,8589],{},"Antwerp:",[343,8591,8592,8595,8598,8601,8604],{},[346,8593,8594],{},"The subway smells like mushrooms.",[346,8596,8597],{},"The subway is small and carries a lot of people.",[346,8599,8600],{},"Awesome and horrible beer at once.",[346,8602,8603],{},"Beautiful main train station.",[346,8605,8606],{},"They love mayonnaise.",[18,8608,8609],{},"Devoxx:",[343,8611,8612,8615,8618,8621,8624,8627,8630],{},[346,8613,8614],{},"Great speakers with very interesting talks.",[346,8616,8617],{},"Nice People.",[346,8619,8620],{},"Learned a lot of new stuff.",[346,8622,8623],{},"Raspberry Pis will rule the world.",[346,8625,8626],{},"Crab sandwiches are delicious. Yeah! Ate a lot of them.",[346,8628,8629],{},"Tuna salad with only tuna are not that fun.",[346,8631,8632],{},"Belgian fries are awesome.",{"title":34,"searchDepth":98,"depth":98,"links":8634},[],[256],"2013-12-06T08:59:44","I am a fresh employee at synyx. I started early in 2013 to work for the company and theDevoxx 2013 in Antwerp was my\\nfirst conference with my new colleagues. Everything started around august when a colleague asked „Whoooo want to go to\\nthe Devoxx in November. Are you in? And you?“, he was so enthusiastic that I thought: “Hm first conference with these\\nguys? One week with them in another country? And that on my birthday? Let’s do it!”.","https://synyx.de/blog/my-first-time-on-a-conference-with-synyx/",{},"/blog/my-first-time-on-a-conference-with-synyx",{"title":8388,"description":8642},"I am a fresh employee at synyx. I started early in 2013 to work for the company and theDevoxx 2013 in Antwerp was my\nfirst conference with my new colleagues. Everything started around august when a colleague asked „Whoooo want to go to\nthe Devoxx in November. Are you in? And you?“, he was so enthusiastic that I thought: “Hm first conference with these\nguys? One week with them in another country? And that on my birthday? Let’s do it!”.","blog/my-first-time-on-a-conference-with-synyx",[113,6379,8645,8646,8647,268,8648,8649],"gogle-glass","java-8","java-ee7","modern-web-development","premium-apps","I am a fresh employee at synyx. I started early in 2013 to work for the company and the Devoxx 2013 in Antwerp was my first conference with my new colleagues.…","o_aFlRO4Z2neys_7nDR-63csn2MLWAYQgg8F4ktsHls",{"id":8653,"title":8654,"author":8655,"body":8656,"category":9104,"date":9105,"description":9106,"extension":104,"link":9107,"meta":9108,"navigation":107,"path":9109,"seo":9110,"slug":8660,"stem":9111,"tags":9112,"teaser":9118,"__hash__":9119},"blog/blog/yammer-metrics-made-easy-part-i.md","yammer – Metrics made easy – Part I",[6392],{"type":11,"value":8657,"toc":9102},[8658,8661,8664,8667,8701,8704,8718,8721,8740,8743,8746,8749,8752,8769,8772,8779,8782,8785,8810,8813,8816,8819,8822,8825,8828,8872,8875,9085,9088,9094,9097,9100],[14,8659,8654],{"id":8660},"yammer-metrics-made-easy-part-i",[18,8662,8663],{},"Metrics by yammer provides runtime metrics and statistics for all kind of apps you can imagine. A lot of stuff is\ndirectly useable out of the box, for example measuring request/response cycles of webapps and provide histograms of the\nmeasured values. So, lets try enabling a simple Java-Application built by maven.",[18,8665,8666],{},"First we add needed dependencies into our pom:",[492,8668,8670],{"className":1751,"code":8669,"language":1753,"meta":34,"style":34},"\n \u003Cdependency>\n \u003Cgroupid>com.yammer.metrics\u003C/groupid>\n \u003Cartifactid>metrics-core\u003C/artifactid>\n \u003Cversion>3.0.0-BETA1\u003C/version>\n \u003C/dependency>\n\n",[405,8671,8672,8676,8681,8686,8691,8696],{"__ignoreMap":34},[500,8673,8674],{"class":502,"line":503},[500,8675,644],{"emptyLinePlaceholder":107},[500,8677,8678],{"class":502,"line":98},[500,8679,8680],{}," \u003Cdependency>\n",[500,8682,8683],{"class":502,"line":249},[500,8684,8685],{}," \u003Cgroupid>com.yammer.metrics\u003C/groupid>\n",[500,8687,8688],{"class":502,"line":583},[500,8689,8690],{}," \u003Cartifactid>metrics-core\u003C/artifactid>\n",[500,8692,8693],{"class":502,"line":615},[500,8694,8695],{}," \u003Cversion>3.0.0-BETA1\u003C/version>\n",[500,8697,8698],{"class":502,"line":621},[500,8699,8700],{}," \u003C/dependency>\n",[18,8702,8703],{},"After providing this, we are able to do something like that in our code:",[492,8705,8707],{"className":867,"code":8706,"language":869,"meta":34,"style":34},"\nstatic final MetricRegistry metrics = new MetricRegistry(\"Demonstration\");\n\n",[405,8708,8709,8713],{"__ignoreMap":34},[500,8710,8711],{"class":502,"line":503},[500,8712,644],{"emptyLinePlaceholder":107},[500,8714,8715],{"class":502,"line":98},[500,8716,8717],{},"static final MetricRegistry metrics = new MetricRegistry(\"Demonstration\");\n",[18,8719,8720],{},"The MetricRegistry is not more and not less than a structural component for a couple of Metrics in you Application.\nLet’s imagine you’ve developed an application for remote number crunching, then it would be a good idea creating 2\nMetricRegistry Instances like this:",[492,8722,8724],{"className":867,"code":8723,"language":869,"meta":34,"style":34},"\nstatic final MetricRegistry crunchMetrics = new MetricRegistry(\"CrunchMeasurement\");\nstatic final MetricRegistry requestMetrics = new MetricRegistry(\"RequestMeasurement\");\n\n",[405,8725,8726,8730,8735],{"__ignoreMap":34},[500,8727,8728],{"class":502,"line":503},[500,8729,644],{"emptyLinePlaceholder":107},[500,8731,8732],{"class":502,"line":98},[500,8733,8734],{},"static final MetricRegistry crunchMetrics = new MetricRegistry(\"CrunchMeasurement\");\n",[500,8736,8737],{"class":502,"line":249},[500,8738,8739],{},"static final MetricRegistry requestMetrics = new MetricRegistry(\"RequestMeasurement\");\n",[18,8741,8742],{},"You would use one of them for all measurement of the crunching component itself and the other for the little server you\nincluded to access your numbercruncher (possibly to measure request/response cycles too).",[18,8744,8745],{},"First step is done. We are now able to add some Metrics to a registry which is needed to expose them. But wait … what we\nshould expose now?",[18,8747,8748],{},"Which possibilities do we have with metrics?",[18,8750,8751],{},"Well, there are 5 types of measurements included",[343,8753,8754,8757,8760,8763,8766],{},[346,8755,8756],{},"Gauge (instantaneous measurement of one value)",[346,8758,8759],{},"Histogram (measurement of value variants)",[346,8761,8762],{},"Timer (measurement of timings)",[346,8764,8765],{},"Counter (measurement of atomic longs)",[346,8767,8768],{},"Meter (measurement of ticks in a time range)",[18,8770,8771],{},"as well as some typically needed ones for special purposes like the",[18,8773,8774],{},[469,8775,8776],{},[168,8777,8778],{},"com.yammer.metrics.servlet.DefaultWebappMetricsFilter (we will discuss this later in the blog)",[18,8780,8781],{},"So for our example we should take two types of measurements: a Timer for measurement of request/response cycles and a\nsecond timer for measurement of the number crunching calculation.",[18,8783,8784],{},"Next step is to expose the measured values to a format you can use it. Metrics provides a lot of default exposements\nlike:",[343,8786,8787,8790,8793,8796,8799,8802,8805,8808],{},[346,8788,8789],{},"JMX",[346,8791,8792],{},"JSON",[346,8794,8795],{},"CSV",[346,8797,8798],{},"log4j / slf4j",[346,8800,8801],{},"logback",[346,8803,8804],{},"ganglia",[346,8806,8807],{},"graphite",[346,8809,5619],{},[18,8811,8812],{},"Of course you are able to create your own Reporters ’cause its open source software 🙂 For our example it’s enough using\none of the bundled reporters, e.G. the ConsoleReporter.",[18,8814,8815],{},"So, at a glance we need to do the following steps to enable a Java-Application with Metrics:",[18,8817,8818],{},"1.) Create and instantiate a MetricRegistry (i highly encourage you to inject them into your productive code!)",[18,8820,8821],{},"2.) Create Measurements to your needs (in our example the mentioned 2 timers)",[18,8823,8824],{},"3.) Create and instantiate a Reporter to your needs ( i highly encourage you to inject them into your productive code\n🙂",[18,8826,8827],{},"Let’s show this with a very straightforward coded application",[492,8829,8831],{"className":867,"code":8830,"language":869,"meta":34,"style":34},"\n final MetricRegistry metrics = new MetricRegistry(\"Demonstration\");\n evictions = metrics.counter(MetricRegistry.name(HealthCheckDemo.class, \"cache-evictions\"));\n request = metrics.timer(MetricRegistry.name(ArithmeticDemoOperation.class, \"calculation-duration\"));\n reporter = ConsoleReporter.forRegistry(metrics).build();\n jmxReporter = JmxReporter.forRegistry(metrics).build();\n reporter.start(1, TimeUnit.MINUTES); // should expose values every minute\n jmxReporter.start();\n\n",[405,8832,8833,8837,8842,8847,8852,8857,8862,8867],{"__ignoreMap":34},[500,8834,8835],{"class":502,"line":503},[500,8836,644],{"emptyLinePlaceholder":107},[500,8838,8839],{"class":502,"line":98},[500,8840,8841],{}," final MetricRegistry metrics = new MetricRegistry(\"Demonstration\");\n",[500,8843,8844],{"class":502,"line":249},[500,8845,8846],{}," evictions = metrics.counter(MetricRegistry.name(HealthCheckDemo.class, \"cache-evictions\"));\n",[500,8848,8849],{"class":502,"line":583},[500,8850,8851],{}," request = metrics.timer(MetricRegistry.name(ArithmeticDemoOperation.class, \"calculation-duration\"));\n",[500,8853,8854],{"class":502,"line":615},[500,8855,8856],{}," reporter = ConsoleReporter.forRegistry(metrics).build();\n",[500,8858,8859],{"class":502,"line":621},[500,8860,8861],{}," jmxReporter = JmxReporter.forRegistry(metrics).build();\n",[500,8863,8864],{"class":502,"line":631},[500,8865,8866],{}," reporter.start(1, TimeUnit.MINUTES); // should expose values every minute\n",[500,8868,8869],{"class":502,"line":641},[500,8870,8871],{}," jmxReporter.start();\n",[18,8873,8874],{},"After running this application you should see a console output like this:",[492,8876,8878],{"className":3047,"code":8877,"language":3049,"meta":34,"style":34},"\n05.05.13 08:22:03 ==============================================================\n-- Counters --------------------------------------------------------------------\norg.synyx.demos.HealthCheckDemo.cache-evictions\ncount = 1\n-- Timers ----------------------------------------------------------------------\norg.synyx.demos.ArithmeticDemoOperation.calculation-duration\ncount = 1\nmean rate = 0,02 calls/second\n1-minute rate = 0,09 calls/second\n5-minute rate = 0,17 calls/second\n15-minute rate = 0,19 calls/second\nmin = 1250,28 milliseconds\nmax = 1250,28 milliseconds\nmean = 1250,28 milliseconds\nstddev = 0,00 milliseconds\nmedian = 1250,28 milliseconds\n75% \u003C= 1250,28 milliseconds\n95% \u003C= 1250,28 milliseconds\n98% \u003C= 1250,28 milliseconds\n99% \u003C= 1250,28 milliseconds\n99.9% \u003C= 1250,28 milliseconds\n05.05.13 08:23:03 ==============================================================\n-- Counters --------------------------------------------------------------------\norg.synyx.demos.HealthCheckDemo.cache-evictions\ncount = 1\n-- Timers ----------------------------------------------------------------------\norg.synyx.demos.ArithmeticDemoOperation.calculation-duration\ncount = 1\nmean rate = 0,01 calls/second\n1-minute rate = 0,03 calls/second\n5-minute rate = 0,14 calls/second\n15-minute rate = 0,18 calls/second\nmin = 1250,28 milliseconds\nmax = 1250,28 milliseconds\nmean = 1250,28 milliseconds\nstddev = 0,00 milliseconds\nmedian = 1250,28 milliseconds\n75% \u003C= 1250,28 milliseconds\n95% \u003C= 1250,28 milliseconds\n98% \u003C= 1250,28 milliseconds\n99% \u003C= 1250,28 milliseconds\n99.9% \u003C= 1250,28 milliseconds\n\n",[405,8879,8880,8884,8889,8894,8899,8904,8909,8914,8918,8923,8928,8933,8938,8943,8948,8953,8958,8963,8968,8973,8978,8983,8988,8993,8997,9001,9005,9009,9013,9017,9022,9027,9032,9037,9041,9045,9050,9055,9060,9065,9070,9075,9080],{"__ignoreMap":34},[500,8881,8882],{"class":502,"line":503},[500,8883,644],{"emptyLinePlaceholder":107},[500,8885,8886],{"class":502,"line":98},[500,8887,8888],{},"05.05.13 08:22:03 ==============================================================\n",[500,8890,8891],{"class":502,"line":249},[500,8892,8893],{},"-- Counters --------------------------------------------------------------------\n",[500,8895,8896],{"class":502,"line":583},[500,8897,8898],{},"org.synyx.demos.HealthCheckDemo.cache-evictions\n",[500,8900,8901],{"class":502,"line":615},[500,8902,8903],{},"count = 1\n",[500,8905,8906],{"class":502,"line":621},[500,8907,8908],{},"-- Timers ----------------------------------------------------------------------\n",[500,8910,8911],{"class":502,"line":631},[500,8912,8913],{},"org.synyx.demos.ArithmeticDemoOperation.calculation-duration\n",[500,8915,8916],{"class":502,"line":641},[500,8917,8903],{},[500,8919,8920],{"class":502,"line":647},[500,8921,8922],{},"mean rate = 0,02 calls/second\n",[500,8924,8925],{"class":502,"line":805},[500,8926,8927],{},"1-minute rate = 0,09 calls/second\n",[500,8929,8930],{"class":502,"line":810},[500,8931,8932],{},"5-minute rate = 0,17 calls/second\n",[500,8934,8935],{"class":502,"line":826},[500,8936,8937],{},"15-minute rate = 0,19 calls/second\n",[500,8939,8940],{"class":502,"line":838},[500,8941,8942],{},"min = 1250,28 milliseconds\n",[500,8944,8945],{"class":502,"line":937},[500,8946,8947],{},"max = 1250,28 milliseconds\n",[500,8949,8950],{"class":502,"line":943},[500,8951,8952],{},"mean = 1250,28 milliseconds\n",[500,8954,8955],{"class":502,"line":948},[500,8956,8957],{},"stddev = 0,00 milliseconds\n",[500,8959,8960],{"class":502,"line":954},[500,8961,8962],{},"median = 1250,28 milliseconds\n",[500,8964,8965],{"class":502,"line":1898},[500,8966,8967],{},"75% \u003C= 1250,28 milliseconds\n",[500,8969,8970],{"class":502,"line":1904},[500,8971,8972],{},"95% \u003C= 1250,28 milliseconds\n",[500,8974,8975],{"class":502,"line":1910},[500,8976,8977],{},"98% \u003C= 1250,28 milliseconds\n",[500,8979,8980],{"class":502,"line":1916},[500,8981,8982],{},"99% \u003C= 1250,28 milliseconds\n",[500,8984,8985],{"class":502,"line":1922},[500,8986,8987],{},"99.9% \u003C= 1250,28 milliseconds\n",[500,8989,8990],{"class":502,"line":1928},[500,8991,8992],{},"05.05.13 08:23:03 ==============================================================\n",[500,8994,8995],{"class":502,"line":1934},[500,8996,8893],{},[500,8998,8999],{"class":502,"line":1940},[500,9000,8898],{},[500,9002,9003],{"class":502,"line":1946},[500,9004,8903],{},[500,9006,9007],{"class":502,"line":1952},[500,9008,8908],{},[500,9010,9011],{"class":502,"line":1958},[500,9012,8913],{},[500,9014,9015],{"class":502,"line":1964},[500,9016,8903],{},[500,9018,9019],{"class":502,"line":1970},[500,9020,9021],{},"mean rate = 0,01 calls/second\n",[500,9023,9024],{"class":502,"line":1976},[500,9025,9026],{},"1-minute rate = 0,03 calls/second\n",[500,9028,9029],{"class":502,"line":1982},[500,9030,9031],{},"5-minute rate = 0,14 calls/second\n",[500,9033,9034],{"class":502,"line":1988},[500,9035,9036],{},"15-minute rate = 0,18 calls/second\n",[500,9038,9039],{"class":502,"line":1994},[500,9040,8942],{},[500,9042,9043],{"class":502,"line":2000},[500,9044,8947],{},[500,9046,9048],{"class":502,"line":9047},36,[500,9049,8952],{},[500,9051,9053],{"class":502,"line":9052},37,[500,9054,8957],{},[500,9056,9058],{"class":502,"line":9057},38,[500,9059,8962],{},[500,9061,9063],{"class":502,"line":9062},39,[500,9064,8967],{},[500,9066,9068],{"class":502,"line":9067},40,[500,9069,8972],{},[500,9071,9073],{"class":502,"line":9072},41,[500,9074,8977],{},[500,9076,9078],{"class":502,"line":9077},42,[500,9079,8982],{},[500,9081,9083],{"class":502,"line":9082},43,[500,9084,8987],{},[18,9086,9087],{},"Furthermore, if you debug the demo application you are able to inspect our exposements via a jmx-client like jVisualVM\nof jConsole after connecting.",[18,9089,9090],{},[32,9091],{"alt":9092,"src":9093},"\"Bildschirmfoto 2013-09-02 um 12.08.02\"","https://media.synyx.de/uploads//2013/06/Bildschirmfoto-2013-09-02-um-12.08.02.png",[18,9095,9096],{},"SUCCESS!! o/ As you can see you are able to expose the same values to different reporters if you want to. Isn’t that\nnice? Yes it is!",[18,9098,9099],{},"Next time we will enable a java-webapplication with some measurements, so stay tuned!",[1017,9101,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":9103},[],[1029,6722],"2013-09-02T11:50:58","Metrics by yammer provides runtime metrics and statistics for all kind of apps you can imagine. A lot of stuff is\\ndirectly useable out of the box, for example measuring request/response cycles of webapps and provide histograms of the\\nmeasured values. So, lets try enabling a simple Java-Application built by maven.","https://synyx.de/blog/yammer-metrics-made-easy-part-i/",{},"/blog/yammer-metrics-made-easy-part-i",{"title":8654,"description":8663},"blog/yammer-metrics-made-easy-part-i",[869,9113,9114,9115,6178,9116,9117],"javaee","jetty","measurement","monitoring","tomcat","Metrics by yammer provides runtime metrics and statistics for all kind of apps you can imagine. A lot of stuff is directly useable out of the box, for example measuring…","3qXtXK-jIyC3OdUZ5imnvlDvTO3UVK8aJvjo9_nNGT8",{"id":9121,"title":9122,"author":9123,"body":9125,"category":10092,"date":10093,"description":10094,"extension":104,"link":10095,"meta":10096,"navigation":107,"path":10097,"seo":10098,"slug":10100,"stem":10101,"tags":10102,"teaser":10105,"__hash__":10106},"blog/blog/asynchronous-concurrency-with-vert-x-part-2.md","Asynchronous concurrency with vert.x – Part 2",[9124],"allmendinger",{"type":11,"value":9126,"toc":10090},[9127,9130,9145,9159,9162,9185,9188,9212,9215,9278,9281,9289,9292,9307,9314,9590,9593,9596,9825,9828,9857,9864,9933,9936,10015,10026,10065,10068,10086,10088],[14,9128,9122],{"id":9129},"asynchronous-concurrency-with-vertx-part-2",[18,9131,9132,9133,9138,9139,9144],{},"CoffeeScript\nVert.x supports JavaScript through the ",[22,9134,9137],{"href":9135,"rel":9136},"https://developer.mozilla.org/en/docs/Rhino",[26],"Rhino JavaScript engine",". Although\nJavaScript is a decent language once you get to know it, I prefer ",[22,9140,9143],{"href":9141,"rel":9142},"http://www.coffeescript.org",[26],"CoffeeScript",", a\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\ncompiled JavaScript file.\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:",[492,9146,9148],{"className":3047,"code":9147,"language":3049,"meta":34,"style":34},"\nfoo = (a, b) -> a + b\n\n",[405,9149,9150,9154],{"__ignoreMap":34},[500,9151,9152],{"class":502,"line":503},[500,9153,644],{"emptyLinePlaceholder":107},[500,9155,9156],{"class":502,"line":98},[500,9157,9158],{},"foo = (a, b) -> a + b\n",[18,9160,9161],{},"Translates to the JavaScript code",[492,9163,9165],{"className":3047,"code":9164,"language":3049,"meta":34,"style":34},"\nvar foo = function (a, b) {\n return a + b; // (the last statement is returned)\n}\n\n",[405,9166,9167,9171,9176,9181],{"__ignoreMap":34},[500,9168,9169],{"class":502,"line":503},[500,9170,644],{"emptyLinePlaceholder":107},[500,9172,9173],{"class":502,"line":98},[500,9174,9175],{},"var foo = function (a, b) {\n",[500,9177,9178],{"class":502,"line":249},[500,9179,9180],{}," return a + b; // (the last statement is returned)\n",[500,9182,9183],{"class":502,"line":583},[500,9184,802],{},[18,9186,9187],{},"Also parentheses around function arguments are optional",[492,9189,9191],{"className":3047,"code":9190,"language":3049,"meta":34,"style":34},"\n foo a, b, c\n # same as\n foo(a, b, c)\n\n",[405,9192,9193,9197,9202,9207],{"__ignoreMap":34},[500,9194,9195],{"class":502,"line":503},[500,9196,644],{"emptyLinePlaceholder":107},[500,9198,9199],{"class":502,"line":98},[500,9200,9201],{}," foo a, b, c\n",[500,9203,9204],{"class":502,"line":249},[500,9205,9206],{}," # same as\n",[500,9208,9209],{"class":502,"line":583},[500,9210,9211],{}," foo(a, b, c)\n",[18,9213,9214],{},"The translated source code from the example described in the last post is",[492,9216,9218],{"className":3047,"code":9217,"language":3049,"meta":34,"style":34},"\nvertx = require 'vertx'\naddress = 'example.address'\nhandler = (message, replier) ->\n stdout.println \"sender sent \" + message\n replier \"pong 1\", (message, replier) ->\n # and so on\nvertx.eventBus.registerHandler address, handler\nvertx.eventBus.send address, \"ping 1\", (message, replier) ->\n stdout.println \"handler sent \" + message\n replier \"ping 2\", (message, replier) ->\n # and so on\n\n",[405,9219,9220,9224,9229,9234,9239,9244,9249,9254,9259,9264,9269,9274],{"__ignoreMap":34},[500,9221,9222],{"class":502,"line":503},[500,9223,644],{"emptyLinePlaceholder":107},[500,9225,9226],{"class":502,"line":98},[500,9227,9228],{},"vertx = require 'vertx'\n",[500,9230,9231],{"class":502,"line":249},[500,9232,9233],{},"address = 'example.address'\n",[500,9235,9236],{"class":502,"line":583},[500,9237,9238],{},"handler = (message, replier) ->\n",[500,9240,9241],{"class":502,"line":615},[500,9242,9243],{}," stdout.println \"sender sent \" + message\n",[500,9245,9246],{"class":502,"line":621},[500,9247,9248],{}," replier \"pong 1\", (message, replier) ->\n",[500,9250,9251],{"class":502,"line":631},[500,9252,9253],{}," # and so on\n",[500,9255,9256],{"class":502,"line":641},[500,9257,9258],{},"vertx.eventBus.registerHandler address, handler\n",[500,9260,9261],{"class":502,"line":647},[500,9262,9263],{},"vertx.eventBus.send address, \"ping 1\", (message, replier) ->\n",[500,9265,9266],{"class":502,"line":805},[500,9267,9268],{}," stdout.println \"handler sent \" + message\n",[500,9270,9271],{"class":502,"line":810},[500,9272,9273],{}," replier \"ping 2\", (message, replier) ->\n",[500,9275,9276],{"class":502,"line":826},[500,9277,9253],{},[18,9279,9280],{},"The shorter function declaration notation is a huge improvement, especially when dealing with the kind of\ncallback-heavy code that is prevalent when dealing with asynchronous concurrency.",[18,9282,9283,9284,1437],{},"The Sleeping Barber Problem\nTo challenge vert.x with something more exciting than ping-pong, I decided to model a basic concurrency problem that\nmirrors some of the challenges that our new application will face – the\nfamous ",[22,9285,9288],{"href":9286,"rel":9287},"http://en.wikipedia.org/wiki/Sleeping_barber_problem",[26],"Sleeping Barber Problem",[18,9290,9291],{},"The analogy is based upon a hypothetical barber shop with one barber. The barber has one barber chair and a waiting room\nwith a number of chairs in it. When the barber finishes cutting a customer’s hair, he dismisses the customer and then\ngoes to the waiting room to see if there are other customers waiting. If there are, he brings one of them back to the\nchair and cuts his hair. If there are no other customers waiting, he returns to his chair and sleeps in it.\nEach customer, when he arrives, looks to see what the barber is doing. If the barber is sleeping, then the customer\nwakes him up and sits in the chair. If the barber is cutting hair, then the customer goes to the waiting room. If there\nis a free chair in the waiting room, the customer sits in it and waits his turn. If there is no free chair, then the\ncustomer leaves. Based on a naïve analysis, the above description should ensure that the shop functions correctly, with\nthe barber cutting the hair of anyone who arrives until there are no more customers, and then sleeping until the next\ncustomer arrives. In practice, there are a number of problems that can occur that are illustrative of general scheduling\nproblems.",[18,9293,9294,9295,9300,9301,9306],{},"I’ve ",[22,9296,9299],{"href":9297,"rel":9298},"https://github.com/OttoAllmendinger/term-paper-stm",[26],"previously solved this problem","\nusing ",[22,9302,9305],{"href":9303,"rel":9304},"http://en.wikipedia.org/wiki/Software_transactional_memory",[26],"Software Transactional Memory"," and was interested how\nthe message-passing style of vert.x compares.",[18,9308,9309,9310,9313],{},"Barber.coffee\nThe barber shop problem nicely separates into two systems: a ",[405,9311,9312],{},"barber"," message handler that keeps track of incoming\ncustomers and manages the queue, and set of callback methods representing the customer, which initiate a communication\nsequence with the message handler. The following code defines the barber message handler.",[492,9315,9317],{"className":3047,"code":9316,"language":3049,"meta":34,"style":34},"\nvertx = require 'vertx'\naddr = 'barber'\nwaitTime = -> Math.random() * 100\nbarber = ->\n # the state of the message handler lives\n # in this closure\n busy = false\n queue = []\n freeSeats = 20\n # make the system a little indeterministic\n log = (message) ->\n stdout.println \"barber: #{message}\"\n # the following methods define the core behavior\n checkQueue = ->\n if queue.length > 0\n serveCustomer queue.shift()\n freeSeats += 1\n return true\n else\n return false\n serveCustomer = ({customer, replier}) ->\n log \"serving #{customer}\"\n busy = true\n replier 'serve', (message, replier) ->\n vertx.setTimer waitTime(), ->\n log \"done serving #{customer}\"\n busy = checkQueue()\n replier 'done'\n # this is the handler's callback method that\n # is being returned by the barber function\n (message, replier) ->\n customer = message\n if busy\n # there is an intermediate state where we know that we\n # have to queue the customer because there aren't any\n # free seats, but the customer must first acknowledge\n # the waiting state before we can actually put him in\n # the queue.\n if freeSeats > 0\n freeSeats -= 1\n log \"sending #{customer} to queue\"\n replier 'busy', (message, replier) ->\n # customer waiting ack\n queue.push {customer, replier}\n log \"queued #{customer} - \" +\n \"length: #{queue.length} - free seats: #{freeSeats}\"\n else\n replier 'full'\n else\n serveCustomer {customer, replier}\nexports.start = ->\n vertx.eventBus.registerHandler addr, barber()\n\n",[405,9318,9319,9323,9327,9332,9337,9342,9347,9352,9357,9362,9367,9372,9377,9382,9387,9392,9397,9402,9407,9412,9417,9421,9426,9431,9436,9441,9446,9451,9456,9461,9466,9471,9476,9481,9486,9491,9496,9501,9506,9511,9516,9521,9526,9531,9537,9543,9549,9555,9561,9567,9572,9578,9584],{"__ignoreMap":34},[500,9320,9321],{"class":502,"line":503},[500,9322,644],{"emptyLinePlaceholder":107},[500,9324,9325],{"class":502,"line":98},[500,9326,9228],{},[500,9328,9329],{"class":502,"line":249},[500,9330,9331],{},"addr = 'barber'\n",[500,9333,9334],{"class":502,"line":583},[500,9335,9336],{},"waitTime = -> Math.random() * 100\n",[500,9338,9339],{"class":502,"line":615},[500,9340,9341],{},"barber = ->\n",[500,9343,9344],{"class":502,"line":621},[500,9345,9346],{}," # the state of the message handler lives\n",[500,9348,9349],{"class":502,"line":631},[500,9350,9351],{}," # in this closure\n",[500,9353,9354],{"class":502,"line":641},[500,9355,9356],{}," busy = false\n",[500,9358,9359],{"class":502,"line":647},[500,9360,9361],{}," queue = []\n",[500,9363,9364],{"class":502,"line":805},[500,9365,9366],{}," freeSeats = 20\n",[500,9368,9369],{"class":502,"line":810},[500,9370,9371],{}," # make the system a little indeterministic\n",[500,9373,9374],{"class":502,"line":826},[500,9375,9376],{}," log = (message) ->\n",[500,9378,9379],{"class":502,"line":838},[500,9380,9381],{}," stdout.println \"barber: #{message}\"\n",[500,9383,9384],{"class":502,"line":937},[500,9385,9386],{}," # the following methods define the core behavior\n",[500,9388,9389],{"class":502,"line":943},[500,9390,9391],{}," checkQueue = ->\n",[500,9393,9394],{"class":502,"line":948},[500,9395,9396],{}," if queue.length > 0\n",[500,9398,9399],{"class":502,"line":954},[500,9400,9401],{}," serveCustomer queue.shift()\n",[500,9403,9404],{"class":502,"line":1898},[500,9405,9406],{}," freeSeats += 1\n",[500,9408,9409],{"class":502,"line":1904},[500,9410,9411],{}," return true\n",[500,9413,9414],{"class":502,"line":1910},[500,9415,9416],{}," else\n",[500,9418,9419],{"class":502,"line":1916},[500,9420,8206],{},[500,9422,9423],{"class":502,"line":1922},[500,9424,9425],{}," serveCustomer = ({customer, replier}) ->\n",[500,9427,9428],{"class":502,"line":1928},[500,9429,9430],{}," log \"serving #{customer}\"\n",[500,9432,9433],{"class":502,"line":1934},[500,9434,9435],{}," busy = true\n",[500,9437,9438],{"class":502,"line":1940},[500,9439,9440],{}," replier 'serve', (message, replier) ->\n",[500,9442,9443],{"class":502,"line":1946},[500,9444,9445],{}," vertx.setTimer waitTime(), ->\n",[500,9447,9448],{"class":502,"line":1952},[500,9449,9450],{}," log \"done serving #{customer}\"\n",[500,9452,9453],{"class":502,"line":1958},[500,9454,9455],{}," busy = checkQueue()\n",[500,9457,9458],{"class":502,"line":1964},[500,9459,9460],{}," replier 'done'\n",[500,9462,9463],{"class":502,"line":1970},[500,9464,9465],{}," # this is the handler's callback method that\n",[500,9467,9468],{"class":502,"line":1976},[500,9469,9470],{}," # is being returned by the barber function\n",[500,9472,9473],{"class":502,"line":1982},[500,9474,9475],{}," (message, replier) ->\n",[500,9477,9478],{"class":502,"line":1988},[500,9479,9480],{}," customer = message\n",[500,9482,9483],{"class":502,"line":1994},[500,9484,9485],{}," if busy\n",[500,9487,9488],{"class":502,"line":2000},[500,9489,9490],{}," # there is an intermediate state where we know that we\n",[500,9492,9493],{"class":502,"line":9047},[500,9494,9495],{}," # have to queue the customer because there aren't any\n",[500,9497,9498],{"class":502,"line":9052},[500,9499,9500],{}," # free seats, but the customer must first acknowledge\n",[500,9502,9503],{"class":502,"line":9057},[500,9504,9505],{}," # the waiting state before we can actually put him in\n",[500,9507,9508],{"class":502,"line":9062},[500,9509,9510],{}," # the queue.\n",[500,9512,9513],{"class":502,"line":9067},[500,9514,9515],{}," if freeSeats > 0\n",[500,9517,9518],{"class":502,"line":9072},[500,9519,9520],{}," freeSeats -= 1\n",[500,9522,9523],{"class":502,"line":9077},[500,9524,9525],{}," log \"sending #{customer} to queue\"\n",[500,9527,9528],{"class":502,"line":9082},[500,9529,9530],{}," replier 'busy', (message, replier) ->\n",[500,9532,9534],{"class":502,"line":9533},44,[500,9535,9536],{}," # customer waiting ack\n",[500,9538,9540],{"class":502,"line":9539},45,[500,9541,9542],{}," queue.push {customer, replier}\n",[500,9544,9546],{"class":502,"line":9545},46,[500,9547,9548],{}," log \"queued #{customer} - \" +\n",[500,9550,9552],{"class":502,"line":9551},47,[500,9553,9554],{}," \"length: #{queue.length} - free seats: #{freeSeats}\"\n",[500,9556,9558],{"class":502,"line":9557},48,[500,9559,9560],{}," else\n",[500,9562,9564],{"class":502,"line":9563},49,[500,9565,9566],{}," replier 'full'\n",[500,9568,9570],{"class":502,"line":9569},50,[500,9571,9416],{},[500,9573,9575],{"class":502,"line":9574},51,[500,9576,9577],{}," serveCustomer {customer, replier}\n",[500,9579,9581],{"class":502,"line":9580},52,[500,9582,9583],{},"exports.start = ->\n",[500,9585,9587],{"class":502,"line":9586},53,[500,9588,9589],{}," vertx.eventBus.registerHandler addr, barber()\n",[18,9591,9592],{},"The state of the barber is encoded by the callback method that will be called for an upcoming event and the values of\nthe variables defined in the closure. By being able to store repliers you can easily trigger remote state changes\natomically, when they should occur.",[18,9594,9595],{},"Customer.coffee\nLet’s define the behavior of the customer in a separate file",[492,9597,9599],{"className":3047,"code":9598,"language":3049,"meta":34,"style":34},"\nvertx = require 'vertx'\naddr = 'barber'\nwaitTime = -> Math.random() * 100\nsendCustomer = (i) ->\n # As with the barber, the customer's state is\n # defined in this closure. The variables will\n # be modified by the callback methods that are\n # triggered by the message handler's replies.\n waiting = false\n beingServed = false\n log = (message) ->\n stdout.println \"customer #{i}: #{message}\"\n # just a shorthand\n send = (message, callback) ->\n vertx.eventBus.send addr, message, callback\n # factor out the exit method:\n # a customer can exit after having been served\n # or when the queue is full\n exit = (message) ->\n log message + \" - exiting\"\n # this method doesn't send a response\n # via the replier\n getHaircut = (message, replier) ->\n waiting = false\n beingServed = true\n log \"being served\"\n replier 'being-served', exit\n log \"enters\"\n send \"customer #{i}\", (message, replier) ->\n switch message\n when 'busy'\n waiting = true\n log 'waiting'\n replier 'waiting', getHaircut\n when 'serve'\n getHaircut message, replier\n when 'full'\n exit message\n# a loop that continuously sends customers\n# to the barber\nsendCustomerLoop = (i) ->\n sendCustomer i\n vertx.setTimer waitTime(), -> sendCustomerLoop i + 1\nexports.start = ->\n sendCustomerLoop 1\n\n",[405,9600,9601,9605,9609,9613,9617,9622,9627,9632,9637,9642,9647,9652,9656,9661,9666,9671,9676,9681,9686,9691,9696,9701,9706,9711,9716,9721,9726,9731,9736,9741,9746,9751,9756,9761,9766,9771,9776,9781,9786,9791,9796,9801,9806,9811,9816,9820],{"__ignoreMap":34},[500,9602,9603],{"class":502,"line":503},[500,9604,644],{"emptyLinePlaceholder":107},[500,9606,9607],{"class":502,"line":98},[500,9608,9228],{},[500,9610,9611],{"class":502,"line":249},[500,9612,9331],{},[500,9614,9615],{"class":502,"line":583},[500,9616,9336],{},[500,9618,9619],{"class":502,"line":615},[500,9620,9621],{},"sendCustomer = (i) ->\n",[500,9623,9624],{"class":502,"line":621},[500,9625,9626],{}," # As with the barber, the customer's state is\n",[500,9628,9629],{"class":502,"line":631},[500,9630,9631],{}," # defined in this closure. The variables will\n",[500,9633,9634],{"class":502,"line":641},[500,9635,9636],{}," # be modified by the callback methods that are\n",[500,9638,9639],{"class":502,"line":647},[500,9640,9641],{}," # triggered by the message handler's replies.\n",[500,9643,9644],{"class":502,"line":805},[500,9645,9646],{}," waiting = false\n",[500,9648,9649],{"class":502,"line":810},[500,9650,9651],{}," beingServed = false\n",[500,9653,9654],{"class":502,"line":826},[500,9655,9376],{},[500,9657,9658],{"class":502,"line":838},[500,9659,9660],{}," stdout.println \"customer #{i}: #{message}\"\n",[500,9662,9663],{"class":502,"line":937},[500,9664,9665],{}," # just a shorthand\n",[500,9667,9668],{"class":502,"line":943},[500,9669,9670],{}," send = (message, callback) ->\n",[500,9672,9673],{"class":502,"line":948},[500,9674,9675],{}," vertx.eventBus.send addr, message, callback\n",[500,9677,9678],{"class":502,"line":954},[500,9679,9680],{}," # factor out the exit method:\n",[500,9682,9683],{"class":502,"line":1898},[500,9684,9685],{}," # a customer can exit after having been served\n",[500,9687,9688],{"class":502,"line":1904},[500,9689,9690],{}," # or when the queue is full\n",[500,9692,9693],{"class":502,"line":1910},[500,9694,9695],{}," exit = (message) ->\n",[500,9697,9698],{"class":502,"line":1916},[500,9699,9700],{}," log message + \" - exiting\"\n",[500,9702,9703],{"class":502,"line":1922},[500,9704,9705],{}," # this method doesn't send a response\n",[500,9707,9708],{"class":502,"line":1928},[500,9709,9710],{}," # via the replier\n",[500,9712,9713],{"class":502,"line":1934},[500,9714,9715],{}," getHaircut = (message, replier) ->\n",[500,9717,9718],{"class":502,"line":1940},[500,9719,9720],{}," waiting = false\n",[500,9722,9723],{"class":502,"line":1946},[500,9724,9725],{}," beingServed = true\n",[500,9727,9728],{"class":502,"line":1952},[500,9729,9730],{}," log \"being served\"\n",[500,9732,9733],{"class":502,"line":1958},[500,9734,9735],{}," replier 'being-served', exit\n",[500,9737,9738],{"class":502,"line":1964},[500,9739,9740],{}," log \"enters\"\n",[500,9742,9743],{"class":502,"line":1970},[500,9744,9745],{}," send \"customer #{i}\", (message, replier) ->\n",[500,9747,9748],{"class":502,"line":1976},[500,9749,9750],{}," switch message\n",[500,9752,9753],{"class":502,"line":1982},[500,9754,9755],{}," when 'busy'\n",[500,9757,9758],{"class":502,"line":1988},[500,9759,9760],{}," waiting = true\n",[500,9762,9763],{"class":502,"line":1994},[500,9764,9765],{}," log 'waiting'\n",[500,9767,9768],{"class":502,"line":2000},[500,9769,9770],{}," replier 'waiting', getHaircut\n",[500,9772,9773],{"class":502,"line":9047},[500,9774,9775],{}," when 'serve'\n",[500,9777,9778],{"class":502,"line":9052},[500,9779,9780],{}," getHaircut message, replier\n",[500,9782,9783],{"class":502,"line":9057},[500,9784,9785],{}," when 'full'\n",[500,9787,9788],{"class":502,"line":9062},[500,9789,9790],{}," exit message\n",[500,9792,9793],{"class":502,"line":9067},[500,9794,9795],{},"# a loop that continuously sends customers\n",[500,9797,9798],{"class":502,"line":9072},[500,9799,9800],{},"# to the barber\n",[500,9802,9803],{"class":502,"line":9077},[500,9804,9805],{},"sendCustomerLoop = (i) ->\n",[500,9807,9808],{"class":502,"line":9082},[500,9809,9810],{}," sendCustomer i\n",[500,9812,9813],{"class":502,"line":9533},[500,9814,9815],{}," vertx.setTimer waitTime(), -> sendCustomerLoop i + 1\n",[500,9817,9818],{"class":502,"line":9539},[500,9819,9583],{},[500,9821,9822],{"class":502,"line":9545},[500,9823,9824],{}," sendCustomerLoop 1\n",[18,9826,9827],{},"barbershop.coffee\nThis time, we want to run both handler and sender in the same process, for easier testing.",[492,9829,9831],{"className":3047,"code":9830,"language":3049,"meta":34,"style":34},"\nbarber = require 'barber'\ncustomer = require 'customer'\nbarber.start()\ncustomer.start()\n\n",[405,9832,9833,9837,9842,9847,9852],{"__ignoreMap":34},[500,9834,9835],{"class":502,"line":503},[500,9836,644],{"emptyLinePlaceholder":107},[500,9838,9839],{"class":502,"line":98},[500,9840,9841],{},"barber = require 'barber'\n",[500,9843,9844],{"class":502,"line":249},[500,9845,9846],{},"customer = require 'customer'\n",[500,9848,9849],{"class":502,"line":583},[500,9850,9851],{},"barber.start()\n",[500,9853,9854],{"class":502,"line":615},[500,9855,9856],{},"customer.start()\n",[18,9858,9859,9860,9863],{},"Running the shop\nWhen we start the ",[405,9861,9862],{},"barbershop.coffee"," script, we can see in the log that the shop is running as it is supposed to:",[492,9865,9867],{"className":3047,"code":9866,"language":3049,"meta":34,"style":34},"\ncustomer 1: enters\nbarber: serving customer 1\ncustomer 1: being served\nbarber: done serving customer 1\ncustomer 1: done - exiting\ncustomer 2: enters\nbarber: serving customer 2\ncustomer 2: being served\nbarber: done serving customer 2\ncustomer 2: done - exiting\ncustomer 3: enters\n[...]\n\n",[405,9868,9869,9873,9878,9883,9888,9893,9898,9903,9908,9913,9918,9923,9928],{"__ignoreMap":34},[500,9870,9871],{"class":502,"line":503},[500,9872,644],{"emptyLinePlaceholder":107},[500,9874,9875],{"class":502,"line":98},[500,9876,9877],{},"customer 1: enters\n",[500,9879,9880],{"class":502,"line":249},[500,9881,9882],{},"barber: serving customer 1\n",[500,9884,9885],{"class":502,"line":583},[500,9886,9887],{},"customer 1: being served\n",[500,9889,9890],{"class":502,"line":615},[500,9891,9892],{},"barber: done serving customer 1\n",[500,9894,9895],{"class":502,"line":621},[500,9896,9897],{},"customer 1: done - exiting\n",[500,9899,9900],{"class":502,"line":631},[500,9901,9902],{},"customer 2: enters\n",[500,9904,9905],{"class":502,"line":641},[500,9906,9907],{},"barber: serving customer 2\n",[500,9909,9910],{"class":502,"line":647},[500,9911,9912],{},"customer 2: being served\n",[500,9914,9915],{"class":502,"line":805},[500,9916,9917],{},"barber: done serving customer 2\n",[500,9919,9920],{"class":502,"line":810},[500,9921,9922],{},"customer 2: done - exiting\n",[500,9924,9925],{"class":502,"line":826},[500,9926,9927],{},"customer 3: enters\n",[500,9929,9930],{"class":502,"line":838},[500,9931,9932],{},"[...]\n",[18,9934,9935],{},"This is what the output looks like when there is no congestion at all. By chance, these customers came in just after the\nprevious customer was served. If we wait a little longer, we can see a customer entering while the barber is busy:",[492,9937,9939],{"className":3047,"code":9938,"language":3049,"meta":34,"style":34},"\nbarber: serving customer 3\ncustomer 3: being served\ncustomer 4: enters\nbarber: sending customer 4 to queue\ncustomer 4: waiting\nbarber: queued customer 4 - length: 1 - free seats: 19\ncustomer 5: enters\nbarber: sending customer 5 to queue\ncustomer 5: waiting\nbarber: queued customer 5 - length: 2 - free seats: 18\nbarber: done serving customer 3\nbarber: serving customer 4\ncustomer 3: done - exiting\ncustomer 4: being served\n\n",[405,9940,9941,9945,9950,9955,9960,9965,9970,9975,9980,9985,9990,9995,10000,10005,10010],{"__ignoreMap":34},[500,9942,9943],{"class":502,"line":503},[500,9944,644],{"emptyLinePlaceholder":107},[500,9946,9947],{"class":502,"line":98},[500,9948,9949],{},"barber: serving customer 3\n",[500,9951,9952],{"class":502,"line":249},[500,9953,9954],{},"customer 3: being served\n",[500,9956,9957],{"class":502,"line":583},[500,9958,9959],{},"customer 4: enters\n",[500,9961,9962],{"class":502,"line":615},[500,9963,9964],{},"barber: sending customer 4 to queue\n",[500,9966,9967],{"class":502,"line":621},[500,9968,9969],{},"customer 4: waiting\n",[500,9971,9972],{"class":502,"line":631},[500,9973,9974],{},"barber: queued customer 4 - length: 1 - free seats: 19\n",[500,9976,9977],{"class":502,"line":641},[500,9978,9979],{},"customer 5: enters\n",[500,9981,9982],{"class":502,"line":647},[500,9983,9984],{},"barber: sending customer 5 to queue\n",[500,9986,9987],{"class":502,"line":805},[500,9988,9989],{},"customer 5: waiting\n",[500,9991,9992],{"class":502,"line":810},[500,9993,9994],{},"barber: queued customer 5 - length: 2 - free seats: 18\n",[500,9996,9997],{"class":502,"line":826},[500,9998,9999],{},"barber: done serving customer 3\n",[500,10001,10002],{"class":502,"line":838},[500,10003,10004],{},"barber: serving customer 4\n",[500,10006,10007],{"class":502,"line":937},[500,10008,10009],{},"customer 3: done - exiting\n",[500,10011,10012],{"class":502,"line":943},[500,10013,10014],{},"customer 4: being served\n",[18,10016,10017,10018,10021,10022,10025],{},"As you can see, customer 4 was added to the queue and is being served right customer 3 is done. But what happens if the\nqueue is full? Let’s set ",[405,10019,10020],{},"waitTime = -> Math.random() * 80"," in ",[405,10023,10024],{},"customer.coffee"," so that there are a few more customers\nentering than leaving.",[492,10027,10029],{"className":3047,"code":10028,"language":3049,"meta":34,"style":34},"\ncustomer 34: enters\nbarber: sending customer 34 to queue\ncustomer 34: waiting\nbarber: queued customer 34 - length: 20 - free seats: 0\ncustomer 35: enters\ncustomer 35: full - exiting\n\n",[405,10030,10031,10035,10040,10045,10050,10055,10060],{"__ignoreMap":34},[500,10032,10033],{"class":502,"line":503},[500,10034,644],{"emptyLinePlaceholder":107},[500,10036,10037],{"class":502,"line":98},[500,10038,10039],{},"customer 34: enters\n",[500,10041,10042],{"class":502,"line":249},[500,10043,10044],{},"barber: sending customer 34 to queue\n",[500,10046,10047],{"class":502,"line":583},[500,10048,10049],{},"customer 34: waiting\n",[500,10051,10052],{"class":502,"line":615},[500,10053,10054],{},"barber: queued customer 34 - length: 20 - free seats: 0\n",[500,10056,10057],{"class":502,"line":621},[500,10058,10059],{},"customer 35: enters\n",[500,10061,10062],{"class":502,"line":631},[500,10063,10064],{},"customer 35: full - exiting\n",[18,10066,10067],{},"New customers are being turned away, as expected. The important thing is that there is no deadlocks and no invalid\nstates, which can be easily checked by reading the log output. Knowing that there is just one callback method being\nexecuted at any point in time is a great help when reasoning about the program.",[18,10069,10070,10071,2314,10074,10077,10078,10081,10082,10085],{},"Conclusion\nThe central primitive is the construct ",[405,10072,10073],{},"replier(send_message, next_state)",[405,10075,10076],{},"replier"," triggers a state transition in\nthe remote system through ",[405,10079,10080],{},"send_message"," and defines the local ",[405,10083,10084],{},"next_state",".\nIf you can model your system as something similar to linked state machines, this concurrency approach is easy to\nimplement and very powerful.",[4960,10087],{},[1017,10089,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":10091},[],[1029,6722],"2013-04-24T12:34:33","CoffeeScript\\nVert.x supports JavaScript through the Rhino JavaScript engine. Although\\nJavaScript is a decent language once you get to know it, I prefer CoffeeScript, a\\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\\ncompiled JavaScript file.\\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:","https://synyx.de/blog/asynchronous-concurrency-with-vert-x-part-2/",{},"/blog/asynchronous-concurrency-with-vert-x-part-2",{"title":9122,"description":10099},"CoffeeScript\nVert.x supports JavaScript through the Rhino JavaScript engine. Although\nJavaScript is a decent language once you get to know it, I prefer CoffeeScript, a\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\ncompiled JavaScript file.\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:","asynchronous-concurrency-with-vert-x-part-2","blog/asynchronous-concurrency-with-vert-x-part-2",[10103,4934,670,10104],"coffeescript","vertx","CoffeeScript Vert.x supports JavaScript through the Rhino JavaScript engine. Although JavaScript is a decent language once you get to know it, I prefer CoffeeScript, a language that compiles to JavaScript.…","jAhQ7HOciuqYnRkEMPmBHkaC1DBrMMrda632jXz81ds",{"id":10108,"title":10109,"author":10110,"body":10111,"category":10412,"date":10413,"description":10414,"extension":104,"link":10415,"meta":10416,"navigation":107,"path":10417,"seo":10418,"slug":10420,"stem":10421,"tags":10422,"teaser":10423,"__hash__":10424},"blog/blog/asynchronous-concurrency-with-vert-x-part-1.md","Asynchronous concurrency with vert.x – Part 1",[9124],{"type":11,"value":10112,"toc":10408},[10113,10116,10147,10150,10162,10224,10227,10287,10290,10309,10318,10397,10406],[14,10114,10109],{"id":10115},"asynchronous-concurrency-with-vertx-part-1",[18,10117,10118,10119,10124,10125,10128,10129,10132,10133,10136,10137,10140,10141,10146],{},"Event-Driven Concurrency\nAt synyx, we are looking at ",[22,10120,10123],{"href":10121,"rel":10122},"http://www.vertx.io",[26],"vert.x"," for an upcoming project where we are building a system that\nwill need to scale under load. The tag-line of vert.x is ",[168,10126,10127],{},"effortless asynchronous application development for the\nmodern web and enterprise",", which fits the bill, so I decided to play around with it a little bit.\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\nAnybody who has has used jQuery’s ",[405,10130,10131],{},"$.ajax"," should have some idea of what event-driven concurrency means: an event loop\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\nfunction can run simultaneously. The same is true for ",[405,10134,10135],{},"setTimeout",", which is used extensively for animations: adjust the\nproperties of an element a little bit each call, then return to the event loop.\nThis is the reason why there is no ",[405,10138,10139],{},"sleep()"," function in JavaScript – the browser would freeze, the user couldn’t\ninteract with the web page. Each callback method must be short-running.\nWith ",[22,10142,10145],{"href":10143,"rel":10144},"https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers",[26],"WebWorkers",", you can now also perform client-side\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\npassing.",[18,10148,10149],{},"vert.x\nVert.x brings this concept to the server side on top of the JVM. It allows writing applications using a event-driven\nconcurrency model. There are bindings for basically every language that runs on top of the JVM: Java, Ruby, Groovy,\nPython and JavaScript. The distributed event bus provides seamless scaling over multiple cores or hosts. You perform a\ncomputation one process, send data via the event bus to another process where a callback method is executed.",[18,10151,10152,10153,10158,10159,1437],{},"Event bus\nA short example in JavaScript that uses the event bus from\nthe ",[22,10154,10157],{"href":10155,"rel":10156},"https://github.com/vert-x/vert.x/blob/master/vertx-examples/src/main/javascript/eventbus",[26],"vert.x github repository"," –\ndefine a message handler that simply displays messages sent to the event bus address ",[405,10160,10161],{},"example.address",[492,10163,10165],{"className":3047,"code":10164,"language":3049,"meta":34,"style":34},"\n// file handler.js\nload('vertx.js')\nvar eb = vertx.eventBus;\nvar address = 'example.address'\nvar handler = function(message) {\n stdout.println('Received message ' + message)\n}\neb.registerHandler(address, handler);\nfunction vertxStop() {\n eb.unregisterHandler(address, handler);\n}\n\n",[405,10166,10167,10171,10176,10181,10186,10191,10196,10201,10205,10210,10215,10220],{"__ignoreMap":34},[500,10168,10169],{"class":502,"line":503},[500,10170,644],{"emptyLinePlaceholder":107},[500,10172,10173],{"class":502,"line":98},[500,10174,10175],{},"// file handler.js\n",[500,10177,10178],{"class":502,"line":249},[500,10179,10180],{},"load('vertx.js')\n",[500,10182,10183],{"class":502,"line":583},[500,10184,10185],{},"var eb = vertx.eventBus;\n",[500,10187,10188],{"class":502,"line":615},[500,10189,10190],{},"var address = 'example.address'\n",[500,10192,10193],{"class":502,"line":621},[500,10194,10195],{},"var handler = function(message) {\n",[500,10197,10198],{"class":502,"line":631},[500,10199,10200],{}," stdout.println('Received message ' + message)\n",[500,10202,10203],{"class":502,"line":641},[500,10204,802],{},[500,10206,10207],{"class":502,"line":647},[500,10208,10209],{},"eb.registerHandler(address, handler);\n",[500,10211,10212],{"class":502,"line":805},[500,10213,10214],{},"function vertxStop() {\n",[500,10216,10217],{"class":502,"line":810},[500,10218,10219],{}," eb.unregisterHandler(address, handler);\n",[500,10221,10222],{"class":502,"line":826},[500,10223,802],{},[18,10225,10226],{},"We put the program that sends messages in a different file to achieve process isolation:",[492,10228,10230],{"className":3047,"code":10229,"language":3049,"meta":34,"style":34},"\n// file sender.js\nload('vertx.js')\nvar eb = vertx.eventBus;\nvar address = 'example.address'\nvertx.setPeriodic(2000, sendMessage)\nvar count = 0\nfunction sendMessage() {\n var msg = \"some-message-\" + count++;\n eb.send(address, msg);\n stdout.println(\"sent message \" + msg)\n}\n\n",[405,10231,10232,10236,10241,10245,10249,10253,10258,10263,10268,10273,10278,10283],{"__ignoreMap":34},[500,10233,10234],{"class":502,"line":503},[500,10235,644],{"emptyLinePlaceholder":107},[500,10237,10238],{"class":502,"line":98},[500,10239,10240],{},"// file sender.js\n",[500,10242,10243],{"class":502,"line":249},[500,10244,10180],{},[500,10246,10247],{"class":502,"line":583},[500,10248,10185],{},[500,10250,10251],{"class":502,"line":615},[500,10252,10190],{},[500,10254,10255],{"class":502,"line":621},[500,10256,10257],{},"vertx.setPeriodic(2000, sendMessage)\n",[500,10259,10260],{"class":502,"line":631},[500,10261,10262],{},"var count = 0\n",[500,10264,10265],{"class":502,"line":641},[500,10266,10267],{},"function sendMessage() {\n",[500,10269,10270],{"class":502,"line":647},[500,10271,10272],{}," var msg = \"some-message-\" + count++;\n",[500,10274,10275],{"class":502,"line":805},[500,10276,10277],{}," eb.send(address, msg);\n",[500,10279,10280],{"class":502,"line":810},[500,10281,10282],{}," stdout.println(\"sent message \" + msg)\n",[500,10284,10285],{"class":502,"line":826},[500,10286,802],{},[18,10288,10289],{},"Both programs are then started separately using the vertx runtime. They can then communicate on the event bus via the\nnetwork:",[492,10291,10293],{"className":3047,"code":10292,"language":3049,"meta":34,"style":34},"\n# vertx run handler.js -cluster -cluster-port 10001 &\n# vertx run sender.js -cluster -cluster-port 10002\n\n",[405,10294,10295,10299,10304],{"__ignoreMap":34},[500,10296,10297],{"class":502,"line":503},[500,10298,644],{"emptyLinePlaceholder":107},[500,10300,10301],{"class":502,"line":98},[500,10302,10303],{},"# vertx run handler.js -cluster -cluster-port 10001 &\n",[500,10305,10306],{"class":502,"line":249},[500,10307,10308],{},"# vertx run sender.js -cluster -cluster-port 10002\n",[18,10310,10311,10312,10317],{},"Repliers\nThis described system can be extended by the use\nof ",[22,10313,10316],{"href":10314,"rel":10315},"http://vertx.io/core_manual_js.html#replying-to-messages",[26],"repliers",", which can be used to start a dialog between a\nmessage handler and a sender. The sender and the replier both live in the same file this time:",[492,10319,10321],{"className":3047,"code":10320,"language":3049,"meta":34,"style":34},"\nvar vertx = require('vertx'); // alternative import\nvar address = \"example.address\";\nvar handler = function (message, replier) {\n stdout.println(\"sender sent \" + message);\n replier(\"pong 1\", function (message, replier) {\n // and so on\n });\n}\nvertx.eventBus.registerHandler(address, handler);\nvertx.eventBus.send(address, \"ping 1\", function (message, replier) {\n stdout.println(\"handler sent \" + message);\n replier(\"ping 2\", function(message, replier) {\n // and so on\n });\n});\n\n",[405,10322,10323,10327,10332,10337,10342,10347,10352,10357,10361,10365,10370,10375,10380,10385,10389,10393],{"__ignoreMap":34},[500,10324,10325],{"class":502,"line":503},[500,10326,644],{"emptyLinePlaceholder":107},[500,10328,10329],{"class":502,"line":98},[500,10330,10331],{},"var vertx = require('vertx'); // alternative import\n",[500,10333,10334],{"class":502,"line":249},[500,10335,10336],{},"var address = \"example.address\";\n",[500,10338,10339],{"class":502,"line":583},[500,10340,10341],{},"var handler = function (message, replier) {\n",[500,10343,10344],{"class":502,"line":615},[500,10345,10346],{}," stdout.println(\"sender sent \" + message);\n",[500,10348,10349],{"class":502,"line":621},[500,10350,10351],{}," replier(\"pong 1\", function (message, replier) {\n",[500,10353,10354],{"class":502,"line":631},[500,10355,10356],{}," // and so on\n",[500,10358,10359],{"class":502,"line":641},[500,10360,792],{},[500,10362,10363],{"class":502,"line":647},[500,10364,802],{},[500,10366,10367],{"class":502,"line":805},[500,10368,10369],{},"vertx.eventBus.registerHandler(address, handler);\n",[500,10371,10372],{"class":502,"line":810},[500,10373,10374],{},"vertx.eventBus.send(address, \"ping 1\", function (message, replier) {\n",[500,10376,10377],{"class":502,"line":826},[500,10378,10379],{}," stdout.println(\"handler sent \" + message);\n",[500,10381,10382],{"class":502,"line":838},[500,10383,10384],{}," replier(\"ping 2\", function(message, replier) {\n",[500,10386,10387],{"class":502,"line":937},[500,10388,10356],{},[500,10390,10391],{"class":502,"line":943},[500,10392,792],{},[500,10394,10395],{"class":502,"line":948},[500,10396,841],{},[335,10398,10400,10401,10405],{"id":10399},"every-sent-message-can-be-acknowledged-with-a-reply-by-the-other-side-and-vice-versa-this-concurrency-model-is-very-easy-to-grasp-and-very-powerful-we-will-use-it-in-the-next-part-of-this-series-where-we-tackle-the-sleeping-barber-problem-stay-tuned","Every sent message can be acknowledged with a reply by the other side and vice versa. This concurrency model is very easy to grasp and very powerful. We will use it in the next part of this series, where we tackle the ",[22,10402,10404],{"href":9286,"rel":10403},[26],"Sleeping barber problem"," – stay tuned!",[1017,10407,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":10409},[10410],{"id":10399,"depth":98,"text":10411},"Every sent message can be acknowledged with a reply by the other side and vice versa. This concurrency model is very easy to grasp and very powerful. We will use it in the next part of this series, where we tackle the Sleeping barber problem – stay tuned!",[1029,6722],"2013-04-15T20:59:34","Event-Driven Concurrency\\nAt synyx, we are looking at vert.x for an upcoming project where we are building a system that\\nwill need to scale under load. The tag-line of vert.x is effortless asynchronous application development for the\\nmodern web and enterprise, which fits the bill, so I decided to play around with it a little bit.\\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\\nAnybody who has has used jQuery’s $.ajax should have some idea of what event-driven concurrency means: an event loop\\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\\nfunction can run simultaneously. The same is true for setTimeout, which is used extensively for animations: adjust the\\nproperties of an element a little bit each call, then return to the event loop.\\nThis is the reason why there is no sleep() function in JavaScript – the browser would freeze, the user couldn’t\\ninteract with the web page. Each callback method must be short-running.\\nWith WebWorkers, you can now also perform client-side\\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\\npassing.","https://synyx.de/blog/asynchronous-concurrency-with-vert-x-part-1/",{},"/blog/asynchronous-concurrency-with-vert-x-part-1",{"title":10109,"description":10419},"Event-Driven Concurrency\nAt synyx, we are looking at vert.x for an upcoming project where we are building a system that\nwill need to scale under load. The tag-line of vert.x is effortless asynchronous application development for the\nmodern web and enterprise, which fits the bill, so I decided to play around with it a little bit.\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\nAnybody who has has used jQuery’s $.ajax should have some idea of what event-driven concurrency means: an event loop\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\nfunction can run simultaneously. The same is true for setTimeout, which is used extensively for animations: adjust the\nproperties of an element a little bit each call, then return to the event loop.\nThis is the reason why there is no sleep() function in JavaScript – the browser would freeze, the user couldn’t\ninteract with the web page. Each callback method must be short-running.\nWith WebWorkers, you can now also perform client-side\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\npassing.","asynchronous-concurrency-with-vert-x-part-1","blog/asynchronous-concurrency-with-vert-x-part-1",[4934,670,10104],"Event-Driven Concurrency At synyx, we are looking at vert.x for an upcoming project where we are building a system that will need to scale under load. The tag-line of vert.x…","DkNmAOE6c6bSo2QAVLXKERm9hchogl5z2xWxAovYy3Q",{"id":10426,"title":10427,"author":10428,"body":10430,"category":11178,"date":11179,"description":11180,"extension":104,"link":11181,"meta":11182,"navigation":107,"path":11183,"seo":11184,"slug":10434,"stem":11186,"tags":11187,"teaser":11190,"__hash__":11191},"blog/blog/running-rxtx-on-your-raspberry-pi.md","Running RXTX on your Raspberry Pi",[10429],"graf",{"type":11,"value":10431,"toc":11170},[10432,10435,10450,10453,10468,10472,10475,10499,10503,10518,10521,10525,10539,10543,10546,10549,10682,10691,10694,10697,10778,10784,10820,10824,10827,10843,10846,10867,10870,10874,10877,11081,11084,11102,11112,11115,11164,11167],[14,10433,10427],{"id":10434},"running-rxtx-on-your-raspberry-pi",[18,10436,10437,10438,10443,10444,10449],{},"A few years ago, a good friend of mine installed a\nsmall ",[22,10439,10442],{"href":10440,"rel":10441,"title":10442},"http://en.wikipedia.org/wiki/Photovoltaic_system",[26],"photovoltaic system"," on his roof. I’m\nvery exited about installing some solar panels on a roof and start producing electricity. It turned out that the\ninstalled inverters have massive quality problems. So the idea was born to monitor the whole setup like i know it from\ncomputer systems. Each ",[22,10445,10448],{"href":10446,"rel":10447,"title":10448},"http://en.wikipedia.org/wiki/Power_inverter",[26],"inverter"," in this system has a serial\nport interface to transmit data.",[18,10451,10452],{},"But at this time, the market of low cost and low power consuming computers wasn’t distinct as it is nowadays. In\nFebruary 2012, the Raspberry Pi Foundation started to sell Raspberry Pis. I saw this as a chance to get a cheap computer\nwith a minimal power consumption.",[18,10454,10455,10456,10461,10462,10467],{},"So this blog post is about connecting a Raspberry Pi to inverters (or any other device) via RS232. We will use\nthe ",[22,10457,10460],{"href":10458,"rel":10459,"title":10460},"http://www.oracle.com/technetwork/java/index-jsp-141752.html",[26],"Java Communications API"," to\nread received data from the inverters. We don’t use\nthe ",[22,10463,10466],{"href":10464,"rel":10465,"title":10466},"http://www.savagehomeautomation.com/projects/raspberry-pi-installing-a-rs232-serial-port.html",[26],"integrated RS232 port","\nbecause we want to monitore more than just a signle inverter, so we have to use USB-RS232 adapters.",[335,10469,10471],{"id":10470},"prepare-your-pi","Prepare your Pi",[18,10473,10474],{},"To demonstrate this post, I’m using the following hardware setup:",[343,10476,10477,10480,10483,10490,10493,10496],{},[346,10478,10479],{},"Raspberry Pi – Model B rev 2",[346,10481,10482],{},"SDCard SanDisk Ultra SDHC Class 10",[346,10484,10485],{},[22,10486,10489],{"href":10487,"rel":10488,"title":10489},"http://www.dlink.com/us/en/home-solutions/connect/usb-hubs/dub-h7-7-port-usb-2-0-hub",[26],"D-Link USB hub",[346,10491,10492],{},"Micro USB cable",[346,10494,10495],{},"USB-RS232 adapters",[346,10497,10498],{},"Alfa Networks USB WIFI adapter",[335,10500,10502],{"id":10501},"connect-cables-and-stuff","Connect Cables and Stuff",[18,10504,10505,10506,10512,10513,10517],{},"First of all we have to get a Pi ready running a Linux with SSH access. The Raspberry guys recommend\nusing ",[22,10507,10511],{"href":10508,"rel":10509,"title":10510},"http://www.raspberrypi.org/downloads",[26],"Raspbian ","Raspbian “wheezy”"," with Hard-Float. Follow the Quick start\nguide ",[22,10514,10515],{"href":10515,"rel":10516},"http://www.raspberrypi.org/quick-start-guide",[26]," to setup your Pi.",[18,10519,10520],{},"Plug in all your USB devices into the USB hub, like keyboard, mouse, WIFI adapter, USB-RS232 adapters. The USB hub will\nbe used as a power supply, so plug in the micro USB cable into the hub and into the micro USB port of your Pi. For the\nfirst boot, connect also a network cable into your Pi. Power up the USB hub and the Pi is going to boot for the first\ntime.",[335,10522,10524],{"id":10523},"configure-wifi","Configure WIFI",[18,10526,10527,10528,10531,10532,1368,10535,10538],{},"Now it’s time to setup the WIFI connection. Install the wpasupplicant package via ",[405,10529,10530],{},"apt-get install wpasupplicant",". Then\ncopy paste ",[405,10533,10534],{},"/etc/network/interfaces",[405,10536,10537],{},"/etc/wpa_supplicant/wpa_supplicant.conf"," from my gists and adapt the files.",[335,10540,10542],{"id":10541},"serial-adapters","Serial Adapters",[18,10544,10545],{},"The D-Link USB hub has 7 ports (I define port 1 as the one on the left side and port 7 as the port on the right side)\nand I plugged two USB RS232 adapters into port 1 and port 2.",[18,10547,10548],{},"To check the USB-RS232 adapters are recognized by the Pi, run:",[492,10550,10554],{"className":10551,"code":10552,"language":10553,"meta":34,"style":34},"language-bash shiki shiki-themes github-light github-dark","root@raspberrypi:/# lsusb | grep 232\nBus 001 Device 011: ID 0557:2008 ATEN International Co., Ltd UC-232A Serial Port [pl2303]\nBus 001 Device 010: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC\nroot@raspberrypi:/# ls /dev/ttyUSB*\n/dev/ttyUSB0 /dev/ttyUSB1\nroot@raspberrypi:/#\n","bash",[405,10555,10556,10573,10617,10656,10669,10677],{"__ignoreMap":34},[500,10557,10558,10561,10564,10567,10570],{"class":502,"line":503},[500,10559,10560],{"class":513},"root@raspberrypi:/#",[500,10562,10563],{"class":520}," lsusb",[500,10565,10566],{"class":677}," |",[500,10568,10569],{"class":513}," grep",[500,10571,10572],{"class":703}," 232\n",[500,10574,10575,10578,10581,10584,10587,10590,10593,10596,10599,10602,10605,10608,10611,10614],{"class":502,"line":98},[500,10576,10577],{"class":513},"Bus",[500,10579,10580],{"class":703}," 001",[500,10582,10583],{"class":520}," Device",[500,10585,10586],{"class":520}," 011:",[500,10588,10589],{"class":520}," ID",[500,10591,10592],{"class":520}," 0557:2008",[500,10594,10595],{"class":520}," ATEN",[500,10597,10598],{"class":520}," International",[500,10600,10601],{"class":520}," Co.,",[500,10603,10604],{"class":520}," Ltd",[500,10606,10607],{"class":520}," UC-232A",[500,10609,10610],{"class":520}," Serial",[500,10612,10613],{"class":520}," Port",[500,10615,10616],{"class":506}," [pl2303]\n",[500,10618,10619,10621,10623,10625,10628,10630,10633,10636,10639,10642,10645,10647,10650,10653],{"class":502,"line":249},[500,10620,10577],{"class":513},[500,10622,10580],{"class":703},[500,10624,10583],{"class":520},[500,10626,10627],{"class":520}," 010:",[500,10629,10589],{"class":520},[500,10631,10632],{"class":520}," 0403:6001",[500,10634,10635],{"class":520}," Future",[500,10637,10638],{"class":520}," Technology",[500,10640,10641],{"class":520}," Devices",[500,10643,10644],{"class":520}," International,",[500,10646,10604],{"class":520},[500,10648,10649],{"class":520}," FT232",[500,10651,10652],{"class":520}," USB-Serial",[500,10654,10655],{"class":506}," (UART) IC\n",[500,10657,10658,10660,10663,10666],{"class":502,"line":583},[500,10659,10560],{"class":513},[500,10661,10662],{"class":520}," ls",[500,10664,10665],{"class":520}," /dev/ttyUSB",[500,10667,10668],{"class":703},"*\n",[500,10670,10671,10674],{"class":502,"line":615},[500,10672,10673],{"class":513},"/dev/ttyUSB0",[500,10675,10676],{"class":520}," /dev/ttyUSB1\n",[500,10678,10679],{"class":502,"line":621},[500,10680,10681],{"class":513},"root@raspberrypi:/#\n",[18,10683,10684,10685,10687,10688,10690],{},"One important question is, which of the adapters is ",[405,10686,10673],{},"? Smart people will say, ",[405,10689,10673],{}," is the one,\nplugged in first – that’s right.",[18,10692,10693],{},"Lazy people (including myself) are using udev rules to create an alias per USB hub port.",[18,10695,10696],{},"After the rules are applied, all serial ports are accessible via aliases and we have a nice and easy way accessing our\nserial ports, no matter which port was plugged in first.",[492,10698,10700],{"className":10551,"code":10699,"language":10553,"meta":34,"style":34},"root@raspberrypi:/# ls -l /dev/ttySerialPort*\nlrwxrwxrwx 1 root root 7 Jan 1 1970 /dev/ttySerialPort1 -> ttyUSB1\nlrwxrwxrwx 1 root root 7 Jan 1 1970 /dev/ttySerialPort2 -> ttyUSB0\n",[405,10701,10702,10716,10750],{"__ignoreMap":34},[500,10703,10704,10706,10708,10711,10714],{"class":502,"line":503},[500,10705,10560],{"class":513},[500,10707,10662],{"class":520},[500,10709,10710],{"class":703}," -l",[500,10712,10713],{"class":520}," /dev/ttySerialPort",[500,10715,10668],{"class":703},[500,10717,10718,10721,10723,10725,10727,10729,10732,10735,10738,10741,10744,10747],{"class":502,"line":98},[500,10719,10720],{"class":513},"lrwxrwxrwx",[500,10722,8258],{"class":703},[500,10724,3839],{"class":520},[500,10726,3839],{"class":520},[500,10728,8300],{"class":703},[500,10730,10731],{"class":520}," Jan",[500,10733,10734],{"class":703}," 1",[500,10736,10737],{"class":703}," 1970",[500,10739,10740],{"class":520}," /dev/ttySerialPort1",[500,10742,10743],{"class":506}," -",[500,10745,10746],{"class":677},">",[500,10748,10749],{"class":520}," ttyUSB1\n",[500,10751,10752,10754,10756,10758,10760,10762,10764,10766,10768,10771,10773,10775],{"class":502,"line":249},[500,10753,10720],{"class":513},[500,10755,8258],{"class":703},[500,10757,3839],{"class":520},[500,10759,3839],{"class":520},[500,10761,8300],{"class":703},[500,10763,10731],{"class":520},[500,10765,10734],{"class":703},[500,10767,10737],{"class":703},[500,10769,10770],{"class":520}," /dev/ttySerialPort2",[500,10772,10743],{"class":506},[500,10774,10746],{"class":677},[500,10776,10777],{"class":520}," ttyUSB0\n",[18,10779,10780,10781],{},"To run the Java application later without root, we need a new user which has the privilege to access serial ports. To do\nso, just add a new user to group ",[405,10782,10783],{},"dialout",[492,10785,10787],{"className":10551,"code":10786,"language":10553,"meta":34,"style":34},"root@raspberrypi:/# useradd -m -d /home/rxtxpi -s /bin/bash -G dialout rxtxpi\n",[405,10788,10789],{"__ignoreMap":34},[500,10790,10791,10793,10796,10799,10802,10805,10808,10811,10814,10817],{"class":502,"line":503},[500,10792,10560],{"class":513},[500,10794,10795],{"class":520}," useradd",[500,10797,10798],{"class":703}," -m",[500,10800,10801],{"class":703}," -d",[500,10803,10804],{"class":520}," /home/rxtxpi",[500,10806,10807],{"class":703}," -s",[500,10809,10810],{"class":520}," /bin/bash",[500,10812,10813],{"class":703}," -G",[500,10815,10816],{"class":520}," dialout",[500,10818,10819],{"class":520}," rxtxpi\n",[335,10821,10823],{"id":10822},"installing-software","Installing Software",[18,10825,10826],{},"Now we’re going to install the required software to communicate via serial adapters. For Java applications, we can use\nthe Java Communications API (JCA). JCA requires some native platform specific code which is called via Java Native\nInterface (JNI). We also need the Java part of JCA.",[18,10828,10829,10830,10836,10837,10842],{},"The ugly part – JCA is not provided by Oracle via JRE and I couldn’t find any downloads from Oracle. The good part –\nthere is an Open Source implementation of JCA called ",[22,10831,10835],{"href":10832,"rel":10833,"title":10834},"http://rxtx.qbang.org/",[26],"RXTX project website","RXTX",". The native\npart can be installed via librxtx-java deb package. As long as there is no RXTX jar in Maven Central you can use\nthis ",[22,10838,10839],{"href":10839,"rel":10840,"title":10841},"https://github.com/grafjo/rxtx",[26],"inofficial RXTX maven repository"," as the Maven\nrepository for RXTX.",[18,10844,10845],{},"We just install a JRE and RXTX on Pi, because compiling software on the Pi is really really slow! Install a Java 1.6\nJDK and Gradle > 1.2 on your desktop, build the Java application there and copy it to the Pi.",[492,10847,10849],{"className":10551,"code":10848,"language":10553,"meta":34,"style":34},"root@raspberrypi:/# apt-get install openjdk-6-jre librxtx-java\n",[405,10850,10851],{"__ignoreMap":34},[500,10852,10853,10855,10858,10861,10864],{"class":502,"line":503},[500,10854,10560],{"class":513},[500,10856,10857],{"class":520}," apt-get",[500,10859,10860],{"class":520}," install",[500,10862,10863],{"class":520}," openjdk-6-jre",[500,10865,10866],{"class":520}," librxtx-java\n",[18,10868,10869],{},"Now the Pi is ready to run a Java application 🙂",[335,10871,10873],{"id":10872},"running-sample-application","Running Sample Application",[18,10875,10876],{},"The sample application just displays the plugged in serial adapters of your Pi. So switch to your desktop and build the\nsample application:",[492,10878,10880],{"className":10551,"code":10879,"language":10553,"meta":34,"style":34},"joo@jgraf:~$ git clone git@github.com:grafjo/rxtxpi.git\nCloning into 'rxtxpi'...\nremote: Counting objects: 25, done.\nremote: Compressing objects: 100% (16/16), done.\nremote: Total 25 (delta 1), reused 24 (delta 0)\nReceiving objects: 100% (25/25), done.\nResolving deltas: 100% (1/1), done.\njoo@jgraf:~$ cd rxtxpi/\njoo@jgraf:~/rxtxpi$ gradle distZip\n:compileJava\nwarning: [options] bootstrap class path not set in conjunction with -source 1.6\n1 warning\n:processResources UP-TO-DATE\n:classes\n:jar\n:startScripts\n:distZip\nBUILD SUCCESSFUL\nTotal time: 4.458 secs\n\n",[405,10881,10882,10896,10907,10924,10939,10965,10977,10990,11000,11011,11016,11024,11031,11039,11044,11049,11054,11059,11067],{"__ignoreMap":34},[500,10883,10884,10887,10890,10893],{"class":502,"line":503},[500,10885,10886],{"class":513},"joo@jgraf:~$",[500,10888,10889],{"class":520}," git",[500,10891,10892],{"class":520}," clone",[500,10894,10895],{"class":520}," git@github.com:grafjo/rxtxpi.git\n",[500,10897,10898,10901,10904],{"class":502,"line":98},[500,10899,10900],{"class":513},"Cloning",[500,10902,10903],{"class":520}," into",[500,10905,10906],{"class":520}," 'rxtxpi'...\n",[500,10908,10909,10912,10915,10918,10921],{"class":502,"line":249},[500,10910,10911],{"class":513},"remote:",[500,10913,10914],{"class":520}," Counting",[500,10916,10917],{"class":520}," objects:",[500,10919,10920],{"class":520}," 25,",[500,10922,10923],{"class":520}," done.\n",[500,10925,10926,10928,10931,10933,10936],{"class":502,"line":583},[500,10927,10911],{"class":513},[500,10929,10930],{"class":520}," Compressing",[500,10932,10917],{"class":520},[500,10934,10935],{"class":520}," 100%",[500,10937,10938],{"class":506}," (16/16), done.\n",[500,10940,10941,10943,10946,10949,10952,10954,10957,10960,10963],{"class":502,"line":615},[500,10942,10911],{"class":513},[500,10944,10945],{"class":520}," Total",[500,10947,10948],{"class":703}," 25",[500,10950,10951],{"class":506}," (delta ",[500,10953,4080],{"class":703},[500,10955,10956],{"class":506},"), reused 24 (",[500,10958,10959],{"class":513},"delta",[500,10961,10962],{"class":703}," 0",[500,10964,4044],{"class":506},[500,10966,10967,10970,10972,10974],{"class":502,"line":621},[500,10968,10969],{"class":513},"Receiving",[500,10971,10917],{"class":520},[500,10973,10935],{"class":520},[500,10975,10976],{"class":506}," (25/25), done.\n",[500,10978,10979,10982,10985,10987],{"class":502,"line":631},[500,10980,10981],{"class":513},"Resolving",[500,10983,10984],{"class":520}," deltas:",[500,10986,10935],{"class":520},[500,10988,10989],{"class":506}," (1/1), done.\n",[500,10991,10992,10994,10997],{"class":502,"line":641},[500,10993,10886],{"class":513},[500,10995,10996],{"class":520}," cd",[500,10998,10999],{"class":520}," rxtxpi/\n",[500,11001,11002,11005,11008],{"class":502,"line":647},[500,11003,11004],{"class":513},"joo@jgraf:~/rxtxpi$",[500,11006,11007],{"class":520}," gradle",[500,11009,11010],{"class":520}," distZip\n",[500,11012,11013],{"class":502,"line":805},[500,11014,11015],{"class":513},":compileJava\n",[500,11017,11018,11021],{"class":502,"line":810},[500,11019,11020],{"class":513},"warning:",[500,11022,11023],{"class":506}," [options] bootstrap class path not set in conjunction with -source 1.6\n",[500,11025,11026,11028],{"class":502,"line":826},[500,11027,4080],{"class":513},[500,11029,11030],{"class":520}," warning\n",[500,11032,11033,11036],{"class":502,"line":838},[500,11034,11035],{"class":513},":processResources",[500,11037,11038],{"class":520}," UP-TO-DATE\n",[500,11040,11041],{"class":502,"line":937},[500,11042,11043],{"class":513},":classes\n",[500,11045,11046],{"class":502,"line":943},[500,11047,11048],{"class":513},":jar\n",[500,11050,11051],{"class":502,"line":948},[500,11052,11053],{"class":513},":startScripts\n",[500,11055,11056],{"class":502,"line":954},[500,11057,11058],{"class":513},":distZip\n",[500,11060,11061,11064],{"class":502,"line":1898},[500,11062,11063],{"class":513},"BUILD",[500,11065,11066],{"class":520}," SUCCESSFUL\n",[500,11068,11069,11072,11075,11078],{"class":502,"line":1904},[500,11070,11071],{"class":513},"Total",[500,11073,11074],{"class":520}," time:",[500,11076,11077],{"class":703}," 4.458",[500,11079,11080],{"class":520}," secs\n",[18,11082,11083],{},"Copy the application to your Pi:",[492,11085,11087],{"className":10551,"code":11086,"language":10553,"meta":34,"style":34},"joo@jgraf:~/rxtxpi$ scp build/distributions/rxtxpi.zip rxtxpi@raspberry:/home/rxtxpi\n",[405,11088,11089],{"__ignoreMap":34},[500,11090,11091,11093,11096,11099],{"class":502,"line":503},[500,11092,11004],{"class":513},[500,11094,11095],{"class":520}," scp",[500,11097,11098],{"class":520}," build/distributions/rxtxpi.zip",[500,11100,11101],{"class":520}," rxtxpi@raspberry:/home/rxtxpi\n",[18,11103,11104,11105,11108,11109,707],{},"Switch back to your Pi, unzip the rxtxpi.zip. To use the predefined aliases for serial ports, we have to modify the\ndefault JVM options to ",[405,11106,11107],{},"DEFAULT_JVM_OPTS=\"-Dgnu.io.rxtx.SerialPorts=/dev/ttySerialPort1:/dev/ttySerialPort2\""," inside the\nrxtx_pi startup script ",[405,11110,11111],{},"rxtx_pi/bin/rxtx_pi",[18,11113,11114],{},"Ok – when every thing was installed successfully, we will get this output from our Java application using RXTX",[492,11116,11118],{"className":10551,"code":11117,"language":10553,"meta":34,"style":34},"rxtxpi@raspberrypi ~/rxtx_pi/bin $ ./rxtx_pi\n[main] INFO de.synyx.rxtxpi.SerialPortUtils - Looking for serial ports\n[main] INFO de.synyx.rxtxpi.SerialPortUtils - Found port: /dev/ttySerialPort1\n[main] INFO de.synyx.rxtxpi.SerialPortUtils - Found port: /dev/ttySerialPort2\nrxtxpi@raspberrypi ~/rxtx_pi/bin $\n\n",[405,11119,11120,11134,11145,11150,11155],{"__ignoreMap":34},[500,11121,11122,11125,11128,11131],{"class":502,"line":503},[500,11123,11124],{"class":513},"rxtxpi@raspberrypi",[500,11126,11127],{"class":520}," ~/rxtx_pi/bin",[500,11129,11130],{"class":506}," $ ",[500,11132,11133],{"class":520},"./rxtx_pi\n",[500,11135,11136,11139,11142],{"class":502,"line":98},[500,11137,11138],{"class":506},"[main] INFO de.synyx.rxtxpi.SerialPortUtils - Looking ",[500,11140,11141],{"class":677},"for",[500,11143,11144],{"class":506}," serial ports\n",[500,11146,11147],{"class":502,"line":249},[500,11148,11149],{"class":506},"[main] INFO de.synyx.rxtxpi.SerialPortUtils - Found port: /dev/ttySerialPort1\n",[500,11151,11152],{"class":502,"line":583},[500,11153,11154],{"class":506},"[main] INFO de.synyx.rxtxpi.SerialPortUtils - Found port: /dev/ttySerialPort2\n",[500,11156,11157,11159,11161],{"class":502,"line":615},[500,11158,11124],{"class":513},[500,11160,11127],{"class":520},[500,11162,11163],{"class":506}," $\n",[18,11165,11166],{},"So this blog post was about getting a Pi ready to run a RXTX Java application and verified that everything works well.\nThe next post will be about how to use the Java Communications API to read data from an inverter.",[1017,11168,11169],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":98,"depth":98,"links":11171},[11172,11173,11174,11175,11176,11177],{"id":10470,"depth":98,"text":10471},{"id":10501,"depth":98,"text":10502},{"id":10523,"depth":98,"text":10524},{"id":10541,"depth":98,"text":10542},{"id":10822,"depth":98,"text":10823},{"id":10872,"depth":98,"text":10873},[6722],"2013-03-12T11:36:50","A few years ago, a good friend of mine installed a\\nsmall photovoltaic system on his roof. I’m\\nvery exited about installing some solar panels on a roof and start producing electricity. It turned out that the\\ninstalled inverters have massive quality problems. So the idea was born to monitor the whole setup like i know it from\\ncomputer systems. Each inverter in this system has a serial\\nport interface to transmit data.","https://synyx.de/blog/running-rxtx-on-your-raspberry-pi/",{},"/blog/running-rxtx-on-your-raspberry-pi",{"title":10427,"description":11185},"A few years ago, a good friend of mine installed a\nsmall photovoltaic system on his roof. I’m\nvery exited about installing some solar panels on a roof and start producing electricity. It turned out that the\ninstalled inverters have massive quality problems. So the idea was born to monitor the whole setup like i know it from\ncomputer systems. Each inverter in this system has a serial\nport interface to transmit data.","blog/running-rxtx-on-your-raspberry-pi",[869,11188,11189],"java-communications-api","raspberry-pi","A few years ago, a good friend of mine installed a small photovoltaic system on his roof. I’m very exited about installing some solar panels on a roof and start…","IILUDO8EXdnYtZkxUr-up01We3gJyNea4Apbe5Kvx4A",{"id":11193,"title":11194,"author":11195,"body":11197,"category":11561,"date":11563,"description":11204,"extension":104,"link":11564,"meta":11565,"navigation":107,"path":11566,"seo":11567,"slug":11201,"stem":11568,"tags":11569,"teaser":11574,"__hash__":11575},"blog/blog/how-sysadmins-monitor-your-java-application-with-jmx.md","How Sysadmins monitor your Java application with JMX",[11196],"ruessel",{"type":11,"value":11198,"toc":11552},[11199,11202,11205,11213,11217,11221,11224,11235,11241,11244,11247,11257,11260,11270,11279,11304,11308,11323,11327,11330,11351,11398,11403,11418,11435,11473,11478,11536,11541,11549],[14,11200,11194],{"id":11201},"how-sysadmins-monitor-your-java-application-with-jmx",[18,11203,11204],{},"some time ago Aljona showed",[18,11206,11207],{},[22,11208,11212],{"href":11209,"rel":11210,"title":11211},"http://blog.synyx.de/2012/05/how-to-monitor-and-manage-your-java-application-with-jmx/",[26],"JMX-Blog by Aljona ","how to monitor and manage your java application with jmx",[335,11214,11216],{"id":11215},"im-going-to-show-how-you-can-make-use-of-jmx-from-the-viewpoint-of-a-sysadmin","I’m going to show, how you can make use of JMX from the viewpoint of a sysadmin.",[133,11218,11220],{"id":11219},"initial-point","initial point:",[18,11222,11223],{},"You have a Java-application deployed in an applicationserver like JBoss or Tomcat and you want to monitor the health of\nthis application(including the applicationserver and the Java-virtual-machine it is running in) with a tool like\nNagios.",[133,11225,11227,11228,11234],{"id":11226},"use-jolokia-on-the-server-side","Use ",[22,11229,11233],{"href":11230,"rel":11231,"title":11232},"http://jolokia.org",[26],"JMX on Capsaicin","Jolokia"," on the server-side!",[18,11236,11237],{},[32,11238],{"alt":11239,"src":11240},"\"jolokia\"","https://media.synyx.de/uploads//2013/02/jolokia.png",[18,11242,11243],{},"Why do i have to use an extra Agent for this? My applicationserver is able to use Mbeans.",[18,11245,11246],{},"Of course you don’t have to, but Jolokia is a HTTP/JSON bridge, which means jolokia can be accessed via HTTP and\nresponds with JSON, that is cool, isn’t it?",[18,11248,11249,11250,11256],{},"There are some more advantages like bulk-requests and it\nsupports ",[22,11251,11255],{"href":11252,"rel":11253,"title":11254},"https://web.archive.org/web/20130301163513/http://square.github.com:80/cubism/",[26],"cubism on github","cubism.js","\nsince version 1.0.5 (Jonathan uses this to get some nicely visualized charts) … and it’s fast.",[18,11258,11259],{},"Just deploy it into your applicationserver it isn’t that big.",[18,11261,11262,11263,11269],{},"As a sysadmin i don’t want to code Java nor do i want to have a JVM running on my Nagios-server, i’m used to use perl (\njust to satisfy any prejudices), so i stumbled\nover ",[22,11264,11268],{"href":11265,"rel":11266,"title":11267},"http://labs.consol.de/jmx4perl/",[26],"Jmx4Perl on ConsolLabs ","jmx4perl"," and decided to make use of this.",[18,11271,11272,11273,11278],{},"I installed ",[22,11274,11268],{"href":11275,"rel":11276,"title":11277},"http://search.cpan.org/~roland/jmx4perl/",[26],"Jmx4Perl on CPAN"," and read the documentation.",[492,11280,11284],{"className":11281,"code":11282,"language":11283,"meta":34,"style":34},"language-shell shiki shiki-themes github-light github-dark","\ncpan[1]> install JMX::Jmx4Perl\n\n","shell",[405,11285,11286,11290],{"__ignoreMap":34},[500,11287,11288],{"class":502,"line":503},[500,11289,644],{"emptyLinePlaceholder":107},[500,11291,11292,11295,11298,11301],{"class":502,"line":98},[500,11293,11294],{"class":513},"cpan[1]",[500,11296,11297],{"class":506},"> ",[500,11299,11300],{"class":520},"install",[500,11302,11303],{"class":520}," JMX::Jmx4Perl\n",[133,11305,11307],{"id":11306},"the-tools-brought-along-by-jmx4perl-are-really-very-useful","The tools brought along by jmx4perl are really very useful:",[1622,11309,11310,11317],{},[346,11311,11312,11313,11316],{},"The commandlinetool ",[469,11314,11315],{},"j4psh"," is awesome, you can browse the JMX Mbeans in color by using “cd” and “ls” with “cat”\nyou can get the value of an attribute… tab-completion as a matter of course.",[346,11318,11319,11320,11322],{},"With the commandlinetool ",[469,11321,11268],{}," you can easily test requests and get responses in various forms.",[133,11324,11326],{"id":11325},"time-to-hack","Time to hack:",[18,11328,11329],{},"First Nagios-test we assemble is for monitoring heapspace of the JVM, not only because it’s often referenced in the\ndocumentation but rather because thats often a useful information to identify memory leaks and to know when it’s time to\nrestart the application(server). But you can follow most of the following steps for many other use cases too.",[1622,11331,11332,11335,11338,11345,11348],{},[346,11333,11334],{},"be sure you got the relevant permissions in jolokia (jolokia-access.xml)",[346,11336,11337],{},"search for older Nagios-Plugins and c’n’p the generic parts…",[346,11339,11340,11341],{},"search for the Mbean with j4psh or use type search of jolokia and look for interesting attributes/operations\n",[32,11342],{"alt":11343,"src":11344},"\"j4psh_screenshot\"","https://media.synyx.de/uploads//2013/02/j4psh_screenshot.png",[346,11346,11347],{},"test the request you have in your mind with e.g jmx4perl",[346,11349,11350],{},"canonicalize the request",[492,11352,11356],{"className":11353,"code":11354,"language":11355,"meta":34,"style":34},"language-perl shiki shiki-themes github-light github-dark","my $request = new JMX::Jmx4Perl::Request({\n type => READ,\n mbean => \"java.lang:type=Memory\",\n attribute => \"HeapMemoryUsage\",\n });\n#print(Dumper($request));\nmy $response = $jmx->request($request);\n#print(Dumper($response));\n","perl",[405,11357,11358,11363,11368,11373,11378,11383,11388,11393],{"__ignoreMap":34},[500,11359,11360],{"class":502,"line":503},[500,11361,11362],{},"my $request = new JMX::Jmx4Perl::Request({\n",[500,11364,11365],{"class":502,"line":98},[500,11366,11367],{}," type => READ,\n",[500,11369,11370],{"class":502,"line":249},[500,11371,11372],{}," mbean => \"java.lang:type=Memory\",\n",[500,11374,11375],{"class":502,"line":583},[500,11376,11377],{}," attribute => \"HeapMemoryUsage\",\n",[500,11379,11380],{"class":502,"line":615},[500,11381,11382],{}," });\n",[500,11384,11385],{"class":502,"line":621},[500,11386,11387],{},"#print(Dumper($request));\n",[500,11389,11390],{"class":502,"line":631},[500,11391,11392],{},"my $response = $jmx->request($request);\n",[500,11394,11395],{"class":502,"line":641},[500,11396,11397],{},"#print(Dumper($response));\n",[1622,11399,11400],{"start":641},[346,11401,11402],{},"dereference the response as you need",[492,11404,11406],{"className":11353,"code":11405,"language":11355,"meta":34,"style":34},"my $used_memory=$response->value()->{'used'};\nmy $max_memory=$response->value()->{'max'};\n",[405,11407,11408,11413],{"__ignoreMap":34},[500,11409,11410],{"class":502,"line":503},[500,11411,11412],{},"my $used_memory=$response->value()->{'used'};\n",[500,11414,11415],{"class":502,"line":98},[500,11416,11417],{},"my $max_memory=$response->value()->{'max'};\n",[1622,11419,11420,11423,11426,11429,11432],{"start":805},[346,11421,11422],{},"start a heated debate about sensible values for warning and critical",[346,11424,11425],{},"deploy your Nagios-plugin",[346,11427,11428],{},"copy the script into your plugin-folder of your Nagios-server",[346,11430,11431],{},"install all missing libraries on the Nagios-server",[346,11433,11434],{},"define the command in commands.cfg",[492,11436,11438],{"className":10551,"code":11437,"language":10553,"meta":34,"style":34},"define command{\n command_name check_jmx4perl_heapspace.pl\n command_line $USER1$/check_jmx4perl_heapspace.pl $HOSTADDRESS$ $ARG1$\n}\n",[405,11439,11440,11447,11455,11469],{"__ignoreMap":34},[500,11441,11442,11444],{"class":502,"line":503},[500,11443,816],{"class":513},[500,11445,11446],{"class":520}," command{\n",[500,11448,11449,11452],{"class":502,"line":98},[500,11450,11451],{"class":513}," command_name",[500,11453,11454],{"class":520}," check_jmx4perl_heapspace.pl\n",[500,11456,11457,11460,11463,11466],{"class":502,"line":249},[500,11458,11459],{"class":513}," command_line",[500,11461,11462],{"class":506}," $USER1$",[500,11464,11465],{"class":520},"/check_jmx4perl_heapspace.pl",[500,11467,11468],{"class":506}," $HOSTADDRESS$ $ARG1$\n",[500,11470,11471],{"class":502,"line":583},[500,11472,802],{"class":506},[1622,11474,11475],{"start":615},[346,11476,11477],{},"make use of the new command in your services.cfg (or whatever you’ve called your file)",[492,11479,11481],{"className":10551,"code":11480,"language":10553,"meta":34,"style":34},"\ndefine service{\n use remote-service\n host_name jolokia-host\n service_description HeapSpace of JavaApp\n check_command check_jmx4perl_heapspace.pl!8084\n}\n\n",[405,11482,11483,11487,11494,11502,11510,11524,11532],{"__ignoreMap":34},[500,11484,11485],{"class":502,"line":503},[500,11486,644],{"emptyLinePlaceholder":107},[500,11488,11489,11491],{"class":502,"line":98},[500,11490,816],{"class":513},[500,11492,11493],{"class":520}," service{\n",[500,11495,11496,11499],{"class":502,"line":249},[500,11497,11498],{"class":513}," use",[500,11500,11501],{"class":520}," remote-service\n",[500,11503,11504,11507],{"class":502,"line":583},[500,11505,11506],{"class":513}," host_name",[500,11508,11509],{"class":520}," jolokia-host\n",[500,11511,11512,11515,11518,11521],{"class":502,"line":615},[500,11513,11514],{"class":513}," service_description",[500,11516,11517],{"class":520}," HeapSpace",[500,11519,11520],{"class":520}," of",[500,11522,11523],{"class":520}," JavaApp\n",[500,11525,11526,11529],{"class":502,"line":621},[500,11527,11528],{"class":513}," check_command",[500,11530,11531],{"class":520}," check_jmx4perl_heapspace.pl!8084\n",[500,11533,11534],{"class":502,"line":631},[500,11535,802],{"class":506},[1622,11537,11538],{"start":937},[346,11539,11540],{},"lean back while watching the heapspace grow…",[18,11542,11543],{},[22,11544,11548],{"href":11545,"rel":11546,"title":11547},"https://github.com/zivis/Scripts/blob/master/check_jmx4perl_heapspace.pl",[26],"check_jmx4perl_heapspace.pl on github","get the complete Nagios-plugin from github",[1017,11550,11551],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":34,"searchDepth":98,"depth":98,"links":11553},[11554],{"id":11215,"depth":98,"text":11216,"children":11555},[11556,11557,11559,11560],{"id":11219,"depth":249,"text":11220},{"id":11226,"depth":249,"text":11558},"Use Jolokia on the server-side!",{"id":11306,"depth":249,"text":11307},{"id":11325,"depth":249,"text":11326},[11562],"administrator-blog","2013-02-07T00:48:50","https://synyx.de/blog/how-sysadmins-monitor-your-java-application-with-jmx/",{},"/blog/how-sysadmins-monitor-your-java-application-with-jmx",{"title":11194,"description":11204},"blog/how-sysadmins-monitor-your-java-application-with-jmx",[1039,869,11570,11571,9116,11572,11573,11355,9117],"jboss","jmx","nagios","open-source","some time ago Aljona showed how to monitor and manage your java application with jmx I’m going to show, how you can make use of JMX from the viewpoint of…","MFqu5fP8lFxCs1VVecsJZpCxYTd8MOKoaLxMErncmaw",{"id":11577,"title":11578,"author":11579,"body":11580,"category":11965,"date":11966,"description":11967,"extension":104,"link":11968,"meta":11969,"navigation":107,"path":11970,"seo":11971,"slug":11584,"stem":11973,"tags":11974,"teaser":11978,"__hash__":11979},"blog/blog/properly-calculating-time-differences-in-javascript.md","Properly calculating time differences in JavaScript",[9124],{"type":11,"value":11581,"toc":11963},[11582,11585,11592,11613,11642,11645,11680,11683,11697,11705,11744,11747,11767,11778,11787,11806,11821,11882,11888,11925,11938,11950,11961],[14,11583,11578],{"id":11584},"properly-calculating-time-differences-in-javascript",[18,11586,11587,11588,11591],{},"Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the ",[168,11589,11590],{},"Date","object and do\nsome calculating.",[18,11593,11594,11595,11598,11599,11602,11603,11605,11606,11609,11610,11612],{},"As a JavaScript veteran you know that you have to use ",[405,11596,11597],{},"new Date()"," instead of ",[405,11600,11601],{},"Date()"," because the second one returns a\nstring for some reason, you recall that the month of October is identified by the number ",[405,11604,6952],{}," because we start counting\nthe months starting at ",[405,11607,11608],{},"0"," and quickly figure out that subtracting two ",[405,11611,11590],{}," objects results in a number which is the\namount of milliseconds passed between two moments.",[492,11614,11616],{"className":3047,"code":11615,"language":3049,"meta":34,"style":34},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = new Date(2012, 9, 27);\nvar d2 = new Date(2012, 9, 28);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\n\n",[405,11617,11618,11622,11627,11632,11637],{"__ignoreMap":34},[500,11619,11620],{"class":502,"line":503},[500,11621,644],{"emptyLinePlaceholder":107},[500,11623,11624],{"class":502,"line":98},[500,11625,11626],{},"var DAY_IN_MS = 24 * 60 * 60 * 1000;\n",[500,11628,11629],{"class":502,"line":249},[500,11630,11631],{},"var d1 = new Date(2012, 9, 27);\n",[500,11633,11634],{"class":502,"line":583},[500,11635,11636],{},"var d2 = new Date(2012, 9, 28);\n",[500,11638,11639],{"class":502,"line":615},[500,11640,11641],{},"console.log((d2 - d1) / DAY_IN_MS); // yields 1\n",[18,11643,11644],{},"Looks fine, doesn’t it? So just wrap it in a function, unit-test it and be done with it? Not so fast there. Let’s just\nchange the dates ever so slightly",[492,11646,11648],{"className":3047,"code":11647,"language":3049,"meta":34,"style":34},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = new Date(2012, 9, 27);\nvar d2 = new Date(2012, 9, 28);\nvar d3 = new Date(2012, 9, 29);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\nconsole.log((d3 - d2) / DAY_IN_MS); // yields 1.0416666666666667\n\n",[405,11649,11650,11654,11658,11662,11666,11671,11675],{"__ignoreMap":34},[500,11651,11652],{"class":502,"line":503},[500,11653,644],{"emptyLinePlaceholder":107},[500,11655,11656],{"class":502,"line":98},[500,11657,11626],{},[500,11659,11660],{"class":502,"line":249},[500,11661,11631],{},[500,11663,11664],{"class":502,"line":583},[500,11665,11636],{},[500,11667,11668],{"class":502,"line":615},[500,11669,11670],{},"var d3 = new Date(2012, 9, 29);\n",[500,11672,11673],{"class":502,"line":621},[500,11674,11641],{},[500,11676,11677],{"class":502,"line":631},[500,11678,11679],{},"console.log((d3 - d2) / DAY_IN_MS); // yields 1.0416666666666667\n",[18,11681,11682],{},"This is the point where most developers start cursing. Is this a new way in which JavaScript is broken? It isn’t,\nbecause the number is completely accurate.",[18,11684,11685,11686,11689,11690,719,11693,11696],{},"The JavaScript object created by ",[405,11687,11688],{},"new Date(2012, 9, 28)"," represents midnight on the 28th of October, 2012 ",[168,11691,11692],{},"in your local\ntime zone",[405,11694,11695],{},"new Date(2012, 9, 29)"," represents midnight the following day.",[18,11698,11699,11700,707],{},"Subtracting the first from the seconds yields the number of milliseconds that have passed between those two moments,\nwhich, as you probably have guessed, includes the extra hour put in because\nof ",[22,11701,11704],{"href":11702,"rel":11703},"http://www.timeanddate.com/worldclock/clockchange.html?n=37",[26],"daylight savings time",[492,11706,11708],{"className":3047,"code":11707,"language":3049,"meta":34,"style":34},"\n> new Date(2012, 9, 29);\nMon Oct 29 2012 00:00:00 GMT+0100 (CET)\n> new Date(2012, 9, 28);\nSun Oct 28 2012 00:00:00 GMT+0200 (CEST)\n> (new Date(2012, 9, 29) - new Date(2012, 9, 28)) / 60 / 60 / 100\n25\n\n",[405,11709,11710,11714,11719,11724,11729,11734,11739],{"__ignoreMap":34},[500,11711,11712],{"class":502,"line":503},[500,11713,644],{"emptyLinePlaceholder":107},[500,11715,11716],{"class":502,"line":98},[500,11717,11718],{},"> new Date(2012, 9, 29);\n",[500,11720,11721],{"class":502,"line":249},[500,11722,11723],{},"Mon Oct 29 2012 00:00:00 GMT+0100 (CET)\n",[500,11725,11726],{"class":502,"line":583},[500,11727,11728],{},"> new Date(2012, 9, 28);\n",[500,11730,11731],{"class":502,"line":615},[500,11732,11733],{},"Sun Oct 28 2012 00:00:00 GMT+0200 (CEST)\n",[500,11735,11736],{"class":502,"line":621},[500,11737,11738],{},"> (new Date(2012, 9, 29) - new Date(2012, 9, 28)) / 60 / 60 / 100\n",[500,11740,11741],{"class":502,"line":631},[500,11742,11743],{},"25\n",[18,11745,11746],{},"So where is the error? The error is in our assumption that a day has 24 hours, because depending on how you define a\nday, it hasn’t – October 28th 2012 has 25 hours.",[18,11748,11749,11750,725,11753,11757,11758,180,11762,11766],{},"If you Google “JavaScript time difference”, most people just use\n",[405,11751,11752],{},"Math.round",[22,11754,4080],{"href":11755,"rel":11756},"http://psoug.org/snippet/Javascript-Calculate-time-difference-between-two-dates_116.htm",[26],") or simply\nuse flat-out buggy\ncode (",[22,11759,4080],{"href":11760,"rel":11761},"https://web.archive.org/web/20151119010127/http://www-10.lotus.com:80/ldd/ddwiki.nsf/dx/Various_Time_Differences_in_JavaScript",[26],[22,11763,6758],{"href":11764,"rel":11765},"http://www.javascriptkit.com/javatutors/datedifference.shtml",[26],")\nand call it a day (pun intended), but that is not how we roll here.",[18,11768,11769,11770,11773,11774,11777],{},"What do we really mean when we ask ",[168,11771,11772],{},"“How many days have passed between two dates in the calendar”","? We usually mean\n",[168,11775,11776],{},"“How many midnights have happened between these two dates?”",". Unfortunately, because of DST, you can’t just use the\nnumber of milliseconds between two dates at midnight to calculate how many midnights have happened, because some of them\nare more or less than 24 hours apart. If only there was a magical place that doesn’t have this madness going on…",[18,11779,11780,11781,11786],{},"Luckily, there is, and that place is ",[22,11782,11785],{"href":11783,"rel":11784},"http://en.wikipedia.org/wiki/Coordinated_Universal_Time",[26],"UTC",". UTC is a time\nmeasuring system that does not have daylight savings time.",[18,11788,11789,180,11792],{},[469,11790,11791],{},"Edit:",[168,11793,11794,11795,11800,11801,707],{},"as pointed out in the comments, the rabbit hole goes down even further – officially, even in UTC, a day might\nhave more than 24 hours because of leap seconds. Fortunately for us, the ECMA-262\nspecification ",[22,11796,11799],{"href":11797,"rel":11798},"http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1",[26],"explicitly ignores leap seconds"," and we can\ngo about our business. If JavaScript would implement UTC correctly, we would have to account for leap seconds or\nuse ",[22,11802,11805],{"href":11803,"rel":11804},"http://en.wikipedia.org/wiki/Universal_Time#Versions",[26],"UT1",[18,11807,11808,11809,11811,11812,11814,11815,180,11817,11820],{},"The JavaScript Date API is just as beautiful as most other JavaScript APIs: While the only useful use of the ",[405,11810,11590],{},"\nobject is by using it as a constructor (with ",[405,11813,7603],{},"), the way to use UTC is by using the ",[168,11816,3556],{},[405,11818,11819],{},"Date.UTC"," which\nreturns a unix timestamp. This is the JavaScript time API in a nutshell:",[492,11822,11824],{"className":3047,"code":11823,"language":3049,"meta":34,"style":34},"\n> new Date(2012, 9, 29);\nMon Oct 29 2012 00:00:00 GMT+0100 (CET) // (a somewhat useful object)\n> Date(2012, 9, 29);\n'Mon Nov 05 2012 16:18:12 GMT+0100 (CET)' // (a string - no relation to the parameters)\n> Date.UTC(2012, 9, 29);\n1351468800000 // (unix time in milliseconds)\n> new Date.UTC(2012, 9, 29); // failure\nTypeError: function UTC() { [native code] } is not a constructor\n at repl:1:9\n [....]\n\n",[405,11825,11826,11830,11834,11839,11844,11852,11857,11862,11867,11872,11877],{"__ignoreMap":34},[500,11827,11828],{"class":502,"line":503},[500,11829,644],{"emptyLinePlaceholder":107},[500,11831,11832],{"class":502,"line":98},[500,11833,11718],{},[500,11835,11836],{"class":502,"line":249},[500,11837,11838],{},"Mon Oct 29 2012 00:00:00 GMT+0100 (CET) // (a somewhat useful object)\n",[500,11840,11841],{"class":502,"line":583},[500,11842,11843],{},"> Date(2012, 9, 29);\n",[500,11845,11846,11849],{"class":502,"line":615},[500,11847,11848],{},"'Mon Nov 05 2012 16:18:12 GMT+0100 (CET)'",[500,11850,11851],{}," // (a string - no relation to the parameters)\n",[500,11853,11854],{"class":502,"line":621},[500,11855,11856],{},"> Date.UTC(2012, 9, 29);\n",[500,11858,11859],{"class":502,"line":631},[500,11860,11861],{},"1351468800000 // (unix time in milliseconds)\n",[500,11863,11864],{"class":502,"line":641},[500,11865,11866],{},"> new Date.UTC(2012, 9, 29); // failure\n",[500,11868,11869],{"class":502,"line":647},[500,11870,11871],{},"TypeError: function UTC() { [native code] } is not a constructor\n",[500,11873,11874],{"class":502,"line":805},[500,11875,11876],{}," at repl:1:9\n",[500,11878,11879],{"class":502,"line":810},[500,11880,11881],{}," [....]\n",[18,11883,11884,11885,11887],{},"The correct calculation, without using ",[405,11886,11752],{}," or other hacks therefore is",[492,11889,11891],{"className":3047,"code":11890,"language":3049,"meta":34,"style":34},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = Date.UTC(2012, 9, 27);\nvar d2 = Date.UTC(2012, 9, 28);\nvar d3 = Date.UTC(2012, 9, 29);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\nconsole.log((d3 - d2) / DAY_IN_MS); // yields 1\n\n",[405,11892,11893,11897,11901,11906,11911,11916,11920],{"__ignoreMap":34},[500,11894,11895],{"class":502,"line":503},[500,11896,644],{"emptyLinePlaceholder":107},[500,11898,11899],{"class":502,"line":98},[500,11900,11626],{},[500,11902,11903],{"class":502,"line":249},[500,11904,11905],{},"var d1 = Date.UTC(2012, 9, 27);\n",[500,11907,11908],{"class":502,"line":583},[500,11909,11910],{},"var d2 = Date.UTC(2012, 9, 28);\n",[500,11912,11913],{"class":502,"line":615},[500,11914,11915],{},"var d3 = Date.UTC(2012, 9, 29);\n",[500,11917,11918],{"class":502,"line":621},[500,11919,11641],{},[500,11921,11922],{"class":502,"line":631},[500,11923,11924],{},"console.log((d3 - d2) / DAY_IN_MS); // yields 1\n",[18,11926,11927,11928,11931,11932,11937],{},"These kinds of bugs are sneaky because they only show up for certain input values (I wonder if I would have noticed it\nif I hadn’t tested the code last week around the DST change) and usually don’t show up in unit tests unless you happen\nto know what you are looking for. The results are often ",[168,11929,11930],{},"nearly"," correct, and we are not used to thinking about time\nzones and\nwe ",[22,11933,11936],{"href":11934,"rel":11935},"http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time",[26],"often hold invalid assumptions about time",".\nAlways using UTC isn’t a solution either, because sometimes we want the local time zone to be considered.",[18,11939,11940,11941,11946,11947,11949],{},"Libraries like ",[22,11942,11945],{"href":11943,"rel":11944},"http://momentjs.com/",[26],"Moment.js"," help, but the real protection against these kinds of bugs is to know\nabout time zones, time measurement system and thinking about what you are actually calculating instead of simply\nthrowing a ",[405,11948,11752],{}," in there to make it all work.",[18,11951,11952,11953,11956,11957,11960],{},"Just as anybody that has had the pleasure of seeing ",[168,11954,11955],{},"Rent"," will tell you: while a year has ",[168,11958,11959],{},"five hundred twenty-five\nthousand six hundred minutes",", it still is difficult to measure the time of the year.",[1017,11962,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":11964},[],[1029],"2012-11-05T17:05:23","Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the Dateobject and do\\nsome calculating.","https://synyx.de/blog/properly-calculating-time-differences-in-javascript/",{},"/blog/properly-calculating-time-differences-in-javascript",{"title":11578,"description":11972},"Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the Dateobject and do\nsome calculating.","blog/properly-calculating-time-differences-in-javascript",[11975,670,11976,11977],"date","time","utc","Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We want to calculate the difference between two dates, measured in…","ni8qXO9StrZax4AqNEra8EirfvOftQEpRiXb9e9ojTo",{"id":11981,"title":11982,"author":11983,"body":11984,"category":12085,"date":12086,"description":12087,"extension":104,"link":12088,"meta":12089,"navigation":107,"path":12090,"seo":12091,"slug":11988,"stem":12092,"tags":12093,"teaser":12095,"__hash__":12096},"blog/blog/my-take-on-things-java-community-events-vs-java-conferences.md","My take on things – Java Community events vs. Java Conferences",[6392],{"type":11,"value":11985,"toc":12083},[11986,11989,11992,11997,12000,12005,12010,12017,12020,12025,12030,12033,12036,12040,12045,12048,12051,12056,12059,12062,12067,12070,12073,12076,12080],[14,11987,11982],{"id":11988},"my-take-on-things-java-community-events-vs-java-conferences",[18,11990,11991],{},"Heute will ich einmal die Gelegenheit nutzen und die beiden wohl populärsten Konferenzen für Java-Entwicklung innerhalb\nEuropas gegenüberstellen. Dazu muss ich aber fairerweise anmerken, dass ich die Devoxx’2011 komplett besuchte, während\nich auf der JAX’2012 leider nur 2 Tage sein konnte.",[18,11993,11994],{},[469,11995,11996],{},"Organisation:",[18,11998,11999],{},"Hier können beide Konferenzen eindeutig punkten. Die JAX war an den beiden Tagen meines Besuchs hervorragend\norganisiert, es gab kaum Wartezeiten oder aber überfüllte Säle, selbst bei den recht beliebten Keynotes nicht. Beide\nVeranstaltungen profitieren natürlich von den hervorragenden Locations. Dies macht die Organisation deutlich einfacher,\nwas man wirklich an vielen, vielen Kleinigkeiten bemerkt. Die Devoxx liegt hier aber aufgrund der Tatsache das sie in\neinem der größten Kinos in Europa stattfindet noch einen Punkt besser im Rennen. Die Bequemlichkeit der Säle ist einfach\nungeschlagen (Kinosessel der modernsten Art!) und natürlich auch die Sicht. Während man im einen oder anderen Talk auf\nder JAX auf einmal eine Säule oder den Beamer im Weg hatte (ist eben nicht besser machbar) gibt es aufgrund der\nperfekten Location auf der Devoxx solche Probleme nicht.",[18,12001,12002],{},[168,12003,12004],{},"+1 für die Devoxx",[18,12006,12007],{},[469,12008,12009],{},"Verpflegung:",[18,12011,12012,12013,12016],{},"Die Verpflegung ist ja immer so eine Sache auf Konferenzen, die einen brauchen nur Kaffee, die anderen dauernd\nNervenfutter und Süßes für ihre ",[168,12014,12015],{},"Konzentration."," Die Devoxx bietet mit dauernden Kaffeetankstellen als auch dauernd\ngefüllten Kühlschränken recht ordentliche Ausstattung. Auch für Frühstück, Mittagessen und einen Nachmittagssnack ist\ngesorgt. An für sich gibt es hier keine Klagen. Ach doch, bitte, bitte liebe Devoxxianer … sorgt für besseren Kaffee!!!!\nIch bezahle gerne ein wenig mehr 🙂",[18,12018,12019],{},"Das Angebot der Devoxx ist allerdings in keinster Weise mit dem Angebot der JAX zu vergleichen. Auf der JAX gab es\nwährend der 2 Tage alles, was das Herz begehrt. Guten Kaffee, hervorragende Zwischensnacks und ein Buffet zu quasi jeder\nTageszeit, natürlich unterschiedlichst befüllt. Hierfür muss ich großen Respekt zollen. Das war einfach unfassbar gut\nund lecker! Einzig eine Kleinigkeit der Verbesserung fällt mir noch ein. Man könnte Beschildern, was wirklich\nvegetarisch oder aber vegan ist. Das Personal am Buffet konnte aber nahezu jede Frage beantworten und so hatte man auch\nals Vegetarier das gute Gefühl ordentlich zu essen 🙂",[18,12021,12022],{},[168,12023,12024],{},"+1 für die JAX",[18,12026,12027],{},[469,12028,12029],{},"Technische Ausstattung:",[18,12031,12032],{},"Ja die technische Ausstattung ist natürlich so eine Sache. Die Devoxx ist hier klar im Vorteil durch die wirklich\ngeniale Location. Gegen solch einen Sound und eine Sicht und eine Lichtstärke der Beamer und Projektoren ist natürlich\nkein Gras gewachsen.",[18,12034,12035],{},"Vergleichbar sind also letztlich nur die kleineren Dinge wie Mikrofone oder aber die Gadgets. Auf beiden Seiten\narbeiteten aufopferungssvoll die Techniker an einer perfekten Situation. Ein kleiner Pluspunkt für die Devoxx vielleicht\naufgrund der sehr gut angenommenen Twitterwall und den einfach unschlagbar guten Apps für die Mobile Devices. Auf der\nDevoxx ist das WLan auch eindeutig besser, da man in einigen Sälen der JAX schlicht keines hatte (zumindest ging es mir\nund meinen Kollegen so für die Räume im Keller).",[18,12037,12038],{},[168,12039,12004],{},[18,12041,12042],{},[469,12043,12044],{},"Anspruch Sessions, Workshops:",[18,12046,12047],{},"Hier erspare ich mir einen Vergleich, da es einfach kaum zu vergleichen ist. Die Devoxx wird seit 2 Jahren von den\ngroßen Firmen hinter Java als Podest genutzt und ist einfach auch besser international erreichbar, daher sind die\nSpeaker rein namentlich schon kaum zu schlagen (für den der Wert darauf legt). Oracle als auch Google oder Springsource\nhaben regelmäßig ihre TopSpeaker an Bord. Auf der JAX wirken die Talks auch deutlich lösungsorientierter, während es auf\nder Devoxx sehr sehr viel um die reine Entwicklung geht. Hier spürt man meiner Meinung nach einfach auch die Geschichte\nbeider Konferenzen noch sehr. Die Devoxx welche aus der Community entstand, während die JAX ein kommerzielles Event ist.",[18,12049,12050],{},"Daher erspare ich mir auch den Vergleich der Kosten, das macht reichlich wenig Sinn und jeder sollte für sich\nentscheiden, was hier ein gutes Maß ist und was nicht. Eine Kleinigkeit aber, die die Devoxxianer bereits seit einigen\nJahren machen, sollte sich hier die JAX doch noch abschauen: Den Talks ein Level mitgeben, für wen sie geeignet sind (\nbsp. Beginner, Senior, Expert oder vgl..) Ich saß dann doch ein zwei Mal in einem Talk, wo einfach das eigene Wissen\ngrößer war als das des Speakers.",[18,12052,12053],{},[469,12054,12055],{},"Speaker:",[18,12057,12058],{},"Die Speaker sind natürlich eine sehr schwer vergleichbare Sache, alleine schon durch die oben bereits erwähnte Tatsache,\ndass die Devoxx von den großen drei als Bühne genutzt wird, während die JAX hier nicht so populär ist. Allerdings kann\nauch die JAX mit sehr namhaften und erfahrenen Speakern punkten und holt hier aus meiner Sicht Jahr für Jahr auf.",[18,12060,12061],{},"Ich mag hier keinen Punkt für die eine oder andere Seite geben.",[18,12063,12064],{},[469,12065,12066],{},"Umgebung:",[18,12068,12069],{},"Die JAX findet in einer echten Konferenzlocation statt, während die Devoxx, wie bereits erwähnt in einem der größten\nKinokomplexe Europas stattfindet. Das macht sich natürlich auch in der Umgebung bemerkbar, könnte man meinen. Aber: Das\nKino ist wirklich derart am Ende von Antwerpen, dass dies viel eher ein Nachteil ist, denn ein Vorteil. Die\nRheingoldhalle in der die JAX nun seit 2011 stattfindet ist deutlich direkter in der Stadt selbst, was für das Programm\ndanach sehr von Vorteil ist.",[18,12071,12072],{},"Für mich als Besucher aus Süddeutschland ist die JAX natürlich eine Top-Location, da ich sogar pendeln kann, wenn ich\nmag. Die Devoxx schlägt hier direkt noch mit einer 5 Stunden Anfahrt zu, ist allerdings auch per Flieger direkt zu\nerreichen, während man für Mainz wohl am besten über FFM fliegt und dann noch ein wenig die DB genießt. Allerdings kann\nder Vorteil des direkten Flughafens in der Stadt wohl doch bei internationalen Speakern punkten.",[18,12074,12075],{},"Dennoch gebe ich aufgrund meiner persönlichen Situation hier den Punkt an die JAX.",[18,12077,12078],{},[168,12079,12024],{},[18,12081,12082],{},"Zusammenfassend kann man wohl sagen, dass beide Konferenzen ihre Fans haben und sich auch nicht so ähnlich sind, dass\nman nicht beide besuchen könnte. Ich persönlich werde wohl auch dieses Jahr wieder die Devoxx besuchen und dann im\nFrühjahr 2013 wieder die JAX… 😉",{"title":34,"searchDepth":98,"depth":98,"links":12084},[],[256],"2012-04-25T09:44:39","Heute will ich einmal die Gelegenheit nutzen und die beiden wohl populärsten Konferenzen für Java-Entwicklung innerhalb\\nEuropas gegenüberstellen. Dazu muss ich aber fairerweise anmerken, dass ich die Devoxx’2011 komplett besuchte, während\\nich auf der JAX’2012 leider nur 2 Tage sein konnte.","https://synyx.de/blog/my-take-on-things-java-community-events-vs-java-conferences/",{},"/blog/my-take-on-things-java-community-events-vs-java-conferences",{"title":11982,"description":11991},"blog/my-take-on-things-java-community-events-vs-java-conferences",[12094,113,6380,869,268,6733],"community","Heute will ich einmal die Gelegenheit nutzen und die beiden wohl populärsten Konferenzen für Java-Entwicklung innerhalb Europas gegenüberstellen. Dazu muss ich aber fairerweise anmerken, dass ich die Devoxx’2011 komplett besuchte,…","LlCT-W2p4EzOxwXO4tBpBQx5VG80g60BW-CYXAspPV0",{"id":12098,"title":12099,"author":12100,"body":12101,"category":12186,"date":12187,"description":12188,"extension":104,"link":12189,"meta":12190,"navigation":107,"path":12191,"seo":12192,"slug":12105,"stem":12194,"tags":12195,"teaser":12196,"__hash__":12197},"blog/blog/reasons-why-i-go-to-devoxx.md","Reasons why I go to Devoxx",[2944],{"type":11,"value":12102,"toc":12184},[12103,12106,12114,12121,12127,12145,12158,12171],[14,12104,12099],{"id":12105},"reasons-why-i-go-to-devoxx",[18,12107,12108,12109,12113],{},"Yet another year is almost over. One of the reasons I notice this is\nbecause ",[22,12110,179],{"href":12111,"rel":12112},"http://devoxx.com/display/DV11/Home",[26]," is coming up again. And – of course – Synyx is going to be there.\nIn the last year four of Synyx’ employees attended the full conference. This year all of last years visitors are going\nagain, and even three more. So there has to be a reason why it’s so popular. This post is going to be about Devoxx and\nwhy I personally enjoy going there. Well… there are several reasons…",[18,12115,12116,12117,12120],{},"A big challenge in our business is ",[469,12118,12119],{},"staying up to date",". There are plenty of books, articles, tweets and blogs to read\nin order know what is going on in the world of software development. And there are even more things to filter out and\nforget (at least for a while) because they don’t apply to you and your daily work. Sometimes you just don’t have enough\ntime for this because you already have loads of work with current technologies in your day-to-day work.",[18,12122,12123,12126],{},[469,12124,12125],{},"The talks at Devoxx keep me in sync with what is going on"," and what is (probably) important. It has pre-selected\ntalks with relevance to Java or me as a Java developer / architect.",[18,12128,12129,12130,12135,12136,12141,12142],{},"Considering last year’s Devoxx there were many things that came to our knowledge and are in use at Synyx now. Some\naffected the ways we work at Synyx (Hello, ",[22,12131,12134],{"href":12132,"rel":12133},"http://www.nealford.com/",[26],"Neal Ford","), some the tools, libraries and\nframeworks we are using now (e. g. ",[22,12137,12140],{"href":12138,"rel":12139},"http://wicket.apache.org/",[26],"Wicket",") and some simply increased our knowledge and\nbrought us up to date (like the “what’s new in” JPA, Spring or Java talks). ",[469,12143,12144],{},"Go see the stuff you can easily use in\nyour daily work.",[18,12146,12147,12148,12153,12154,12157],{},"In addition to the interesting topics of the talk you just have to look at\nthe ",[22,12149,12152],{"href":12150,"rel":12151},"http://devoxx.com/display/DV11/Devoxxians",[26],"Cast",". There are many well-known ",[469,12155,12156],{},"speakers"," which have great\nexperience with their topics and also know, how to present it to the audience. So it’s almost always a pleasure to\nlisten and learn from them because they are the best.",[18,12159,12160,12161,12164,12165,12170],{},"Another big thing that makes me really looking forward to next week is that I’ll spend a ",[469,12162,12163],{},"nice week in Antwerp with so\nmany of my co-workers and other friends",". I’m looking forward visiting ",[22,12166,12169],{"href":12167,"rel":12168},"http://www.kellys.be/",[26],"Kelly’s Irish Pub","\nagain, which is about 100m down the street from our hotel and has been our conference-table almost every evening/night\nlast year (and probably will be again this year).",[18,12172,12173,12174,12179,12180,12183],{},"And the last thing why I’m going there is optimism: As I requested in\nmy ",[22,12175,12178],{"href":12176,"rel":12177},"http://blog.synyx.de/2010/12/devoxx-2010-revisited/",[26],"last post about the conference"," they made the tickets more\nexpensive this year. So I hope they made this because they followed my suggestion to serve ",[469,12181,12182],{},"better coffee"," this\ntime :). Be sure, Synyx will report about it…",{"title":34,"searchDepth":98,"depth":98,"links":12185},[],[1029],"2011-11-10T09:42:44","Yet another year is almost over. One of the reasons I notice this is\\nbecause Devoxx is coming up again. And – of course – Synyx is going to be there.\\nIn the last year four of Synyx’ employees attended the full conference. This year all of last years visitors are going\\nagain, and even three more. So there has to be a reason why it’s so popular. This post is going to be about Devoxx and\\nwhy I personally enjoy going there. Well… there are several reasons…","https://synyx.de/blog/reasons-why-i-go-to-devoxx/",{},"/blog/reasons-why-i-go-to-devoxx",{"title":12099,"description":12193},"Yet another year is almost over. One of the reasons I notice this is\nbecause Devoxx is coming up again. And – of course – Synyx is going to be there.\nIn the last year four of Synyx’ employees attended the full conference. This year all of last years visitors are going\nagain, and even three more. So there has to be a reason why it’s so popular. This post is going to be about Devoxx and\nwhy I personally enjoy going there. Well… there are several reasons…","blog/reasons-why-i-go-to-devoxx",[113,6379,869],"Yet another year is almost over. One of the reasons I notice this is because Devoxx is coming up again. And – of course – Synyx is going to be…","q1nNnb-qWWPQ3xGDD2js7mhC58DjQdJwUG3qIhu7ND8",{"id":12199,"title":12200,"author":12201,"body":12202,"category":12530,"date":12531,"description":12532,"extension":104,"link":12533,"meta":12534,"navigation":107,"path":12535,"seo":12536,"slug":12206,"stem":12537,"tags":12538,"teaser":12541,"__hash__":12542},"blog/blog/make-software-projects-fit-for-git.md","Make software-projects fit for git",[11196],{"type":11,"value":12203,"toc":12528},[12204,12207,12210,12216,12221,12244,12247,12253,12258,12268,12274,12280,12283,12317,12322,12325,12354,12357,12387,12390,12393,12466,12469,12472,12477,12480,12483,12486,12516,12519,12522,12525],[14,12205,12200],{"id":12206},"make-software-projects-fit-for-git",[18,12208,12209],{},"More and more Projects at our company are taking advantage of distributed and local revision control by using git. So to\nmake a complete software-project fit for git, by not only using git-svn with subversion and git on top, some more\nsteps are required than just handling files with git, learning its syntax and understanding the way it works…",[18,12211,12212],{},[32,12213],{"alt":12214,"src":12215},"\"git-logo\"","https://media.synyx.de/uploads//2011/10/git-logo.png",[18,12217,12218,707],{},[168,12219,12220],{},"Source code has to be accessible",[18,12222,12223,12224,12230,12231,12237,12238,12243],{},"We are used to use subversion, a central repository with the leading stage of developement, when using git – all\nrepositories are equal. To take the best of both worlds we host a git-repository, which is defined to be leading (only\nby convention). We like to have things under control, so we\nuse ",[22,12225,12229],{"href":12226,"rel":12227,"title":12228},"http://eagain.net/gitweb/?p=gitosis.git",[26],"Gitosis download","gitosis"," to serve these repositories, but we think\nabout using ",[22,12232,12236],{"href":12233,"rel":12234,"title":12235},"https://github.com/sitaramc/gitolite/wiki",[26],"gitolite on GitHub","gitolite"," because of better/easier\naccess-management. You can also host at ",[22,12239,12242],{"href":12240,"rel":12241,"title":3899},"https://github.com/",[26],"GitHub",", they do great work and it´s their daily\nbusiness.",[18,12245,12246],{},"What else do we need to develop an amazing piece of software in addition to good code and a working methodology? Which\ntools assist the development process and need capability to handle git-repositories?",[18,12248,12249],{},[32,12250],{"alt":12251,"src":12252},"\"chiliproject-logo\"","https://media.synyx.de/uploads//2011/10/chiliproject-logo.png",[18,12254,12255,707],{},[168,12256,12257],{},"Software should do what it is intended for",[18,12259,12260,12261,12267],{},"To reach this goal we collect production requirements and fragment the subsequent work into processable packets with\na *\n*project-management tool** called redmine, more precisely ",[22,12262,12266],{"href":12263,"rel":12264,"title":12265},"https://www.chiliproject.org/",[26],"ChiliProject","**chiliproject\n**",", an rapidly evolving fork of redmine.",[18,12269,12270],{},[32,12271],{"alt":12272,"src":12273},"\"jenkins_logo\"","https://media.synyx.de/uploads//2011/10/jenkins_logo.png",[18,12275,12276,12279],{},[168,12277,12278],{},"Software is something executable",",",[18,12281,12282],{},"plain source-code is for documentation purposes 😉",[18,12284,12285,12286,12291,12292,12295,12296,12302,12303,12309,12310,12316],{},"We have to build it. Most of our projects are written in Java and built\nwith ",[22,12287,12290],{"href":8353,"rel":12288,"title":12289},[26],"Maven","Apache Maven",". To build the written code automatically and pursue the process of\n",[469,12293,12294],{},"continuous integration"," we utilize a tool named hudson, more precisely ",[22,12297,12301],{"href":12298,"rel":12299,"title":12300},"http://jenkins-ci.org/",[26],"Jenkins","**Jenkins\n**",", an Oracle-independent fork (yeah we like using forks, especially when main\ndevelopers from the origin project are switching to the new fork, if you are interested in all of our reasons read\nthe ",[22,12304,12308],{"href":12305,"rel":12306,"title":12307},"http://blog.synyx.de/2011/05/opensource-is-not-just-about-the-license/",[26],"opensource is not just about the license","blogpost","\nwritten by ",[22,12311,12315],{"href":12312,"rel":12313,"title":12314},"http://blog.synyx.de/autoren/?uid=14",[26],"Fabian Buch","Fabian",")",[18,12318,12319],{},[469,12320,12321],{},"So first mission is to make ChiliProject fit for git.",[18,12323,12324],{},"Luckily ChiliProject can handle git-repositories out of the box, but the repositories have to be cloned to localhost(\nthe machine running chiliproject). This can be achieved by giving read-rights to the user running chiliproject, in our\ncase this is generating a passwordless ssh-keypair, deploy the public part of it to gitosis and explicitly give rights\nto this public-key. To use the generated private-key every time we use git (in conjunction with ssh) we have to modify\nthe file ~/.ssh/config:",[492,12326,12328],{"className":10551,"code":12327,"language":10553,"meta":34,"style":34},"Host git.domain.tld\nUser git\nIdentityFile ~/.ssh/git_key.priv\n",[405,12329,12330,12338,12346],{"__ignoreMap":34},[500,12331,12332,12335],{"class":502,"line":503},[500,12333,12334],{"class":513},"Host",[500,12336,12337],{"class":520}," git.domain.tld\n",[500,12339,12340,12343],{"class":502,"line":98},[500,12341,12342],{"class":513},"User",[500,12344,12345],{"class":520}," git\n",[500,12347,12348,12351],{"class":502,"line":249},[500,12349,12350],{"class":513},"IdentityFile",[500,12352,12353],{"class":520}," ~/.ssh/git_key.priv\n",[18,12355,12356],{},"Now we need to clone the repository manually by login to the machine running Chili and do:",[492,12358,12360],{"className":10551,"code":12359,"language":10553,"meta":34,"style":34},"mkdir /path/to/git/repositories/\ncd /path/to/git/repositories/\ngit clone git@gitosis.domain.tld:gitrepo.git\n",[405,12361,12362,12370,12377],{"__ignoreMap":34},[500,12363,12364,12367],{"class":502,"line":503},[500,12365,12366],{"class":513},"mkdir",[500,12368,12369],{"class":520}," /path/to/git/repositories/\n",[500,12371,12372,12375],{"class":502,"line":98},[500,12373,12374],{"class":703},"cd",[500,12376,12369],{"class":520},[500,12378,12379,12382,12384],{"class":502,"line":249},[500,12380,12381],{"class":513},"git",[500,12383,10892],{"class":520},[500,12385,12386],{"class":520}," git@gitosis.domain.tld:gitrepo.git\n",[18,12388,12389],{},"for sure git has to be installed on the server running Chili. And the repository already exists…",[18,12391,12392],{},"but how do we keep this cloned repository up to date? We solved this by installing a cronjob running as the user,\nrunning Chili, every 5 minutes:",[492,12394,12396],{"className":10551,"code":12395,"language":10553,"meta":34,"style":34},"*/5 * * * * for i in /path/to/git/repositories/*/; do cd $i && git fetch; git reset refs/remotes/origin/master; done >>/dev/null 2>&1\n",[405,12397,12398],{"__ignoreMap":34},[500,12399,12400,12403,12406,12408,12411,12413,12415,12418,12421,12424,12427,12430,12433,12435,12438,12440,12443,12445,12447,12450,12453,12455,12457,12460,12463],{"class":502,"line":503},[500,12401,12402],{"class":677},"*",[500,12404,12405],{"class":506},"/5 ",[500,12407,12402],{"class":677},[500,12409,12410],{"class":677}," *",[500,12412,12410],{"class":677},[500,12414,12410],{"class":677},[500,12416,12417],{"class":677}," for",[500,12419,12420],{"class":506}," i ",[500,12422,12423],{"class":677},"in",[500,12425,12426],{"class":520}," /path/to/git/repositories/*/",[500,12428,12429],{"class":506},"; ",[500,12431,12432],{"class":677},"do",[500,12434,10996],{"class":703},[500,12436,12437],{"class":506}," $i && ",[500,12439,12381],{"class":513},[500,12441,12442],{"class":520}," fetch",[500,12444,12429],{"class":506},[500,12446,12381],{"class":513},[500,12448,12449],{"class":520}," reset",[500,12451,12452],{"class":520}," refs/remotes/origin/master",[500,12454,12429],{"class":506},[500,12456,3387],{"class":677},[500,12458,12459],{"class":677}," >>",[500,12461,12462],{"class":506},"/dev/null ",[500,12464,12465],{"class":677},"2>&1\n",[18,12467,12468],{},"jap, you can log into a file instead of /dev/null, but we trust… 🙂",[18,12470,12471],{},"that´s it you can now add the local repository to your project in ChiliProject, but give full path including the\n“.git”-folder, it is little fussy in this point.",[18,12473,12474],{},[469,12475,12476],{},"Get Jenkins to work with git",[18,12478,12479],{},"First, we have to do the same things done for ChiliProject, install the git-binary on the system running Jenkins,\ngenerate ssh-keypair, give read-rights to the user(possible stumbling block: we are running jenkins in a\njava-servlet-container so it´s the user running this container!)",[18,12481,12482],{},"modify ~/.ssh/config. Now we should be able to manually clone the targeted repositories, but that´s not what we want (\nremember automatically and continous integration?)",[18,12484,12485],{},"In order to be able to tag the built release version, the user running jenkins needs to give an author to the\ngit-repository, so modify/create ~/.gitconfig of this user:",[492,12487,12489],{"className":10551,"code":12488,"language":10553,"meta":34,"style":34},"[user]\n name = \"Jenkins\"\n email = \"jenkins@jenkins.domain.tld\"\n",[405,12490,12491,12496,12506],{"__ignoreMap":34},[500,12492,12493],{"class":502,"line":503},[500,12494,12495],{"class":506},"[user]\n",[500,12497,12498,12501,12503],{"class":502,"line":98},[500,12499,12500],{"class":513}," name",[500,12502,3783],{"class":520},[500,12504,12505],{"class":520}," \"Jenkins\"\n",[500,12507,12508,12511,12513],{"class":502,"line":249},[500,12509,12510],{"class":513}," email",[500,12512,3783],{"class":520},[500,12514,12515],{"class":520}," \"jenkins@jenkins.domain.tld\"\n",[18,12517,12518],{},"Jenkins is not able to handle git by default, we have to install a plugin: login -> Jenkins -> manage Jenkins ->\nmanage plugins -> available -> Git plugin (that´s easy to remember)",[18,12520,12521],{},"After restarting Jenkins you´ll find, under “projectX”/configure -> Source Code Management, the new section git where\nyou can insert the url of your repository -> save",[18,12523,12524],{},"Finally you can build the project(small prayer could help 😉 ) and enjoy the built software and Jenkins´ expandable\nworkflow…",[1017,12526,12527],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":34,"searchDepth":98,"depth":98,"links":12529},[],[11562,1029,6722],"2011-10-25T12:34:45","More and more Projects at our company are taking advantage of distributed and local revision control by using git. So to\\nmake a complete software-project fit for git, by not only using git-svn with subversion and git on top, some more\\nsteps are required than just handling files with git, learning its syntax and understanding the way it works…","https://synyx.de/blog/make-software-projects-fit-for-git/",{},"/blog/make-software-projects-fit-for-git",{"title":12200,"description":12209},"blog/make-software-projects-fit-for-git",[12539,869,2534,11573,12540,6733],"apache","project-management","More and more Projects at our company are taking advantage of distributed and local revision control by using git. So to make a complete software-project fit for git, by not…","vCNC4CoaSUI5ng_rt7Nu-7xypFBFlUaDbA6FV5gZLNA",{"id":12544,"title":12545,"author":12546,"body":12547,"category":12575,"date":12576,"description":12560,"extension":104,"link":12577,"meta":12578,"navigation":107,"path":12579,"seo":12580,"slug":12551,"stem":12581,"tags":12582,"teaser":12584,"__hash__":12585},"blog/blog/endlich-mal-mit-profis-arbeiten.md","Endlich mal mit Profis arbeiten?",[5368],{"type":11,"value":12548,"toc":12573},[12549,12552,12561,12564],[14,12550,12545],{"id":12551},"endlich-mal-mit-profis-arbeiten",[18,12553,12554,12558],{},[32,12555],{"alt":12556,"src":12557},"\" Java Entwickler wanted\"","https://media.synyx.de/uploads//2011/07/java_entwickler_wanted.jpg",[469,12559,12560],{},"Wir suchen ab sofort Verstärkung für unser Individualsoftware-Team!",[18,12562,12563],{},"Interessante Projekte, nette Arbeitsatmosphäre und alles, was man sonst so braucht.",[18,12565,12566,12567],{},"Schau mal rein, egal ob Du zum reinen Entwickler, zum Softwarearchitekten oder zum Kommunikationsgenie\ntendierst. ",[22,12568,12572],{"href":12569,"rel":12570,"title":12571},"https://jobs.synyx.de/",[26],"Java Entwickler wanted!","Mehr Infos",{"title":34,"searchDepth":98,"depth":98,"links":12574},[],[1029],"2011-07-06T10:15:59","https://synyx.de/blog/endlich-mal-mit-profis-arbeiten/",{},"/blog/endlich-mal-mit-profis-arbeiten",{"title":12545,"description":12560},"blog/endlich-mal-mit-profis-arbeiten",[1259,869,12583,6488],"job","Wir suchen ab sofort Verstärkung für unser Individualsoftware-Team! Interessante Projekte, nette Arbeitsatmosphäre und alles, was man sonst so braucht. Schau mal rein, egal ob Du zum reinen Entwickler, zum Softwarearchitekten…","OU-Bssp8svksPjHhMZcY_bdkyP4iXjbnMH0ig1Zq5jI",{"id":12587,"title":12588,"author":12589,"body":12591,"category":14477,"date":14478,"description":14479,"extension":104,"link":14480,"meta":14481,"navigation":107,"path":14482,"seo":14483,"slug":12595,"stem":14485,"tags":14486,"teaser":14491,"__hash__":14492},"blog/blog/the-tale-of-jboss-and-the-7-little-logging-frameworks.md","The Tale of JBoss and the 7 Little Logging Frameworks",[12590],"speaker-schalanda",{"type":11,"value":12592,"toc":14473},[12593,12596,12605,12611,12614,12629,12648,12668,12677,12686,12695,12700,12703,12712,12780,12789,12849,12864,14250,14253,14292,14307,14325,14328,14378,14382,14439,14446,14450,14470],[14,12594,12588],{"id":12595},"the-tale-of-jboss-and-the-7-little-logging-frameworks",[18,12597,12598,12599,12604],{},"At Synyx we’re currently taking care of a rather large legacy project for one of our customers in the course of\nour ",[22,12600,12603],{"href":12601,"rel":12602},"https://synyx.de/code-clinic-softwareoptimierung/",[26],"Code Clinic"," services. The project comprises several components\nsuch as a fat client implemented with a custom UI framework on top of Swing, a bulky web application using a mixture of\ncustom and obsolete frameworks, and a lot of asynchronously running jobs to process input from other systems involving\ncustom XSL transformations and a heap of stored procedures in a Oracle 9i database. You get the picture, it’s the\nprototype of a legacy system.",[18,12606,12607],{},[32,12608],{"alt":12609,"src":12610},"\"7 Little Logging Frameworks\"","https://media.synyx.de/uploads//2011/06/7dwarves.jpg",[18,12612,12613],{},"7 Little Logging Frameworks on their way into your code base",[18,12615,12616,12617,12622,12623,12628],{},"The original developers of the system suffered a serious case of the\nwell-known ",[22,12618,12621],{"href":12619,"rel":12620},"http://en.wikipedia.org/wiki/Not_Invented_Here",[26],"NIH syndrome"," and thus a lot\nof ",[22,12624,12627],{"href":12625,"rel":12626},"http://www.martinfowler.com/bliki/TechnicalDebt.html",[26],"technical debt"," has been piled up over the course of its\ndevelopment.",[18,12630,12631,12632,719,12637,12642,12643,707],{},"One peculiar case was the use of about 7 different logging abstractions scattered over the whole code base. While some (\nwell, just one) of the implementations provide a certain added value, the other ones were just plain wrappers around the\neventually used logging frameworks. They literally just added a bad API on top of the other. There were also at least\nthree different logging frameworks in use,\nnamely ",[22,12633,12636],{"href":12634,"rel":12635},"http://commons.apache.org/logging/",[26],"Apache Commons Logging",[22,12638,12641],{"href":12639,"rel":12640},"http://www.slf4j.org/",[26],"SLF4J","\nand ",[22,12644,12647],{"href":12645,"rel":12646},"http://logging.apache.org/log4j/",[26],"Log4j",[18,12649,12650,12651,12642,12656,12661,12662,12667],{},"In order to consolidate the code base, to reduce the dependencies on external frameworks and to prevent conflicts\ninduced by the use of generic class names like Log or Logger we decided to clean up this mess and use SLF4J with its\nLog4j back end as our authoritative logging framework in this project. We chose SLF4J for several (hopefully good)\nreasons, e. g. the low number of dependencies, the ability to plug in the logging framework of choice at deployment\ntime, the clean API and the good support for legacy logging frameworks. Also read the\narticles ",[22,12652,12655],{"href":12653,"rel":12654},"http://bsnyderblog.blogspot.com/2007/08/my-soapbox-for-slf4j.html",[26],"My Soapbox for SLF4J",[22,12657,12660],{"href":12658,"rel":12659},"http://blog.frankel.ch/thoughts-on-java-logging-and-slf4j",[26],"Thoughts on Java logging and SLF4J"," for a more detailed\ndiscussion of SLF4J’s features. Did I mention SLF4J also ",[22,12663,12666],{"href":12664,"rel":12665},"http://www.slf4j.org/android/",[26],"works on Android","?",[18,12669,12670,12671,12676],{},"SLF4J thankfully makes it easy to ",[22,12672,12675],{"href":12673,"rel":12674},"http://www.slf4j.org/legacy.html",[26],"bridge legacy logging frameworks"," so that you don’t\nhave to refactor all your code at once but could incrementally migrate your code base while still using SLF4J under the\nhood.",[18,12678,12679,12680,12685],{},"As you might have already guessed from the title of this blog post, the project uses the\nestablished ",[22,12681,12684],{"href":12682,"rel":12683},"http://www.jboss.org/",[26],"JBoss"," Application Server. Unfortunately it’s currently stuck at version\n4.0.3.SP1 but that’s another topic.",[18,12687,12688,12689,12694],{},"After deciding on which framework to use we started refactoring our code base. At first this wasn’t a problem. Deleting\nthe legacy logging classes and then replacing their calls with our authoritative\nSLF4J ",[22,12690,12693],{"href":12691,"rel":12692},"http://www.slf4j.org/apidocs/org/slf4j/Logger.html",[26],"Logger"," was straight forward. Since the logging classes were\nscattered across the whole code base we sometimes missed a dependency in another component of the project, but that’s\nwhat a Continuous Integration system is for after all.",[18,12696,12697],{},[32,12698],{"alt":12699,"src":12639},"\"SLF4J Logo\"",[18,12701,12702],{},"Simple Logging Facade for Java",[18,12704,12705,12706,12711],{},"We also adapted the dependencies in the components’ POM files to use SLF4J’s commons-logging bridge instead of\ncommons-logging itself. Pro tip from the trenches: Don’t confuse jcl-over-slf4j.jar with slf4j-jcl.jar! The former\nis the API bridge we wanted to use, the latter is a SLF4J backend implementation using commons-logging. In order to get\nthe dependencies straight we used\nMaven’s ",[22,12707,12710],{"href":12708,"rel":12709},"http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Management",[26],"dependency management","\nfeature and added the following declarations in the parent POM for the project’s components:",[492,12713,12715],{"className":1751,"code":12714,"language":1753,"meta":34,"style":34},"\u003CdependencyManagement>\n \u003Cdependency>\n \u003CgroupId>commons-logging\u003C/groupId>\n \u003CartifactId>commons-logging\u003C/artifactId>\n \u003Cversion>1.1.1\u003C/version>\n \u003Cscope>provided\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.slf4j\u003C/groupId>\n \u003CartifactId>jcl-over-slf4j\u003C/artifactId>\n \u003Cversion>1.6.1\u003C/version>\n \u003C/dependency>\n\u003C/dependencyManagement>\n",[405,12716,12717,12722,12727,12732,12737,12742,12747,12752,12756,12761,12766,12771,12775],{"__ignoreMap":34},[500,12718,12719],{"class":502,"line":503},[500,12720,12721],{},"\u003CdependencyManagement>\n",[500,12723,12724],{"class":502,"line":98},[500,12725,12726],{}," \u003Cdependency>\n",[500,12728,12729],{"class":502,"line":249},[500,12730,12731],{}," \u003CgroupId>commons-logging\u003C/groupId>\n",[500,12733,12734],{"class":502,"line":583},[500,12735,12736],{}," \u003CartifactId>commons-logging\u003C/artifactId>\n",[500,12738,12739],{"class":502,"line":615},[500,12740,12741],{}," \u003Cversion>1.1.1\u003C/version>\n",[500,12743,12744],{"class":502,"line":621},[500,12745,12746],{}," \u003Cscope>provided\u003C/scope>\n",[500,12748,12749],{"class":502,"line":631},[500,12750,12751],{}," \u003C/dependency>\n",[500,12753,12754],{"class":502,"line":641},[500,12755,12726],{},[500,12757,12758],{"class":502,"line":647},[500,12759,12760],{}," \u003CgroupId>org.slf4j\u003C/groupId>\n",[500,12762,12763],{"class":502,"line":805},[500,12764,12765],{}," \u003CartifactId>jcl-over-slf4j\u003C/artifactId>\n",[500,12767,12768],{"class":502,"line":810},[500,12769,12770],{}," \u003Cversion>1.6.1\u003C/version>\n",[500,12772,12773],{"class":502,"line":826},[500,12774,12751],{},[500,12776,12777],{"class":502,"line":838},[500,12778,12779],{},"\u003C/dependencyManagement>\n",[18,12781,12782,12783,12788],{},"By setting\nthe ",[22,12784,12787],{"href":12785,"rel":12786},"http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope",[26],"scope"," of\ncommons-logging to “provided” we made sure that it would not be pulled in accidentally. Unfortunately this scope is not\ntransitive and we had to explicitly exclude commons-logging (and other artifacts) from some of our dependencies:",[492,12790,12792],{"className":1751,"code":12791,"language":1753,"meta":34,"style":34},"\u003Cdependency>\n \u003CgroupId>org.apache.ws.commons.axiom\u003C/groupId>\n \u003CartifactId>axiom-api\u003C/artifactId>\n \u003Cversion>1.2\u003C/version>\n \u003Cexclusions>\n \u003Cexclusion>\n \u003CartifactId>commons-logging\u003C/artifactId>\n \u003CgroupId>commons-logging\u003C/groupId>\n \u003C/exclusion>\n \u003C/exclusions>\n\u003C/dependency>\n",[405,12793,12794,12799,12804,12809,12814,12819,12824,12829,12834,12839,12844],{"__ignoreMap":34},[500,12795,12796],{"class":502,"line":503},[500,12797,12798],{},"\u003Cdependency>\n",[500,12800,12801],{"class":502,"line":98},[500,12802,12803],{}," \u003CgroupId>org.apache.ws.commons.axiom\u003C/groupId>\n",[500,12805,12806],{"class":502,"line":249},[500,12807,12808],{}," \u003CartifactId>axiom-api\u003C/artifactId>\n",[500,12810,12811],{"class":502,"line":583},[500,12812,12813],{}," \u003Cversion>1.2\u003C/version>\n",[500,12815,12816],{"class":502,"line":615},[500,12817,12818],{}," \u003Cexclusions>\n",[500,12820,12821],{"class":502,"line":621},[500,12822,12823],{}," \u003Cexclusion>\n",[500,12825,12826],{"class":502,"line":631},[500,12827,12828],{}," \u003CartifactId>commons-logging\u003C/artifactId>\n",[500,12830,12831],{"class":502,"line":641},[500,12832,12833],{}," \u003CgroupId>commons-logging\u003C/groupId>\n",[500,12835,12836],{"class":502,"line":647},[500,12837,12838],{}," \u003C/exclusion>\n",[500,12840,12841],{"class":502,"line":805},[500,12842,12843],{}," \u003C/exclusions>\n",[500,12845,12846],{"class":502,"line":810},[500,12847,12848],{},"\u003C/dependency>\n",[18,12850,12851,12852,12857,12858,12863],{},"In order to analyze the dependency trees of our components\nthe ",[22,12853,12856],{"href":12854,"rel":12855},"http://maven.apache.org/plugins/maven-dependency-plugin/",[26],"Maven dependency plugin"," turned out to be very useful,\nespecially the ",[22,12859,12862],{"href":12860,"rel":12861},"http://maven.apache.org/plugins/maven-dependency-plugin/tree-mojo.html",[26],"dependency:tree"," mojo which will\nproduce a view of the project’s dependency tree including transitive dependencies. Of course a good IDE will also\nsupport you with graphical views of the dependency tree. Here’s the output of mvn dependency:tree for the aforementioned\nSwing application:",[492,12865,12867],{"className":11281,"code":12866,"language":11283,"meta":34,"style":34},"[INFO] [dependency:tree {execution: default-cli}]\n[INFO] com.example:swing-client:jar:1.1.0-SNAPSHOT\n[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:compile\n[INFO] | \\- log4j:log4j:jar:1.2.16:compile\n[INFO] +- jgoodies:plastic:jar:1.2.0:compile\n[INFO] +- com.toedter:jcalendar:jar:1.3.2:compile\n[INFO] +- com.example:jms:jar:1.0.1-SNAPSHOT:compile\n[INFO] | +- jboss:jboss-system:jar:4.0.2:compile\n[INFO] | +- jms:jms:jar:1.0.2:compile\n[INFO] | +- org.quartz-scheduler:quartz:jar:1.2.3:compile\n[INFO] | +- jboss:jbossmq-client:jar:4.0.2:compile\n[INFO] | +- jboss:jboss-jmx:jar:4.0.2:compile\n[INFO] | \\- javax.ejb:ejb:jar:2.1:compile\n[INFO] +- flex:flex-messaging-common:jar:1.0.0:compile\n[INFO] +- flex:flex-messaging-core:jar:1.0.0:compile\n[INFO] +- flex:flex-messaging-opt:jar:1.0.0:compile\n[INFO] +- flex:flex-messaging-proxy:jar:1.0.0:compile\n[INFO] +- flex:flex-messaging-remoting:jar:1.0.0:compile\n[INFO] +- poi:poi-2.5.1-final:jar:20040804:compile\n[INFO] +- com.example:docengine:jar:1.0.0-SNAPSHOT:compile\n[INFO] | +- com.example:webfw:jar:1.0.2-SNAPSHOT:compile\n[INFO] | | +- com.example:dms_client:jar:1.0.2:compile\n[INFO] | | | \\- org.apache.axis2:axis2:jar:1.1.1:compile\n[INFO] | | | +- org.apache.ws.commons.axiom:axiom-dom:jar:1.2:compile\n[INFO] | | | +- org.apache.ws.commons.axiom:axiom-impl:jar:1.2:compile\n[INFO] | | | +- ant:ant:jar:1.6.5:compile\n[INFO] | | | +- woodstox:wstx-asl:jar:3.0.1:compile\n[INFO] | | | +- org.apache.ws.commons.schema:XmlSchema:jar:1.2:compile\n[INFO] | | | +- annogen:annogen:jar:0.1.0:compile\n[INFO] | | | +- commons-httpclient:commons-httpclient:jar:3.0.1:compile\n[INFO] | | | | \\- commons-codec:commons-codec:jar:1.2:compile\n[INFO] | | | +- org.apache.httpcomponents:jakarta-httpcore:jar:4.0-alpha2:compile\n[INFO] | | | +- wsdl4j:wsdl4j:jar:1.6.1:compile\n[INFO] | | | +- backport-util-concurrent:backport-util-concurrent:jar:2.2:compile\n[INFO] | | | +- org.apache.ws.commons.neethi:neethi:jar:2.0:compile\n[INFO] | | | \\- org.apache.woden:woden-impl-om:jar:1.0M8:compile\n[INFO] | | | +- org.apache.woden:woden-api:jar:1.0M8:compile\n[INFO] | | | +- org.apache.ant:ant:jar:1.7.0:compile\n[INFO] | | | | \\- org.apache.ant:ant-launcher:jar:1.7.0:compile\n[INFO] | | | +- xerces:xmlParserAPIs:jar:2.6.0:compile\n[INFO] | | | \\- org.codehaus.woodstox:wstx-asl:jar:3.2.4:runtime\n[INFO] | | +- org.apache.ws.commons.axiom:axiom-api:jar:1.2:compile\n[INFO] | | | +- jaxen:jaxen:jar:1.1-beta-9:compile\n[INFO] | | | +- xml-apis:xml-apis:jar:1.3.03:compile\n[INFO] | | | \\- stax:stax-api:jar:1.0.1:compile\n[INFO] | | +- org.slf4j:jcl-over-slf4j:jar:1.6.1:compile\n[INFO] | | +- org.apache.struts:struts-core:jar:1.3.8:compile\n[INFO] | | | \\- commons-chain:commons-chain:jar:1.1:compile\n[INFO] | | +- com.sun.xml:xml:jar:0.8.0:compile\n[INFO] | | +- xmlc:xmlc-all-runtime:jar:2.2.8.1:compile\n[INFO] | | +- xalan:xalan:jar:2.7.1:compile\n[INFO] | | | \\- xalan:serializer:jar:2.7.1:compile\n[INFO] | | \\- com.lowagie:itext:jar:2.0.7:compile\n[INFO] | | +- bouncycastle:bcmail-jdk14:jar:138:compile\n[INFO] | | \\- bouncycastle:bcprov-jdk14:jar:138:compile\n[INFO] | +- org.apache.xmlgraphics:fop:jar:0.95-1:compile\n[INFO] | | +- org.apache.xmlgraphics:xmlgraphics-commons:jar:1.3.1:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-svg-dom:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-anim:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-css:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-dom:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-parser:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-util:jar:1.7:compile\n[INFO] | | | \\- xml-apis:xml-apis-ext:jar:1.3.04:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-bridge:jar:1.7:compile\n[INFO] | | | +- org.apache.xmlgraphics:batik-script:jar:1.7:compile\n[INFO] | | | \\- org.apache.xmlgraphics:batik-xml:jar:1.7:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-awt-util:jar:1.7:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-gvt:jar:1.7:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-transcoder:jar:1.7:compile\n[INFO] | | | \\- org.apache.xmlgraphics:batik-svggen:jar:1.7:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-extension:jar:1.7:compile\n[INFO] | | +- org.apache.xmlgraphics:batik-ext:jar:1.7:compile\n[INFO] | | +- commons-io:commons-io:jar:1.3.1:compile\n[INFO] | | \\- org.apache.avalon.framework:avalon-framework-impl:jar:4.3.1:compile\n[INFO] | +- org.apache.avalon.framework:avalon-framework-api:jar:4.3.1:compile\n[INFO] | +- poi:poi:jar:2.5.1-final-20040804:compile\n[INFO] | +- javax.servlet:servlet-api:jar:2.5:compile\n[INFO] | +- javax.mail:mail:jar:1.4.1:compile\n[INFO] | | \\- javax.activation:activation:jar:1.1.1:compile\n[INFO] | +- org.openoffice:jurt:jar:3.2.1:compile\n[INFO] | | \\- org.openoffice:ridl:jar:3.2.1:compile\n[INFO] | +- org.openoffice:unoil:jar:3.1.0:compile\n[INFO] | +- org.openoffice:juh:jar:3.1.0:compile\n[INFO] | \\- ru.novosoft.dc:rtf2fo:jar:eval:compile\n[INFO] +- com.jgoodies:forms:jar:1.0.7:compile\n[INFO] +- xerces:xercesImpl:jar:2.4.0:compile\n[INFO] +- com.oracle:ojdbc5:jar:11.1.0.6.0:compile\n[INFO] +- javax.help:javahelp:jar:2.0.02:compile\n[INFO] +- com.example:custom-swing-framework:jar:1.1.2-SNAPSHOT:compile\n[INFO] | +- com.whirlycott:whirlycache:jar:0.7.1:compile\n[INFO] | | +- commons-collections:commons-collections:jar:3.1:compile\n[INFO] | | +- jdom:jdom:jar:1.0:compile\n[INFO] | | \\- concurrent:concurrent:jar:1.3.4:compile\n[INFO] | +- ehcache:ehcache:jar:0.9:compile\n[INFO] | +- org.enhydra.xmlc:xmlc:jar:2.2.7.1:compile\n[INFO] | +- com.jgoodies:looks:jar:2.2.2:compile\n[INFO] | +- org.swinglabs:swingx:jar:1.6.1:compile\n[INFO] | | +- com.jhlabs:filters:jar:2.0.235:compile\n[INFO] | | \\- org.swinglabs:swing-worker:jar:1.1:compile\n[INFO] | \\- struts:struts:jar:1.2.9:compile\n[INFO] | +- commons-beanutils:commons-beanutils:jar:1.7.0:compile\n[INFO] | +- commons-digester:commons-digester:jar:1.6:compile\n[INFO] | +- commons-fileupload:commons-fileupload:jar:1.0:compile\n[INFO] | +- commons-validator:commons-validator:jar:1.1.4:compile\n[INFO] | +- oro:oro:jar:2.0.7:compile\n[INFO] | \\- antlr:antlr:jar:2.7.2:compile\n[INFO] +- junit:junit:jar:4.8.2:test\n[INFO] \\- org.slf4j:slf4j-api:jar:1.6.1:compile\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESSFUL\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 2 seconds\n[INFO] Finished at: Tue Jun 28 14:10:29 CEST 2011\n[INFO] Final Memory: 22M/257M\n[INFO] ------------------------------------------------------------------------\n",[405,12868,12869,12874,12879,12884,12898,12903,12908,12913,12925,12936,12947,12958,12969,12980,12985,12990,12995,13000,13005,13010,13015,13026,13040,13055,13071,13086,13101,13116,13131,13146,13161,13179,13194,13209,13224,13239,13255,13271,13286,13304,13319,13335,13348,13363,13378,13393,13406,13419,13434,13447,13460,13473,13488,13501,13515,13529,13541,13555,13569,13585,13601,13617,13633,13649,13665,13679,13695,13711,13725,13739,13753,13769,13783,13797,13811,13825,13837,13849,13861,13873,13887,13899,13913,13925,13937,13949,13955,13961,13967,13973,13979,13991,14005,14019,14033,14045,14057,14069,14081,14095,14109,14121,14133,14145,14157,14169,14181,14193,14199,14210,14216,14222,14227,14233,14239,14245],{"__ignoreMap":34},[500,12870,12871],{"class":502,"line":503},[500,12872,12873],{"class":506},"[INFO] [dependency:tree {execution: default-cli}]\n",[500,12875,12876],{"class":502,"line":98},[500,12877,12878],{"class":506},"[INFO] com.example:swing-client:jar:1.1.0-SNAPSHOT\n",[500,12880,12881],{"class":502,"line":249},[500,12882,12883],{"class":506},"[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.1:compile\n",[500,12885,12886,12889,12892,12895],{"class":502,"line":583},[500,12887,12888],{"class":506},"[INFO] ",[500,12890,12891],{"class":677},"|",[500,12893,12894],{"class":513}," \\-",[500,12896,12897],{"class":520}," log4j:log4j:jar:1.2.16:compile\n",[500,12899,12900],{"class":502,"line":615},[500,12901,12902],{"class":506},"[INFO] +- jgoodies:plastic:jar:1.2.0:compile\n",[500,12904,12905],{"class":502,"line":621},[500,12906,12907],{"class":506},"[INFO] +- com.toedter:jcalendar:jar:1.3.2:compile\n",[500,12909,12910],{"class":502,"line":631},[500,12911,12912],{"class":506},"[INFO] +- com.example:jms:jar:1.0.1-SNAPSHOT:compile\n",[500,12914,12915,12917,12919,12922],{"class":502,"line":641},[500,12916,12888],{"class":506},[500,12918,12891],{"class":677},[500,12920,12921],{"class":513}," +-",[500,12923,12924],{"class":520}," jboss:jboss-system:jar:4.0.2:compile\n",[500,12926,12927,12929,12931,12933],{"class":502,"line":647},[500,12928,12888],{"class":506},[500,12930,12891],{"class":677},[500,12932,12921],{"class":513},[500,12934,12935],{"class":520}," jms:jms:jar:1.0.2:compile\n",[500,12937,12938,12940,12942,12944],{"class":502,"line":805},[500,12939,12888],{"class":506},[500,12941,12891],{"class":677},[500,12943,12921],{"class":513},[500,12945,12946],{"class":520}," org.quartz-scheduler:quartz:jar:1.2.3:compile\n",[500,12948,12949,12951,12953,12955],{"class":502,"line":810},[500,12950,12888],{"class":506},[500,12952,12891],{"class":677},[500,12954,12921],{"class":513},[500,12956,12957],{"class":520}," jboss:jbossmq-client:jar:4.0.2:compile\n",[500,12959,12960,12962,12964,12966],{"class":502,"line":826},[500,12961,12888],{"class":506},[500,12963,12891],{"class":677},[500,12965,12921],{"class":513},[500,12967,12968],{"class":520}," jboss:jboss-jmx:jar:4.0.2:compile\n",[500,12970,12971,12973,12975,12977],{"class":502,"line":838},[500,12972,12888],{"class":506},[500,12974,12891],{"class":677},[500,12976,12894],{"class":513},[500,12978,12979],{"class":520}," javax.ejb:ejb:jar:2.1:compile\n",[500,12981,12982],{"class":502,"line":937},[500,12983,12984],{"class":506},"[INFO] +- flex:flex-messaging-common:jar:1.0.0:compile\n",[500,12986,12987],{"class":502,"line":943},[500,12988,12989],{"class":506},"[INFO] +- flex:flex-messaging-core:jar:1.0.0:compile\n",[500,12991,12992],{"class":502,"line":948},[500,12993,12994],{"class":506},"[INFO] +- flex:flex-messaging-opt:jar:1.0.0:compile\n",[500,12996,12997],{"class":502,"line":954},[500,12998,12999],{"class":506},"[INFO] +- flex:flex-messaging-proxy:jar:1.0.0:compile\n",[500,13001,13002],{"class":502,"line":1898},[500,13003,13004],{"class":506},"[INFO] +- flex:flex-messaging-remoting:jar:1.0.0:compile\n",[500,13006,13007],{"class":502,"line":1904},[500,13008,13009],{"class":506},"[INFO] +- poi:poi-2.5.1-final:jar:20040804:compile\n",[500,13011,13012],{"class":502,"line":1910},[500,13013,13014],{"class":506},"[INFO] +- com.example:docengine:jar:1.0.0-SNAPSHOT:compile\n",[500,13016,13017,13019,13021,13023],{"class":502,"line":1916},[500,13018,12888],{"class":506},[500,13020,12891],{"class":677},[500,13022,12921],{"class":513},[500,13024,13025],{"class":520}," com.example:webfw:jar:1.0.2-SNAPSHOT:compile\n",[500,13027,13028,13030,13032,13035,13037],{"class":502,"line":1922},[500,13029,12888],{"class":506},[500,13031,12891],{"class":677},[500,13033,13034],{"class":677}," |",[500,13036,12921],{"class":513},[500,13038,13039],{"class":520}," com.example:dms_client:jar:1.0.2:compile\n",[500,13041,13042,13044,13046,13048,13050,13052],{"class":502,"line":1928},[500,13043,12888],{"class":506},[500,13045,12891],{"class":677},[500,13047,13034],{"class":677},[500,13049,13034],{"class":677},[500,13051,12894],{"class":513},[500,13053,13054],{"class":520}," org.apache.axis2:axis2:jar:1.1.1:compile\n",[500,13056,13057,13059,13061,13063,13065,13068],{"class":502,"line":1934},[500,13058,12888],{"class":506},[500,13060,12891],{"class":677},[500,13062,13034],{"class":677},[500,13064,13034],{"class":677},[500,13066,13067],{"class":513}," +-",[500,13069,13070],{"class":520}," org.apache.ws.commons.axiom:axiom-dom:jar:1.2:compile\n",[500,13072,13073,13075,13077,13079,13081,13083],{"class":502,"line":1940},[500,13074,12888],{"class":506},[500,13076,12891],{"class":677},[500,13078,13034],{"class":677},[500,13080,13034],{"class":677},[500,13082,13067],{"class":513},[500,13084,13085],{"class":520}," org.apache.ws.commons.axiom:axiom-impl:jar:1.2:compile\n",[500,13087,13088,13090,13092,13094,13096,13098],{"class":502,"line":1946},[500,13089,12888],{"class":506},[500,13091,12891],{"class":677},[500,13093,13034],{"class":677},[500,13095,13034],{"class":677},[500,13097,13067],{"class":513},[500,13099,13100],{"class":520}," ant:ant:jar:1.6.5:compile\n",[500,13102,13103,13105,13107,13109,13111,13113],{"class":502,"line":1952},[500,13104,12888],{"class":506},[500,13106,12891],{"class":677},[500,13108,13034],{"class":677},[500,13110,13034],{"class":677},[500,13112,13067],{"class":513},[500,13114,13115],{"class":520}," woodstox:wstx-asl:jar:3.0.1:compile\n",[500,13117,13118,13120,13122,13124,13126,13128],{"class":502,"line":1958},[500,13119,12888],{"class":506},[500,13121,12891],{"class":677},[500,13123,13034],{"class":677},[500,13125,13034],{"class":677},[500,13127,13067],{"class":513},[500,13129,13130],{"class":520}," org.apache.ws.commons.schema:XmlSchema:jar:1.2:compile\n",[500,13132,13133,13135,13137,13139,13141,13143],{"class":502,"line":1964},[500,13134,12888],{"class":506},[500,13136,12891],{"class":677},[500,13138,13034],{"class":677},[500,13140,13034],{"class":677},[500,13142,13067],{"class":513},[500,13144,13145],{"class":520}," annogen:annogen:jar:0.1.0:compile\n",[500,13147,13148,13150,13152,13154,13156,13158],{"class":502,"line":1970},[500,13149,12888],{"class":506},[500,13151,12891],{"class":677},[500,13153,13034],{"class":677},[500,13155,13034],{"class":677},[500,13157,13067],{"class":513},[500,13159,13160],{"class":520}," commons-httpclient:commons-httpclient:jar:3.0.1:compile\n",[500,13162,13163,13165,13167,13169,13171,13174,13176],{"class":502,"line":1976},[500,13164,12888],{"class":506},[500,13166,12891],{"class":677},[500,13168,13034],{"class":677},[500,13170,13034],{"class":677},[500,13172,13173],{"class":677}," |",[500,13175,12894],{"class":513},[500,13177,13178],{"class":520}," commons-codec:commons-codec:jar:1.2:compile\n",[500,13180,13181,13183,13185,13187,13189,13191],{"class":502,"line":1982},[500,13182,12888],{"class":506},[500,13184,12891],{"class":677},[500,13186,13034],{"class":677},[500,13188,13034],{"class":677},[500,13190,13067],{"class":513},[500,13192,13193],{"class":520}," org.apache.httpcomponents:jakarta-httpcore:jar:4.0-alpha2:compile\n",[500,13195,13196,13198,13200,13202,13204,13206],{"class":502,"line":1988},[500,13197,12888],{"class":506},[500,13199,12891],{"class":677},[500,13201,13034],{"class":677},[500,13203,13034],{"class":677},[500,13205,13067],{"class":513},[500,13207,13208],{"class":520}," wsdl4j:wsdl4j:jar:1.6.1:compile\n",[500,13210,13211,13213,13215,13217,13219,13221],{"class":502,"line":1994},[500,13212,12888],{"class":506},[500,13214,12891],{"class":677},[500,13216,13034],{"class":677},[500,13218,13034],{"class":677},[500,13220,13067],{"class":513},[500,13222,13223],{"class":520}," backport-util-concurrent:backport-util-concurrent:jar:2.2:compile\n",[500,13225,13226,13228,13230,13232,13234,13236],{"class":502,"line":2000},[500,13227,12888],{"class":506},[500,13229,12891],{"class":677},[500,13231,13034],{"class":677},[500,13233,13034],{"class":677},[500,13235,13067],{"class":513},[500,13237,13238],{"class":520}," org.apache.ws.commons.neethi:neethi:jar:2.0:compile\n",[500,13240,13241,13243,13245,13247,13249,13252],{"class":502,"line":9047},[500,13242,12888],{"class":506},[500,13244,12891],{"class":677},[500,13246,13034],{"class":677},[500,13248,13034],{"class":677},[500,13250,13251],{"class":513}," \\-",[500,13253,13254],{"class":520}," org.apache.woden:woden-impl-om:jar:1.0M8:compile\n",[500,13256,13257,13259,13261,13263,13265,13268],{"class":502,"line":9052},[500,13258,12888],{"class":506},[500,13260,12891],{"class":677},[500,13262,13034],{"class":677},[500,13264,13034],{"class":677},[500,13266,13267],{"class":513}," +-",[500,13269,13270],{"class":520}," org.apache.woden:woden-api:jar:1.0M8:compile\n",[500,13272,13273,13275,13277,13279,13281,13283],{"class":502,"line":9057},[500,13274,12888],{"class":506},[500,13276,12891],{"class":677},[500,13278,13034],{"class":677},[500,13280,13034],{"class":677},[500,13282,13267],{"class":513},[500,13284,13285],{"class":520}," org.apache.ant:ant:jar:1.7.0:compile\n",[500,13287,13288,13290,13292,13294,13296,13299,13301],{"class":502,"line":9062},[500,13289,12888],{"class":506},[500,13291,12891],{"class":677},[500,13293,13034],{"class":677},[500,13295,13034],{"class":677},[500,13297,13298],{"class":677}," |",[500,13300,12894],{"class":513},[500,13302,13303],{"class":520}," org.apache.ant:ant-launcher:jar:1.7.0:compile\n",[500,13305,13306,13308,13310,13312,13314,13316],{"class":502,"line":9067},[500,13307,12888],{"class":506},[500,13309,12891],{"class":677},[500,13311,13034],{"class":677},[500,13313,13034],{"class":677},[500,13315,13267],{"class":513},[500,13317,13318],{"class":520}," xerces:xmlParserAPIs:jar:2.6.0:compile\n",[500,13320,13321,13323,13325,13327,13329,13332],{"class":502,"line":9072},[500,13322,12888],{"class":506},[500,13324,12891],{"class":677},[500,13326,13034],{"class":677},[500,13328,13034],{"class":677},[500,13330,13331],{"class":513}," \\-",[500,13333,13334],{"class":520}," org.codehaus.woodstox:wstx-asl:jar:3.2.4:runtime\n",[500,13336,13337,13339,13341,13343,13345],{"class":502,"line":9077},[500,13338,12888],{"class":506},[500,13340,12891],{"class":677},[500,13342,13034],{"class":677},[500,13344,12921],{"class":513},[500,13346,13347],{"class":520}," org.apache.ws.commons.axiom:axiom-api:jar:1.2:compile\n",[500,13349,13350,13352,13354,13356,13358,13360],{"class":502,"line":9082},[500,13351,12888],{"class":506},[500,13353,12891],{"class":677},[500,13355,13034],{"class":677},[500,13357,13034],{"class":677},[500,13359,12921],{"class":513},[500,13361,13362],{"class":520}," jaxen:jaxen:jar:1.1-beta-9:compile\n",[500,13364,13365,13367,13369,13371,13373,13375],{"class":502,"line":9533},[500,13366,12888],{"class":506},[500,13368,12891],{"class":677},[500,13370,13034],{"class":677},[500,13372,13034],{"class":677},[500,13374,12921],{"class":513},[500,13376,13377],{"class":520}," xml-apis:xml-apis:jar:1.3.03:compile\n",[500,13379,13380,13382,13384,13386,13388,13390],{"class":502,"line":9539},[500,13381,12888],{"class":506},[500,13383,12891],{"class":677},[500,13385,13034],{"class":677},[500,13387,13034],{"class":677},[500,13389,12894],{"class":513},[500,13391,13392],{"class":520}," stax:stax-api:jar:1.0.1:compile\n",[500,13394,13395,13397,13399,13401,13403],{"class":502,"line":9545},[500,13396,12888],{"class":506},[500,13398,12891],{"class":677},[500,13400,13034],{"class":677},[500,13402,12921],{"class":513},[500,13404,13405],{"class":520}," org.slf4j:jcl-over-slf4j:jar:1.6.1:compile\n",[500,13407,13408,13410,13412,13414,13416],{"class":502,"line":9551},[500,13409,12888],{"class":506},[500,13411,12891],{"class":677},[500,13413,13034],{"class":677},[500,13415,12921],{"class":513},[500,13417,13418],{"class":520}," org.apache.struts:struts-core:jar:1.3.8:compile\n",[500,13420,13421,13423,13425,13427,13429,13431],{"class":502,"line":9557},[500,13422,12888],{"class":506},[500,13424,12891],{"class":677},[500,13426,13034],{"class":677},[500,13428,13034],{"class":677},[500,13430,12894],{"class":513},[500,13432,13433],{"class":520}," commons-chain:commons-chain:jar:1.1:compile\n",[500,13435,13436,13438,13440,13442,13444],{"class":502,"line":9563},[500,13437,12888],{"class":506},[500,13439,12891],{"class":677},[500,13441,13034],{"class":677},[500,13443,12921],{"class":513},[500,13445,13446],{"class":520}," com.sun.xml:xml:jar:0.8.0:compile\n",[500,13448,13449,13451,13453,13455,13457],{"class":502,"line":9569},[500,13450,12888],{"class":506},[500,13452,12891],{"class":677},[500,13454,13034],{"class":677},[500,13456,12921],{"class":513},[500,13458,13459],{"class":520}," xmlc:xmlc-all-runtime:jar:2.2.8.1:compile\n",[500,13461,13462,13464,13466,13468,13470],{"class":502,"line":9574},[500,13463,12888],{"class":506},[500,13465,12891],{"class":677},[500,13467,13034],{"class":677},[500,13469,12921],{"class":513},[500,13471,13472],{"class":520}," xalan:xalan:jar:2.7.1:compile\n",[500,13474,13475,13477,13479,13481,13483,13485],{"class":502,"line":9580},[500,13476,12888],{"class":506},[500,13478,12891],{"class":677},[500,13480,13034],{"class":677},[500,13482,13034],{"class":677},[500,13484,12894],{"class":513},[500,13486,13487],{"class":520}," xalan:serializer:jar:2.7.1:compile\n",[500,13489,13490,13492,13494,13496,13498],{"class":502,"line":9586},[500,13491,12888],{"class":506},[500,13493,12891],{"class":677},[500,13495,13034],{"class":677},[500,13497,12894],{"class":513},[500,13499,13500],{"class":520}," com.lowagie:itext:jar:2.0.7:compile\n",[500,13502,13504,13506,13508,13510,13512],{"class":502,"line":13503},54,[500,13505,12888],{"class":506},[500,13507,12891],{"class":677},[500,13509,13034],{"class":677},[500,13511,13067],{"class":513},[500,13513,13514],{"class":520}," bouncycastle:bcmail-jdk14:jar:138:compile\n",[500,13516,13518,13520,13522,13524,13526],{"class":502,"line":13517},55,[500,13519,12888],{"class":506},[500,13521,12891],{"class":677},[500,13523,13034],{"class":677},[500,13525,13251],{"class":513},[500,13527,13528],{"class":520}," bouncycastle:bcprov-jdk14:jar:138:compile\n",[500,13530,13532,13534,13536,13538],{"class":502,"line":13531},56,[500,13533,12888],{"class":506},[500,13535,12891],{"class":677},[500,13537,12921],{"class":513},[500,13539,13540],{"class":520}," org.apache.xmlgraphics:fop:jar:0.95-1:compile\n",[500,13542,13544,13546,13548,13550,13552],{"class":502,"line":13543},57,[500,13545,12888],{"class":506},[500,13547,12891],{"class":677},[500,13549,13034],{"class":677},[500,13551,12921],{"class":513},[500,13553,13554],{"class":520}," org.apache.xmlgraphics:xmlgraphics-commons:jar:1.3.1:compile\n",[500,13556,13558,13560,13562,13564,13566],{"class":502,"line":13557},58,[500,13559,12888],{"class":506},[500,13561,12891],{"class":677},[500,13563,13034],{"class":677},[500,13565,12921],{"class":513},[500,13567,13568],{"class":520}," org.apache.xmlgraphics:batik-svg-dom:jar:1.7:compile\n",[500,13570,13572,13574,13576,13578,13580,13582],{"class":502,"line":13571},59,[500,13573,12888],{"class":506},[500,13575,12891],{"class":677},[500,13577,13034],{"class":677},[500,13579,13034],{"class":677},[500,13581,12921],{"class":513},[500,13583,13584],{"class":520}," org.apache.xmlgraphics:batik-anim:jar:1.7:compile\n",[500,13586,13588,13590,13592,13594,13596,13598],{"class":502,"line":13587},60,[500,13589,12888],{"class":506},[500,13591,12891],{"class":677},[500,13593,13034],{"class":677},[500,13595,13034],{"class":677},[500,13597,12921],{"class":513},[500,13599,13600],{"class":520}," org.apache.xmlgraphics:batik-css:jar:1.7:compile\n",[500,13602,13604,13606,13608,13610,13612,13614],{"class":502,"line":13603},61,[500,13605,12888],{"class":506},[500,13607,12891],{"class":677},[500,13609,13034],{"class":677},[500,13611,13034],{"class":677},[500,13613,12921],{"class":513},[500,13615,13616],{"class":520}," org.apache.xmlgraphics:batik-dom:jar:1.7:compile\n",[500,13618,13620,13622,13624,13626,13628,13630],{"class":502,"line":13619},62,[500,13621,12888],{"class":506},[500,13623,12891],{"class":677},[500,13625,13034],{"class":677},[500,13627,13034],{"class":677},[500,13629,12921],{"class":513},[500,13631,13632],{"class":520}," org.apache.xmlgraphics:batik-parser:jar:1.7:compile\n",[500,13634,13636,13638,13640,13642,13644,13646],{"class":502,"line":13635},63,[500,13637,12888],{"class":506},[500,13639,12891],{"class":677},[500,13641,13034],{"class":677},[500,13643,13034],{"class":677},[500,13645,12921],{"class":513},[500,13647,13648],{"class":520}," org.apache.xmlgraphics:batik-util:jar:1.7:compile\n",[500,13650,13652,13654,13656,13658,13660,13662],{"class":502,"line":13651},64,[500,13653,12888],{"class":506},[500,13655,12891],{"class":677},[500,13657,13034],{"class":677},[500,13659,13034],{"class":677},[500,13661,12894],{"class":513},[500,13663,13664],{"class":520}," xml-apis:xml-apis-ext:jar:1.3.04:compile\n",[500,13666,13668,13670,13672,13674,13676],{"class":502,"line":13667},65,[500,13669,12888],{"class":506},[500,13671,12891],{"class":677},[500,13673,13034],{"class":677},[500,13675,12921],{"class":513},[500,13677,13678],{"class":520}," org.apache.xmlgraphics:batik-bridge:jar:1.7:compile\n",[500,13680,13682,13684,13686,13688,13690,13692],{"class":502,"line":13681},66,[500,13683,12888],{"class":506},[500,13685,12891],{"class":677},[500,13687,13034],{"class":677},[500,13689,13034],{"class":677},[500,13691,12921],{"class":513},[500,13693,13694],{"class":520}," org.apache.xmlgraphics:batik-script:jar:1.7:compile\n",[500,13696,13698,13700,13702,13704,13706,13708],{"class":502,"line":13697},67,[500,13699,12888],{"class":506},[500,13701,12891],{"class":677},[500,13703,13034],{"class":677},[500,13705,13034],{"class":677},[500,13707,12894],{"class":513},[500,13709,13710],{"class":520}," org.apache.xmlgraphics:batik-xml:jar:1.7:compile\n",[500,13712,13714,13716,13718,13720,13722],{"class":502,"line":13713},68,[500,13715,12888],{"class":506},[500,13717,12891],{"class":677},[500,13719,13034],{"class":677},[500,13721,12921],{"class":513},[500,13723,13724],{"class":520}," org.apache.xmlgraphics:batik-awt-util:jar:1.7:compile\n",[500,13726,13728,13730,13732,13734,13736],{"class":502,"line":13727},69,[500,13729,12888],{"class":506},[500,13731,12891],{"class":677},[500,13733,13034],{"class":677},[500,13735,12921],{"class":513},[500,13737,13738],{"class":520}," org.apache.xmlgraphics:batik-gvt:jar:1.7:compile\n",[500,13740,13742,13744,13746,13748,13750],{"class":502,"line":13741},70,[500,13743,12888],{"class":506},[500,13745,12891],{"class":677},[500,13747,13034],{"class":677},[500,13749,12921],{"class":513},[500,13751,13752],{"class":520}," org.apache.xmlgraphics:batik-transcoder:jar:1.7:compile\n",[500,13754,13756,13758,13760,13762,13764,13766],{"class":502,"line":13755},71,[500,13757,12888],{"class":506},[500,13759,12891],{"class":677},[500,13761,13034],{"class":677},[500,13763,13034],{"class":677},[500,13765,12894],{"class":513},[500,13767,13768],{"class":520}," org.apache.xmlgraphics:batik-svggen:jar:1.7:compile\n",[500,13770,13772,13774,13776,13778,13780],{"class":502,"line":13771},72,[500,13773,12888],{"class":506},[500,13775,12891],{"class":677},[500,13777,13034],{"class":677},[500,13779,12921],{"class":513},[500,13781,13782],{"class":520}," org.apache.xmlgraphics:batik-extension:jar:1.7:compile\n",[500,13784,13786,13788,13790,13792,13794],{"class":502,"line":13785},73,[500,13787,12888],{"class":506},[500,13789,12891],{"class":677},[500,13791,13034],{"class":677},[500,13793,12921],{"class":513},[500,13795,13796],{"class":520}," org.apache.xmlgraphics:batik-ext:jar:1.7:compile\n",[500,13798,13800,13802,13804,13806,13808],{"class":502,"line":13799},74,[500,13801,12888],{"class":506},[500,13803,12891],{"class":677},[500,13805,13034],{"class":677},[500,13807,12921],{"class":513},[500,13809,13810],{"class":520}," commons-io:commons-io:jar:1.3.1:compile\n",[500,13812,13814,13816,13818,13820,13822],{"class":502,"line":13813},75,[500,13815,12888],{"class":506},[500,13817,12891],{"class":677},[500,13819,13034],{"class":677},[500,13821,12894],{"class":513},[500,13823,13824],{"class":520}," org.apache.avalon.framework:avalon-framework-impl:jar:4.3.1:compile\n",[500,13826,13828,13830,13832,13834],{"class":502,"line":13827},76,[500,13829,12888],{"class":506},[500,13831,12891],{"class":677},[500,13833,12921],{"class":513},[500,13835,13836],{"class":520}," org.apache.avalon.framework:avalon-framework-api:jar:4.3.1:compile\n",[500,13838,13840,13842,13844,13846],{"class":502,"line":13839},77,[500,13841,12888],{"class":506},[500,13843,12891],{"class":677},[500,13845,12921],{"class":513},[500,13847,13848],{"class":520}," poi:poi:jar:2.5.1-final-20040804:compile\n",[500,13850,13852,13854,13856,13858],{"class":502,"line":13851},78,[500,13853,12888],{"class":506},[500,13855,12891],{"class":677},[500,13857,12921],{"class":513},[500,13859,13860],{"class":520}," javax.servlet:servlet-api:jar:2.5:compile\n",[500,13862,13864,13866,13868,13870],{"class":502,"line":13863},79,[500,13865,12888],{"class":506},[500,13867,12891],{"class":677},[500,13869,12921],{"class":513},[500,13871,13872],{"class":520}," javax.mail:mail:jar:1.4.1:compile\n",[500,13874,13876,13878,13880,13882,13884],{"class":502,"line":13875},80,[500,13877,12888],{"class":506},[500,13879,12891],{"class":677},[500,13881,13034],{"class":677},[500,13883,12894],{"class":513},[500,13885,13886],{"class":520}," javax.activation:activation:jar:1.1.1:compile\n",[500,13888,13890,13892,13894,13896],{"class":502,"line":13889},81,[500,13891,12888],{"class":506},[500,13893,12891],{"class":677},[500,13895,12921],{"class":513},[500,13897,13898],{"class":520}," org.openoffice:jurt:jar:3.2.1:compile\n",[500,13900,13902,13904,13906,13908,13910],{"class":502,"line":13901},82,[500,13903,12888],{"class":506},[500,13905,12891],{"class":677},[500,13907,13034],{"class":677},[500,13909,12894],{"class":513},[500,13911,13912],{"class":520}," org.openoffice:ridl:jar:3.2.1:compile\n",[500,13914,13916,13918,13920,13922],{"class":502,"line":13915},83,[500,13917,12888],{"class":506},[500,13919,12891],{"class":677},[500,13921,12921],{"class":513},[500,13923,13924],{"class":520}," org.openoffice:unoil:jar:3.1.0:compile\n",[500,13926,13928,13930,13932,13934],{"class":502,"line":13927},84,[500,13929,12888],{"class":506},[500,13931,12891],{"class":677},[500,13933,12921],{"class":513},[500,13935,13936],{"class":520}," org.openoffice:juh:jar:3.1.0:compile\n",[500,13938,13940,13942,13944,13946],{"class":502,"line":13939},85,[500,13941,12888],{"class":506},[500,13943,12891],{"class":677},[500,13945,12894],{"class":513},[500,13947,13948],{"class":520}," ru.novosoft.dc:rtf2fo:jar:eval:compile\n",[500,13950,13952],{"class":502,"line":13951},86,[500,13953,13954],{"class":506},"[INFO] +- com.jgoodies:forms:jar:1.0.7:compile\n",[500,13956,13958],{"class":502,"line":13957},87,[500,13959,13960],{"class":506},"[INFO] +- xerces:xercesImpl:jar:2.4.0:compile\n",[500,13962,13964],{"class":502,"line":13963},88,[500,13965,13966],{"class":506},"[INFO] +- com.oracle:ojdbc5:jar:11.1.0.6.0:compile\n",[500,13968,13970],{"class":502,"line":13969},89,[500,13971,13972],{"class":506},"[INFO] +- javax.help:javahelp:jar:2.0.02:compile\n",[500,13974,13976],{"class":502,"line":13975},90,[500,13977,13978],{"class":506},"[INFO] +- com.example:custom-swing-framework:jar:1.1.2-SNAPSHOT:compile\n",[500,13980,13982,13984,13986,13988],{"class":502,"line":13981},91,[500,13983,12888],{"class":506},[500,13985,12891],{"class":677},[500,13987,12921],{"class":513},[500,13989,13990],{"class":520}," com.whirlycott:whirlycache:jar:0.7.1:compile\n",[500,13992,13994,13996,13998,14000,14002],{"class":502,"line":13993},92,[500,13995,12888],{"class":506},[500,13997,12891],{"class":677},[500,13999,13034],{"class":677},[500,14001,12921],{"class":513},[500,14003,14004],{"class":520}," commons-collections:commons-collections:jar:3.1:compile\n",[500,14006,14008,14010,14012,14014,14016],{"class":502,"line":14007},93,[500,14009,12888],{"class":506},[500,14011,12891],{"class":677},[500,14013,13034],{"class":677},[500,14015,12921],{"class":513},[500,14017,14018],{"class":520}," jdom:jdom:jar:1.0:compile\n",[500,14020,14022,14024,14026,14028,14030],{"class":502,"line":14021},94,[500,14023,12888],{"class":506},[500,14025,12891],{"class":677},[500,14027,13034],{"class":677},[500,14029,12894],{"class":513},[500,14031,14032],{"class":520}," concurrent:concurrent:jar:1.3.4:compile\n",[500,14034,14036,14038,14040,14042],{"class":502,"line":14035},95,[500,14037,12888],{"class":506},[500,14039,12891],{"class":677},[500,14041,12921],{"class":513},[500,14043,14044],{"class":520}," ehcache:ehcache:jar:0.9:compile\n",[500,14046,14048,14050,14052,14054],{"class":502,"line":14047},96,[500,14049,12888],{"class":506},[500,14051,12891],{"class":677},[500,14053,12921],{"class":513},[500,14055,14056],{"class":520}," org.enhydra.xmlc:xmlc:jar:2.2.7.1:compile\n",[500,14058,14060,14062,14064,14066],{"class":502,"line":14059},97,[500,14061,12888],{"class":506},[500,14063,12891],{"class":677},[500,14065,12921],{"class":513},[500,14067,14068],{"class":520}," com.jgoodies:looks:jar:2.2.2:compile\n",[500,14070,14072,14074,14076,14078],{"class":502,"line":14071},98,[500,14073,12888],{"class":506},[500,14075,12891],{"class":677},[500,14077,12921],{"class":513},[500,14079,14080],{"class":520}," org.swinglabs:swingx:jar:1.6.1:compile\n",[500,14082,14084,14086,14088,14090,14092],{"class":502,"line":14083},99,[500,14085,12888],{"class":506},[500,14087,12891],{"class":677},[500,14089,13034],{"class":677},[500,14091,12921],{"class":513},[500,14093,14094],{"class":520}," com.jhlabs:filters:jar:2.0.235:compile\n",[500,14096,14098,14100,14102,14104,14106],{"class":502,"line":14097},100,[500,14099,12888],{"class":506},[500,14101,12891],{"class":677},[500,14103,13034],{"class":677},[500,14105,12894],{"class":513},[500,14107,14108],{"class":520}," org.swinglabs:swing-worker:jar:1.1:compile\n",[500,14110,14112,14114,14116,14118],{"class":502,"line":14111},101,[500,14113,12888],{"class":506},[500,14115,12891],{"class":677},[500,14117,12894],{"class":513},[500,14119,14120],{"class":520}," struts:struts:jar:1.2.9:compile\n",[500,14122,14124,14126,14128,14130],{"class":502,"line":14123},102,[500,14125,12888],{"class":506},[500,14127,12891],{"class":677},[500,14129,13067],{"class":513},[500,14131,14132],{"class":520}," commons-beanutils:commons-beanutils:jar:1.7.0:compile\n",[500,14134,14136,14138,14140,14142],{"class":502,"line":14135},103,[500,14137,12888],{"class":506},[500,14139,12891],{"class":677},[500,14141,13067],{"class":513},[500,14143,14144],{"class":520}," commons-digester:commons-digester:jar:1.6:compile\n",[500,14146,14148,14150,14152,14154],{"class":502,"line":14147},104,[500,14149,12888],{"class":506},[500,14151,12891],{"class":677},[500,14153,13067],{"class":513},[500,14155,14156],{"class":520}," commons-fileupload:commons-fileupload:jar:1.0:compile\n",[500,14158,14160,14162,14164,14166],{"class":502,"line":14159},105,[500,14161,12888],{"class":506},[500,14163,12891],{"class":677},[500,14165,13067],{"class":513},[500,14167,14168],{"class":520}," commons-validator:commons-validator:jar:1.1.4:compile\n",[500,14170,14172,14174,14176,14178],{"class":502,"line":14171},106,[500,14173,12888],{"class":506},[500,14175,12891],{"class":677},[500,14177,13067],{"class":513},[500,14179,14180],{"class":520}," oro:oro:jar:2.0.7:compile\n",[500,14182,14184,14186,14188,14190],{"class":502,"line":14183},107,[500,14185,12888],{"class":506},[500,14187,12891],{"class":677},[500,14189,13251],{"class":513},[500,14191,14192],{"class":520}," antlr:antlr:jar:2.7.2:compile\n",[500,14194,14196],{"class":502,"line":14195},108,[500,14197,14198],{"class":506},"[INFO] +- junit:junit:jar:4.8.2:test\n",[500,14200,14202,14204,14207],{"class":502,"line":14201},109,[500,14203,12888],{"class":506},[500,14205,14206],{"class":703},"\\-",[500,14208,14209],{"class":506}," org.slf4j:slf4j-api:jar:1.6.1:compile\n",[500,14211,14213],{"class":502,"line":14212},110,[500,14214,14215],{"class":506},"[INFO] ------------------------------------------------------------------------\n",[500,14217,14219],{"class":502,"line":14218},111,[500,14220,14221],{"class":506},"[INFO] BUILD SUCCESSFUL\n",[500,14223,14225],{"class":502,"line":14224},112,[500,14226,14215],{"class":506},[500,14228,14230],{"class":502,"line":14229},113,[500,14231,14232],{"class":506},"[INFO] Total time: 2 seconds\n",[500,14234,14236],{"class":502,"line":14235},114,[500,14237,14238],{"class":506},"[INFO] Finished at: Tue Jun 28 14:10:29 CEST 2011\n",[500,14240,14242],{"class":502,"line":14241},115,[500,14243,14244],{"class":506},"[INFO] Final Memory: 22M/257M\n",[500,14246,14248],{"class":502,"line":14247},116,[500,14249,14215],{"class":506},[18,14251,14252],{},"As described before the elimination of the “rogue” logging class wasn’t a big problem. But we hadn’t reached the finish\nline yet.",[18,14254,14255,14256,14261,14262,14267,14268,14273,14274,14279,14280,14285,14286,14291],{},"The final application is being assembled into one big EAR file which contains a lot\nof ",[22,14257,14260],{"href":14258,"rel":14259},"http://docs.jboss.org/jbossas/jboss4guide/r4/html/ch5.chapter.html",[26],"EJBs",", SAR\nfiles (",[22,14263,14266],{"href":14264,"rel":14265},"http://community.jboss.org/wiki/ServiceArchive",[26],"JBoss Service Archives","), and\nthree ",[22,14269,14272],{"href":14270,"rel":14271},"http://docs.jboss.org/jbossas/jboss4guide/r4/html/ch9.chapt.html",[26],"WARs"," (web applications). By default JBoss uses\nits unified class loader (well described in the JBoss Admin Guide in\nsection ",[22,14275,14278],{"href":14276,"rel":14277},"http://docs.jboss.org/jbossas/jboss4guide/r4/html/ch2.chapter.html#d0e2490",[26],"2.2.2.4. Inside the JBoss Class Loading Architecture",")\nwhich is for several reasons not suitable for the architecture of this legacy application (using incompatible versions\nof the same library in different components for example). Thus we wanted to utilize Apache Tomcat’s class loading\nmechanism, described in\nthe ",[22,14281,14284],{"href":14282,"rel":14283},"http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html",[26],"Apache Tomcat 5.5 Class Loader HOW-TO",", which\nbasically isolates the deployed web applications from each other. The wiki article\non ",[22,14287,14290],{"href":14288,"rel":14289},"http://community.jboss.org/wiki/JBossClassLoadingUseCases",[26],"Advanced JBoss Class Loading"," is also very informative in\nthis context.",[18,14293,14294,14295,14300,14301,14306],{},"Since JBoss 4.0.3.SP1 is using Apache Tomcat 5.5 as servlet container it\nprovides ",[22,14296,14299],{"href":14297,"rel":14298},"http://community.jboss.org/wiki/UseJBossWebLoader",[26],"a simple configuration setting"," to achieve exactly the\nabove described behaviour. Unfortunately we encountered a lot of class loading problems when we first deployed the\napplication after the log framework refactoring. We checked the dependencies for each component but everything seemed to\nbe correct. So we started what we do best:\nset ",[22,14302,14305],{"href":14303,"rel":14304},"http://community.jboss.org/wiki/EnableClassloaderLogging",[26],"logging to DEBUG"," and engage!",[18,14308,14309,14310,14315,14316,14321,14322,707],{},"In the end some knee-deep class loader debugging hinted us in the right direction: It’s not advisable to have more than\none instance of the SLF4J JARs in your class path. Basically the class loader was complaining that the classes it was\ntrying to load (e.\ng. ",[22,14311,14314],{"href":14312,"rel":14313},"http://www.slf4j.org/apidocs/org/slf4j/spi/LoggerFactoryBinder.html",[26],"org.slf4j.spi.LoggerFactoryBinder",") had already\nbeen loaded from another repository. If you recall the details of Apache\nTomcat’s ",[22,14317,14320],{"href":14318,"rel":14319},"http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html#Overview",[26],"class loader"," this makes perfect\nsense but we thought that it was intelligent enough to actually share these classes. As it turns out there’s a (somewhat\nundocumented) configuration option which will exactly do this for us: ",[469,14323,14324],{},"FilteredPackages",[18,14326,14327],{},"After adding the SLF4J package prefix to our jbossweb-tomcat.sar/META-INF/jboss-service.xml file as shown below, the\napplication magically started working again.",[492,14329,14331],{"className":1751,"code":14330,"language":1753,"meta":34,"style":34}," \u003C!-- The list of package prefixes that should not be loaded without\n delegating to the parent class loader before trying the web app\n class loader. The packages listed here are those tha are used by\n the web container implementation and cannot be overriden. The format\n is a comma separated list of the package names. There cannot be any\n whitespace between the package prefixes.\n This setting only applies when UseJBossWebLoader=false.\n -->\n \u003Cattribute name=\"FilteredPackages\">javax.servlet,org.slf4j\u003C/attribute>\n",[405,14332,14333,14338,14343,14348,14353,14358,14363,14368,14373],{"__ignoreMap":34},[500,14334,14335],{"class":502,"line":503},[500,14336,14337],{}," \u003C!-- The list of package prefixes that should not be loaded without\n",[500,14339,14340],{"class":502,"line":98},[500,14341,14342],{}," delegating to the parent class loader before trying the web app\n",[500,14344,14345],{"class":502,"line":249},[500,14346,14347],{}," class loader. The packages listed here are those tha are used by\n",[500,14349,14350],{"class":502,"line":583},[500,14351,14352],{}," the web container implementation and cannot be overriden. The format\n",[500,14354,14355],{"class":502,"line":615},[500,14356,14357],{}," is a comma separated list of the package names. There cannot be any\n",[500,14359,14360],{"class":502,"line":621},[500,14361,14362],{}," whitespace between the package prefixes.\n",[500,14364,14365],{"class":502,"line":631},[500,14366,14367],{}," This setting only applies when UseJBossWebLoader=false.\n",[500,14369,14370],{"class":502,"line":641},[500,14371,14372],{}," -->\n",[500,14374,14375],{"class":502,"line":647},[500,14376,14377],{}," \u003Cattribute name=\"FilteredPackages\">javax.servlet,org.slf4j\u003C/attribute>\n",[335,14379,14381],{"id":14380},"lessons-learned","Lessons learned",[343,14383,14384,14397,14400,14403,14406,14409,14436],{},[346,14385,14386,14387,14390,14391,14396],{},"If you’re starting a new project choose exactly one logging framework and stick with it. I recommend\nusing ",[22,14388,12641],{"href":12639,"rel":14389},[26]," for its stable API and ",[22,14392,14395],{"href":14393,"rel":14394},"http://logback.qos.ch/",[26],"Logback"," as backend at the\nmoment.",[346,14398,14399],{},"Don’t create log wrappers if they provide no additional value and if you really, really need to implement your own,\nuse the logging framework’s API instead of thinking up your own.",[346,14401,14402],{},"Dependencies over a lot of distinct components which should be assembled into a single EAR can be quite hairy. Think\nvery well about how to partition your application and which components have common dependencies which can be separated\ninto a parent POM.",[346,14404,14405],{},"The one configuration option that might solve all of your problems is not documented very well. Scan through your (\ncommented) configuration files once in a while.",[346,14407,14408],{},"Use the tools your IDE provides. It makes refactoring so much easier if you know what your IDE is capable of.",[346,14410,14411,14412,719,14417,180,14422,180,14427,180,14432,5192],{},"Never underestimate the grief that class loading issues can cause\nyou. ",[22,14413,14416],{"href":14414,"rel":14415},"http://onjava.com/lpt/a/5586",[26],"Really",[22,14418,14421],{"href":14419,"rel":14420},"http://www.devx.com/Java/Article/31614/1954?pf=true",[26],"read",[22,14423,14426],{"href":14424,"rel":14425},"http://onjava.com/lpt/a/4337",[26],"up",[22,14428,14431],{"href":14429,"rel":14430},"http://onjava.com/lpt/a/5795",[26],"on",[22,14433,3546],{"href":14434,"rel":14435},"http://www.theserverside.com/news/1364680/Understanding-J2EE-Application-Server-ClassLoading-Architectures",[26],[346,14437,14438],{},"Continuous integration the way we do it is nice but it doesn’t help you with class loading issues appearing in your\nJEE application server.",[18,14440,14441,14442,14445],{},"How about you? Did you experience similar stories with large legacy applications? What did you take from it? We’re\nthrilled to hear ",[469,14443,14444],{},"your"," “Development War Stories” in the comments!",[335,14447,14449],{"id":14448},"attributions","Attributions",[343,14451,14452],{},[346,14453,14454,14459,14460,719,14465],{},[22,14455,14458],{"href":14456,"rel":14457},"http://www.flickr.com/photos/lorenjavier/3537572809/",[26],"Seven Dwarves “Homeward Bound” statue by Jim Shore as soon from the China Closet on Main Street","\nby ",[22,14461,14464],{"href":14462,"rel":14463},"http://www.flickr.com/photos/lorenjavier/",[26],"Loren Javier",[22,14466,14469],{"href":14467,"rel":14468},"http://creativecommons.org/licenses/by-nd/2.0/",[26],"CC BY-ND 2.0",[1017,14471,14472],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":34,"searchDepth":98,"depth":98,"links":14474},[14475,14476],{"id":14380,"depth":98,"text":14381},{"id":14448,"depth":98,"text":14449},[1029],"2011-06-30T09:37:07","At Synyx we’re currently taking care of a rather large legacy project for one of our customers in the course of\\nour Code Clinic services. The project comprises several components\\nsuch as a fat client implemented with a custom UI framework on top of Swing, a bulky web application using a mixture of\\ncustom and obsolete frameworks, and a lot of asynchronously running jobs to process input from other systems involving\\ncustom XSL transformations and a heap of stored procedures in a Oracle 9i database. You get the picture, it’s the\\nprototype of a legacy system.","https://synyx.de/blog/the-tale-of-jboss-and-the-7-little-logging-frameworks/",{},"/blog/the-tale-of-jboss-and-the-7-little-logging-frameworks",{"title":12588,"description":14484},"At Synyx we’re currently taking care of a rather large legacy project for one of our customers in the course of\nour Code Clinic services. The project comprises several components\nsuch as a fat client implemented with a custom UI framework on top of Swing, a bulky web application using a mixture of\ncustom and obsolete frameworks, and a lot of asynchronously running jobs to process input from other systems involving\ncustom XSL transformations and a heap of stored procedures in a Oracle 9i database. You get the picture, it’s the\nprototype of a legacy system.","blog/the-tale-of-jboss-and-the-7-little-logging-frameworks",[12539,14487,14488,869,11570,14489,14490,9117],"class-loader","commons-logging","log4j","slf4j","At Synyx we’re currently taking care of a rather large legacy project for one of our customers in the course of our Code Clinic services. The project comprises several components…","ppWPHxzoN11zneoOW2aVj0eAXHnxYFD4vHMnjUtyqHs",{"id":14494,"title":14495,"author":14496,"body":14497,"category":14586,"date":14587,"description":14588,"extension":104,"link":14589,"meta":14590,"navigation":107,"path":14591,"seo":14592,"slug":14594,"stem":14595,"tags":14596,"teaser":14599,"__hash__":14600},"blog/blog/devoxx-2010-revisited.md","Devoxx 2010 – Revisited using Parleys",[2944],{"type":11,"value":14498,"toc":14584},[14499,14502,14523,14526,14529,14532,14541,14550,14558,14565,14571],[14,14500,14495],{"id":14501},"devoxx-2010-revisited-using-parleys",[18,14503,14504,14505,14509,14510,12279,14514,12279,14518,14522],{},"This year in November three of my colleagues and I were visiting the best Java conference\never – ",[22,14506,179],{"href":14507,"rel":14508},"http://devoxx.com/display/Devoxx2K10/Home",[26]," in Antwerp (blogs\nhere: ",[22,14511,4080],{"href":14512,"rel":14513},"http://blog.synyx.de/2010/11/devoxx-2010-part-1/",[26],[22,14515,6758],{"href":14516,"rel":14517},"http://blog.synyx.de/2010/11/devoxx-university/",[26],[22,14519,6762],{"href":14520,"rel":14521},"http://blog.synyx.de/2010/11/devoxx-2010-part-2/",[26],").\nNow, since more than a month of time went by its time for recall and see, what about Devoxx is still present in my mind.",[18,14524,14525],{},"The fist thing is, that the conference was very very well organized and the location was almost perfect.",[18,14527,14528],{},"The second thing, that is still in my mind is the awful “Coffee” they served. This stuff was not worth to be called\ncoffee and for a Java conference, this seems to be just wrong, guys ;). Please make the tickets to be more expensive\nnext year and get some good coffee-machines for the extra money you earn. The nerds will thank you.",[18,14530,14531],{},"But the most important thing I remember is the quality of talks and speakers. They helped me a lot and improved my work\nalready. I think Synyx invested the money for Devoxx just right, because all of us enjoyed the conference and also have\na whole new set of ideas and tools ready for production now.",[18,14533,14534,14535,14540],{},"Unfortunately, as on every conference I missed some of the great talks I was really interested in because I was busy\nseeing other talks in parallel. As I said, this happens on every conference. But Devoxx got kind of around that\nusing ",[22,14536,14539],{"href":14537,"rel":14538},"http://parleys.com",[26],"Parleys.com",", where you can watch all talks of Devoxx for around 100$ a year. Over the last\ndays, I’ve seen just about all of the ones I could not see because they were conflicting with other talks and it is just\ngreat. It seems its the perfect thing to do in nights around Christmas if you neither have Internet around, nor a TV\naccessible.",[18,14542,14543,14544,14549],{},"Parleys is a fancy webbased application (",[22,14545,14548],{"href":14546,"rel":14547},"http://get.adobe.com/air/",[26],"Adobe Air",") where you can browse through all of the\ntalks and watch them as you were used to at the Devoxx’ big cinema-Screens: They offer the video of the speaker next to\nthe slides as well as the demo-videos in a synchronous manner. You get also some nice options like enlarging one of the\nvideos, rate them, and so on.",[18,14551,14552,14553,14557],{},"But the best feature is definitely the “download” feature, since this allows you to watch the talks offline at your\ngrandmothers using the Desktop-Client. By the way, on my Linux machines I could not install this bastard using Firefox\nand ",[22,14554,14555],{"href":14555,"rel":14556},"http://parleys.com/desktop/",[26]," but i had to wget the .air file as to be found in the pages sourcecode and then run\nthe installer manually using a pre-installed Adobe Air.",[18,14559,14560,14561,14564],{},"So over the last days I’ve seen some great talks from Devoxx 2010 which I could not attend at the conference and I\ndefinitely shouldn’t have missed. Also, if you were not at Devoxx this is a perfect thing to get some of the feeling and\nknowledge at a nice price. Parleys also helped me a lot the last weeks at work where we\nintroduced ",[22,14562,12140],{"href":12138,"rel":14563},[26]," as the frontend technology of a new application. I could hand my\ncolleagues the 3 hour University-Talk about Wicket as an introduction which turned out to be great since they could\nstart using it right away afterwards.",[18,14566,14567],{},[32,14568],{"alt":14569,"src":14570},"\"Talk in Parleys\"","https://media.synyx.de/uploads//2010/12/devoxx_revisited.jpg",[18,14572,14573,14574,719,14577,1368,14580,14583],{},"The downside of Parleys in my opinion is the way too fancy Adobe Air style of it. As this might be great for designers\nand “regulars”, I’d rather prefer more usability and performance while browsing and searching the videos over some nice\nanimations and effects. The developers of Parleys should consider to include some fast and non-fancy search-able and\nfilterable list of all talks with options like ",[168,14575,14576],{},"queue to download",[168,14578,14579],{},"mark as to be watched",[168,14581,14582],{},"watch",". I really hope\nto see these features announced when we’re coming back to Devoxx 2011.",{"title":34,"searchDepth":98,"depth":98,"links":14585},[],[1029],"2010-12-28T17:46:47","This year in November three of my colleagues and I were visiting the best Java conference\\never – Devoxx in Antwerp (blogs\\nhere: 1,2,3).\\nNow, since more than a month of time went by its time for recall and see, what about Devoxx is still present in my mind.","https://synyx.de/blog/devoxx-2010-revisited/",{},"/blog/devoxx-2010-revisited",{"title":14495,"description":14593},"This year in November three of my colleagues and I were visiting the best Java conference\never – Devoxx in Antwerp (blogs\nhere: 1,2,3).\nNow, since more than a month of time went by its time for recall and see, what about Devoxx is still present in my mind.","devoxx-2010-revisited","blog/devoxx-2010-revisited",[6379,869,14597,14598],"parleys","wicket","This year in November three of my colleagues and I were visiting the best Java conference ever – Devoxx in Antwerp (blogs here: 1,2,3). Now, since more than a month…","2q1f9aI8s6IQ0sxryqynGHaJNhc6eD_d5-mQgdXOj5M",{"id":14602,"title":14603,"author":14604,"body":14606,"category":14682,"date":14683,"description":14684,"extension":104,"link":14685,"meta":14686,"navigation":107,"path":14687,"seo":14688,"slug":14610,"stem":14690,"tags":14691,"teaser":14694,"__hash__":14695},"blog/blog/devoxx-university.md","Devoxx University",[14605],"hopf",{"type":11,"value":14607,"toc":14680},[14608,14611,14619,14632,14659,14674,14677],[14,14609,14603],{"id":14610},"devoxx-university",[18,14612,14613,14614,14618],{},"As the university days on Devoxx are nearly finished I’d like to summarize some of the more interesting talks that\nhappened during the first two days. Marc already\nwrote ",[22,14615,14617],{"href":14512,"rel":14616},[26],"some words on the conference itself"," so I will focus on the\ntalks.",[18,14620,14621,14622,14625,14626,14631],{},"Monday started off with ",[22,14623,12134],{"href":12132,"rel":14624},[26]," talking about Productive Programmers. The topics he\ncovered are quite basic but nevertheless important to know for every developer. The first part showed how to optimize\nyour daily work, circling around the terms automation, acceleration, canonicality and focus. Some useful tools and\ntechniques were presented e.g. an ",[22,14627,14630],{"href":14628,"rel":14629},"http://www.mousefeed.com/",[26],"Eclipse plugin for learning keyboard shortcuts"," that\nsounds really nice: Every time you are using the mouse to navigate or select an action a message is displayed that tells\nyou how to do the same thing just with the keyboard. I’d be glad to have a plugin like this for Netbeans! During the\nsecond part a lot of best practices for coding were covered, always good to keep those in mind. All in all a very\ninspiring talk and a good start of the week.",[18,14633,14634,14635,14640,14641,14646,14647,14652,14653,14658],{},"For the afternoon session I chose ",[22,14636,14639],{"href":14637,"rel":14638},"http://www.mongodb.org/",[26],"MongoDB",", the document oriented database. Being already\nfamiliar with the basic concepts from ",[22,14642,14645],{"href":14643,"rel":14644},"http://blog.synyx.de/2010/08/froscon-2010/",[26],"FrOSCon"," and several podcasts I’ve\nbeen especially interested in seeing it in action using Java. Two basic approaches were introduced: The raw Java driver\nthat is developed by the MongoDB team lets you work on quite a low level handling Maps of Strings and Objects. A more\nsophisticated approach is to use the community developed ",[22,14648,14651],{"href":14649,"rel":14650},"http://code.google.com/p/morphia/",[26],"Morphia","-driver which\nallows to annotate POJOs just like when using JPA. I wouldn’t have expected to see such a nice abstraction for MongoDB\nyet, definitely something to keep an eye on. I am curious if ",[22,14654,14657],{"href":14655,"rel":14656},"http://www.springsource.org/spring-data",[26],"Spring Data"," can\noffer something similar in the near future.",[18,14660,14661,14662,14667,14668,14673],{},"On tuesday I had to get up extra early: the Scala lab offered by Dick Wall of the ",[22,14663,14666],{"href":14664,"rel":14665},"http://www.javaposse.com/",[26],"Javaposse","\nand Bill Venners was scheduled for the BOF rooms which only fit around 50 people. Definitely the highlight of the\nconference so far as with only few attendees there was a lot of time for excercises and support by these two experts. I\nhope I can start using Scala at work soon, ",[22,14669,14672],{"href":14670,"rel":14671},"http://www.scalatest.org/",[26],"ScalaTest"," is supposed to be a good starting\npoint for learning the language without having to integrate it in a production system.",[18,14675,14676],{},"Back to Java in the afternoon: Emmanuel Bernard demonstrated new features in Hibernate and JPA 2. Besides the typesafe\nCriteria API it was really nice to see the fluent APIs used for Hibernate Search. Seems like Hibernate Search can free\nyou from a lot of programming work when integrating Lucene and Hibernate.",[18,14678,14679],{},"Looking forward to the rest of the conference which will surely bring more interesting talks during the rest of the\nweek!",{"title":34,"searchDepth":98,"depth":98,"links":14681},[],[1029],"2010-11-17T09:13:24","As the university days on Devoxx are nearly finished I’d like to summarize some of the more interesting talks that\\nhappened during the first two days. Marc already\\nwrote some words on the conference itself so I will focus on the\\ntalks.","https://synyx.de/blog/devoxx-university/",{},"/blog/devoxx-university",{"title":14603,"description":14689},"As the university days on Devoxx are nearly finished I’d like to summarize some of the more interesting talks that\nhappened during the first two days. Marc already\nwrote some words on the conference itself so I will focus on the\ntalks.","blog/devoxx-university",[113,6379,869,14692,14693],"mongodb","scala","As the university days on Devoxx are nearly finished I’d like to summarize some of the more interesting talks that happened during the first two days. Marc already wrote some…","Fa30-dvVDcsLnSS5JZec5ayVcHOjfehzKa_MalPRWgY",{"id":14697,"title":14698,"author":14699,"body":14701,"category":14963,"date":14965,"description":14966,"extension":104,"link":14967,"meta":14968,"navigation":107,"path":14969,"seo":14970,"slug":14705,"stem":14972,"tags":14973,"teaser":14976,"__hash__":14977},"blog/blog/on-cross-device-mobile-development-part-2.md","On cross-device mobile development – Part 2",[14700],"krupicka",{"type":11,"value":14702,"toc":14956},[14703,14706,14719,14723,14746,14750,14782,14786,14810,14814,14821,14825,14828,14832,14836,14860,14864,14883,14887,14902,14906,14937,14941,14953],[14,14704,14698],{"id":14705},"on-cross-device-mobile-development-part-2",[18,14707,14708,14709,14714,14715,14718],{},"In the ",[22,14710,14713],{"href":14711,"rel":14712},"http://mobile.synyx.de/2010/08/on-cross-device-mobile-development-part-1/",[26],"previous part"," of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as ",[168,14716,14717],{},"the web\nstack","). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …",[335,14720,14722],{"id":14721},"what-does-native-have-what-web-stack-hasnt","What does native have, what web stack hasn’t?",[18,14724,14725,14726,14729,14730,14733,14734,14737,14738,14741,14742,14745],{},"If you want the answer to this question boiled down to one simple word: ",[469,14727,14728],{},"abstraction",". In an application you don’t\nwant to think in terms of ",[405,14731,14732],{},"\u003Cul>"," elements, to which you append ",[405,14735,14736],{},"\u003Cli>"," elements, styled adequately with CSS to harbor\nitems populated from SQLite statements and react to code which is painstakingly attached to every list item to react to\ndifferent user inputs. You would usually think about the problem in terms of — for example — a ",[405,14739,14740],{},"ListController"," and an\nattached ",[405,14743,14744],{},"DataSource",", freeing you from the task of layouting your list. And you don’t want to have to code all this\nboilerplate from scratch. It is not the problem you want to solve. It is something left for a framework.",[335,14747,14749],{"id":14748},"competing-with-native-sdks","Competing with native SDKs",[18,14751,14752,14753,14758,14759,14764,14765,14767,14768,14773,14774,14777,14778,14781],{},"Even before the days of mobile applications, web application developers pushed hard to create frameworks to match up\nagainst their competitors from the native world. An increasing number of those are now also reconsidering deployment on\nsmall mobile devices and are joined by new JavaScript frameworks especially tailored to mobile\ndevices. ",[22,14754,14757],{"href":14755,"rel":14756},"https://web.archive.org/web/20210302121558/https://phonegap.com/",[26],"PhoneGap"," — which we refered to already in\nthe first part of the series — is\nnow ",[22,14760,14763],{"href":14761,"rel":14762},"https://web.archive.org/web/20140122061413/http://phonegap.com/2010/07/19/it%e2%80%99s-easier-than-ever-for-symbian-developers-to-build-mobile-apps-with-phonegap/",[26],"fully embraced","\nby Nokia for their Symbian smartphone operating system. Palm has his whole mobile user experience built around ",[168,14766,14717],{},", accordingly naming their operating system ",[22,14769,14772],{"href":14770,"rel":14771},"http://developer.palm.com/",[26],"WebOS",". The need for mobile ",[168,14775,14776],{},"web stack","\nframeworks is growing. The rest of this article will introduce some of those frameworks, but first a short recap of what\n",[469,14779,14780],{},"abstractions"," we would expect from such a framework, compared to their native siblings:",[3721,14783,14785],{"id":14784},"models","Models",[18,14787,14788,14789,14794,14795,14802,14803,707],{},"The way the data is retrieved and stored on the device comprises our data model. Because of constraints in memory\nfootprint and also because of security considerations, mobile SDKs should try to provide abstraction layers for your\ndata storage. As an example, the iPhone SDK incorporates a subsystem\ncalled ",[22,14790,14793],{"href":14791,"rel":14792},"http://developer.apple.com/iphone/library/documentation/DataManagement/Conceptual/iPhoneCoreData01/",[26],"CoreData",".\nIt provides modelling tools and stub generation to easily integrate with ",[22,14796,14799],{"href":14797,"rel":14798},"http://developer.apple.com/iphone/library/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html",[26],[405,14800,14801],{},"UITableView","\ncontrols. Android, while staying more on the lower SQLite level, also provides similar integration to their native ",[22,14804,14807],{"href":14805,"rel":14806},"http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html",[26],[405,14808,14809],{},"ListView",[3721,14811,14813],{"id":14812},"views-user-interface-ui-elements","Views & user interface (UI) elements",[18,14815,14816,14820],{},[32,14817],{"alt":14818,"src":14819},"\"iPhone UI Controls\"","https://media.synyx.de/uploads//2010/07/iPhone-UI-Controls-e1280510112988.png","\nEvery mobile SDK contains a selection of useful UI elements: buttons, sliders, text entry controls, switches and on a\nlarger scale prebuilt view elements for tabular data, images, display of rich text or maps for geolocative services.\nUsually every of those elements or views exhibits a callback interface tailored towards the particular item. These can\nbecome quite sophisticated: views for tabular data call back for touches on a particular row and/or column or map views\nrequesting to redraw the viewport according to new coordinates after scrolling occured.",[3721,14822,14824],{"id":14823},"controllers","Controllers",[18,14826,14827],{},"The backbone of every applications, they are the heavy lifters which glue your models and views together. Often the\ntasks to be done — for example managing the display of overviews and increasingly detailled item views or also an\neditable table view of data from an underlying data source — are abstract and common enough, that a selection of generic\ncontrollers can eliminate boilerplate code and provide a feature rich set of actions out of the box.",[133,14829,14831],{"id":14830},"javascript-frameworks-the-small-the-huge-and-the-familiar","JavaScript frameworks … the small, the huge and the familiar",[3721,14833,14835],{"id":14834},"jqtouch","jQTouch",[18,14837,14838,14839,14843,14844,14848,14849,14856,14857,14859],{},"Our first framework is perhaps the one which contradicts our demands the most. ",[22,14840,14835],{"href":14841,"rel":14842},"http://jqtouch.com",[26]," is a\nlightweight layer on the ever-present ",[22,14845,8483],{"href":14846,"rel":14847},"http://jquery.com",[26]," library. And with lightweight we are talking about a\nmeager 577 source lines of code. Obviously one cannot expect a lot of the desired abstractions, yet still jQTouch has\nits own eligibility. It provides mainly an implementation of one of the most common view abstractions — a stack of menus\nand toolbars to navigate forward and backward — bundled with finely tuned themes for an iPhone-like or more generic\nmobile UI. While you still have to provide the code for more complex controllers and also data model abstraction, it can\nbe used for those cases, where an already deployed web application employing jQuery, should be quickly ported to a\nmobile device. Furthermore it features resource-preloading as also access to geolocation APIs in mobile WebKit\nimplementations. The missing model and controller code can be adapted from existing desktop browser jQuery extensions,\nwhich are numerous. It is now maintained by Jonathan Stark, whose book ",[22,14850,14853],{"href":14851,"rel":14852},"http://building-iphone-apps.labs.oreilly.com/",[26],[168,14854,14855],{},"Building iPhone Apps with HTML, CSS &\nJavaScript"," we already mentioned in part one of this article series and\nwhich is still a great resource for ",[168,14858,14776],{}," development on mobile devices.",[3721,14861,14863],{"id":14862},"sencha-touch","Sencha Touch",[18,14865,14866,14871,14872,14876,14877,14882],{},[22,14867,14870],{"href":14868,"rel":14869},"http://www.sencha.com/",[26],"Sencha Labs"," (formerly ExtJS) are already well-known for their desktop browser frameworks.\nWith ",[22,14873,14863],{"href":14874,"rel":14875},"http://www.sencha.com/products/touch/",[26]," they provide abstractions for many of the use cases we talked\nabout above. This includes controllers for tool- or tabbar navigation, data sources which can be attached to analogous\nviews — which then are updated automatically, common entry controls like textfields or date pickers, map views and media\nlike audio and video. Every control provided has an extensive list of event callbacks, that you can easily attach your\nfunctions to. From the ",[22,14878,14881],{"href":14879,"rel":14880},"http://www.sencha.com/products/touch/demos.php",[26],"demos"," it becomes clear that they also push hard\ntowards the emerging tablet devices by providing a full-stack framework for even sophisticated applications. It should\nbe noted though, that it is dual-licensed, with commercial use entailing a (small) fee.",[3721,14884,14886],{"id":14885},"jquery-mobile","jQuery mobile",[18,14888,14889,14890,14895,14896,14901],{},"For people, who in the past have worked with ",[22,14891,14894],{"href":14892,"rel":14893},"http://jqueryui.com",[26],"jQuery UI"," for web applications, John Resig recently\nannounced ",[22,14897,14900],{"href":14898,"rel":14899},"http://jquerymobile.com/",[26],"jQuery Mobile",". While it is still in planning & internal beta, it could prove as\nthe missing link from the formerly mentioned jQTouch. Projecting from the existing jQuery UI framework, this would\nprovide a good amount of UI controls and common view abstractions, while maintaining the slick feeling of traditional\njQuery programming. It will also come with themes, which match typical native mobile controls and a strong mission\nstatement to make it a real cross-platform alternative, beyond the iOS platform usually targeted by other frameworks.",[3721,14903,14905],{"id":14904},"google-web-toolkit","Google Web Toolkit",[18,14907,14908,14909,14913,14914,14919,14920,1799,14925,14930,14931,14936],{},"Last but definitely not least, we have to include an old familiar\nfriend: ",[22,14910,14905],{"href":14911,"rel":14912},"http://code.google.com/webtoolkit/",[26]," (GWT) has already matured for desktop browsers. It also\nhas something to provide, which other JavaScript frameworks lack: extensive IDE support with a strictly typed language\nunderneath. Especially Android developers will feel more at home, when developing with Java. While originally targeted\nat desktop browsers, its extensibility makes it easy to retarget mobile\nplatforms. ",[22,14915,14918],{"href":14916,"rel":14917},"http://code.google.com/p/gwt-mobile-webkit/",[26],"GWT Mobile WebKit"," extends GWT with support for touch\ninterfaces and also bundles libraries to\nabstract ",[22,14921,14924],{"href":14922,"rel":14923},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Geolocation",[26],"geolocation services",[22,14926,14929],{"href":14927,"rel":14928},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Database",[26],"SQLite"," database — something even\nmissing from the original Android SDK. Views can be created programmatically\nor ",[22,14932,14935],{"href":14933,"rel":14934},"http://code.google.com/webtoolkit/doc/latest/DevGuideUiBinder.html",[26],"declaratively"," and provide a rich API for\ncallbacks on user input.",[335,14938,14940],{"id":14939},"what-is-left-to-be-said","What is left to be said",[18,14942,2490,14943,14945,14946,14948,14949,14952],{},[168,14944,14776],{}," mobile development community is certainly on the move to sophisticated frameworks. From small\nmicroframeworks to full-stack frameworks like ",[168,14947,14863],{}," or ",[168,14950,14951],{},"GWT",", a range of tastes for different development\nstyles is served. While we tried to give a bigger perspective on what is available, we still haven’t even touched topics\nlike the upcoming HTML5 — which together with CSS3 will bring fast animations and sophisticated graphics — or actual\ncross-device behaviour. What can be definitely said is, that these developments — wether or not one decides to jump on\nthis bandwagon — increase the diversity of mobile development and provide new insights in how we think about mobile\napplication development.",[18,14954,14955],{},"To be continued some time …",{"title":34,"searchDepth":98,"depth":98,"links":14957},[14958,14959,14962],{"id":14721,"depth":98,"text":14722},{"id":14748,"depth":98,"text":14749,"children":14960},[14961],{"id":14830,"depth":249,"text":14831},{"id":14939,"depth":98,"text":14940},[14964],"mobile-blog","2010-09-07T12:00:39","In the previous part of this series we took\\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\\nfor, shouldn’t it? Not quite …","https://synyx.de/blog/on-cross-device-mobile-development-part-2/",{},"/blog/on-cross-device-mobile-development-part-2",{"title":14698,"description":14971},"In the previous part of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …","blog/on-cross-device-mobile-development-part-2",[1683,14974,496,14975,670],"css","iphone","In the previous part of this series we took a look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web stack). A…","YieTuihm_5oZzpoNHWVn3JDuz9uBqr4EfoBq26hjIl0",{"id":14979,"title":14980,"author":14981,"body":14982,"category":16501,"date":16503,"description":16504,"extension":104,"link":16505,"meta":16506,"navigation":107,"path":16507,"seo":16508,"slug":14986,"stem":16510,"tags":16511,"teaser":16512,"__hash__":16513},"blog/blog/on-cross-device-mobile-development-part-1.md","On cross-device mobile development – Part 1",[14700],{"type":11,"value":14983,"toc":16495},[14984,14987,14994,14998,15001,15005,15014,15319,15322,16014,16017,16218,16229,16276,16279,16447,16449,16458,16461,16468,16472,16475,16489,16492],[14,14985,14980],{"id":14986},"on-cross-device-mobile-development-part-1",[18,14988,14989,14990,14993],{},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. ",[168,14991,14992],{},"That’s the theory at least."," In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.",[335,14995,14997],{"id":14996},"its-not-in-the-browser","It’s (not) in the browser",[18,14999,15000],{},"Developing applications with HTML, CSS & Javascript is a fundamentally different experience. It already is on the\nnormal desktop with its widescreen browsers. On a mobile device it becomes a game changer. We are accustomed to\ngraphical user interface (GUI) toolkits with their more direct manipulation of data through on-screen elements like\nbuttons. While browsers and HTML provide their own set of UI elements and the ability to wire events like clicks to\nJavascript callbacks, they lack a lot of the bells and whistles. Especially in the handling of client-side data — the\nmodel in model-view-controller (MVC) so to say — and the controller abstraction even modern browsers lack the feature\nrichness of common native SDKs. In the rest of this article we will give a short overview on which web technologies map\nto which concept in the MVC approach and how you would basically structure an application based on such technologies.",[335,15002,15004],{"id":15003},"of-models-views-controllers","Of models, views & controllers",[18,15006,15007,15008,15013],{},"In ",[22,15009,15012],{"href":15010,"rel":15011},"http://building-iphone-apps.labs.oreilly.com/ch04.html",[26],"“Building iPhone Apps with HTML, CSS and JavaScript”","\nJonathan Stark describes how to turn websites into single page iPhone applications. Views are implemented straight\nforward with plain HTML and CSS, throwing in some bits and pieces to make browser standard behavior of HTML elements\nmore native like. To give an example, we will show how to create a graphical navigation bar and switch between different\nscreens. First we need some HTML and CSS snippets to create the layout:",[492,15015,15017],{"className":494,"code":15016,"language":496,"meta":34,"style":34},"\u003Cul id=\"navigation\">\n \u003Cli>\u003Ca class=\"current\" href=\"#home\">Home\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#new\">New things\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#favorites\">I like those\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#more\">More content\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#info\">About\u003C/a>\u003C/li>\n\u003C/ul>\n\u003Cdiv class=\"view\" id=\"home\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"new\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"favorites\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"more\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"info\">...\u003C/div>\n",[405,15018,15019,15034,15072,15100,15128,15156,15184,15192,15219,15244,15269,15294],{"__ignoreMap":34},[500,15020,15021,15023,15025,15027,15029,15032],{"class":502,"line":503},[500,15022,507],{"class":506},[500,15024,343],{"class":510},[500,15026,654],{"class":513},[500,15028,517],{"class":506},[500,15030,15031],{"class":520},"\"navigation\"",[500,15033,532],{"class":506},[500,15035,15036,15038,15040,15043,15045,15048,15050,15053,15056,15058,15061,15064,15066,15068,15070],{"class":502,"line":98},[500,15037,537],{"class":506},[500,15039,346],{"class":510},[500,15041,15042],{"class":506},">\u003C",[500,15044,22],{"class":510},[500,15046,15047],{"class":513}," class",[500,15049,517],{"class":506},[500,15051,15052],{"class":520},"\"current\"",[500,15054,15055],{"class":513}," href",[500,15057,517],{"class":506},[500,15059,15060],{"class":520},"\"#home\"",[500,15062,15063],{"class":506},">Home\u003C/",[500,15065,22],{"class":510},[500,15067,661],{"class":506},[500,15069,346],{"class":510},[500,15071,532],{"class":506},[500,15073,15074,15076,15078,15080,15082,15084,15086,15089,15092,15094,15096,15098],{"class":502,"line":249},[500,15075,537],{"class":506},[500,15077,346],{"class":510},[500,15079,15042],{"class":506},[500,15081,22],{"class":510},[500,15083,15055],{"class":513},[500,15085,517],{"class":506},[500,15087,15088],{"class":520},"\"#new\"",[500,15090,15091],{"class":506},">New things\u003C/",[500,15093,22],{"class":510},[500,15095,661],{"class":506},[500,15097,346],{"class":510},[500,15099,532],{"class":506},[500,15101,15102,15104,15106,15108,15110,15112,15114,15117,15120,15122,15124,15126],{"class":502,"line":583},[500,15103,537],{"class":506},[500,15105,346],{"class":510},[500,15107,15042],{"class":506},[500,15109,22],{"class":510},[500,15111,15055],{"class":513},[500,15113,517],{"class":506},[500,15115,15116],{"class":520},"\"#favorites\"",[500,15118,15119],{"class":506},">I like those\u003C/",[500,15121,22],{"class":510},[500,15123,661],{"class":506},[500,15125,346],{"class":510},[500,15127,532],{"class":506},[500,15129,15130,15132,15134,15136,15138,15140,15142,15145,15148,15150,15152,15154],{"class":502,"line":615},[500,15131,537],{"class":506},[500,15133,346],{"class":510},[500,15135,15042],{"class":506},[500,15137,22],{"class":510},[500,15139,15055],{"class":513},[500,15141,517],{"class":506},[500,15143,15144],{"class":520},"\"#more\"",[500,15146,15147],{"class":506},">More content\u003C/",[500,15149,22],{"class":510},[500,15151,661],{"class":506},[500,15153,346],{"class":510},[500,15155,532],{"class":506},[500,15157,15158,15160,15162,15164,15166,15168,15170,15173,15176,15178,15180,15182],{"class":502,"line":621},[500,15159,537],{"class":506},[500,15161,346],{"class":510},[500,15163,15042],{"class":506},[500,15165,22],{"class":510},[500,15167,15055],{"class":513},[500,15169,517],{"class":506},[500,15171,15172],{"class":520},"\"#info\"",[500,15174,15175],{"class":506},">About\u003C/",[500,15177,22],{"class":510},[500,15179,661],{"class":506},[500,15181,346],{"class":510},[500,15183,532],{"class":506},[500,15185,15186,15188,15190],{"class":502,"line":631},[500,15187,634],{"class":506},[500,15189,343],{"class":510},[500,15191,532],{"class":506},[500,15193,15194,15196,15198,15200,15202,15205,15207,15209,15212,15215,15217],{"class":502,"line":641},[500,15195,507],{"class":506},[500,15197,481],{"class":510},[500,15199,15047],{"class":513},[500,15201,517],{"class":506},[500,15203,15204],{"class":520},"\"view\"",[500,15206,654],{"class":513},[500,15208,517],{"class":506},[500,15210,15211],{"class":520},"\"home\"",[500,15213,15214],{"class":506},">...\u003C/",[500,15216,481],{"class":510},[500,15218,532],{"class":506},[500,15220,15221,15223,15225,15227,15229,15231,15233,15235,15238,15240,15242],{"class":502,"line":647},[500,15222,507],{"class":506},[500,15224,481],{"class":510},[500,15226,15047],{"class":513},[500,15228,517],{"class":506},[500,15230,15204],{"class":520},[500,15232,654],{"class":513},[500,15234,517],{"class":506},[500,15236,15237],{"class":520},"\"new\"",[500,15239,15214],{"class":506},[500,15241,481],{"class":510},[500,15243,532],{"class":506},[500,15245,15246,15248,15250,15252,15254,15256,15258,15260,15263,15265,15267],{"class":502,"line":805},[500,15247,507],{"class":506},[500,15249,481],{"class":510},[500,15251,15047],{"class":513},[500,15253,517],{"class":506},[500,15255,15204],{"class":520},[500,15257,654],{"class":513},[500,15259,517],{"class":506},[500,15261,15262],{"class":520},"\"favorites\"",[500,15264,15214],{"class":506},[500,15266,481],{"class":510},[500,15268,532],{"class":506},[500,15270,15271,15273,15275,15277,15279,15281,15283,15285,15288,15290,15292],{"class":502,"line":810},[500,15272,507],{"class":506},[500,15274,481],{"class":510},[500,15276,15047],{"class":513},[500,15278,517],{"class":506},[500,15280,15204],{"class":520},[500,15282,654],{"class":513},[500,15284,517],{"class":506},[500,15286,15287],{"class":520},"\"more\"",[500,15289,15214],{"class":506},[500,15291,481],{"class":510},[500,15293,532],{"class":506},[500,15295,15296,15298,15300,15302,15304,15306,15308,15310,15313,15315,15317],{"class":502,"line":826},[500,15297,507],{"class":506},[500,15299,481],{"class":510},[500,15301,15047],{"class":513},[500,15303,517],{"class":506},[500,15305,15204],{"class":520},[500,15307,654],{"class":513},[500,15309,517],{"class":506},[500,15311,15312],{"class":520},"\"info\"",[500,15314,15214],{"class":506},[500,15316,481],{"class":510},[500,15318,532],{"class":506},[18,15320,15321],{},"The navigational items are then replaced with nice button graphics via CSS and positioned on the page.",[492,15323,15326],{"className":15324,"code":15325,"language":14974,"meta":34,"style":34},"language-css shiki shiki-themes github-light github-dark","/* navigation is a fixed block */\n#navigation {\n position: fixed;\n top: 0px;\n left: 0px;\n width: 320px;\n margin: 0;\n padding: 0;\n}\n#navigation li {\n display: block;\n position: absolute;\n}\n#navigation li a {\n display: block;\n position: absolute;\n height: 48px;\n width: 80px;\n top: 0px;\n text-indent: -9999px;\n}\na[href=\"#home\"] {\n background: url(home.png);\n left: 0px;\n}\na[href=\"#home\"].current {\n background: url(home-current.png);\n}\na[href=\"#new\"] {\n background: url(new.png);\n left: 80px;\n}\na[href=\"#new\"].current {\n background: url(new-current.png);\n}\na[href=\"#favorites\"] {\n background: url(favorites.png);\n left: 160px;\n}\na[href=\"#favorites\"].current {\n background: url(favorites-current.png);\n}\na[href=\"#more\"] {\n background: url(more.png);\n left: 240px;\n}\na[href=\"#info\"] {\n background: url(info.png);\n position: fixed;\n width: 48px;\n height: 48px;\n bottom: 16px;\n right: 16px;\n}\n/* finally position the views themselves */\ndiv.view {\n position: absolute;\n top: 48px;\n left: 0px;\n width: 320px;\n height: 396px;\n}\n",[405,15327,15328,15333,15340,15353,15367,15380,15394,15405,15416,15420,15429,15441,15452,15456,15467,15477,15487,15501,15514,15526,15540,15544,15561,15578,15590,15594,15614,15629,15633,15647,15662,15674,15678,15696,15711,15715,15729,15744,15757,15761,15779,15794,15798,15812,15827,15840,15844,15858,15873,15883,15895,15907,15920,15933,15937,15942,15951,15961,15973,15985,15997,16010],{"__ignoreMap":34},[500,15329,15330],{"class":502,"line":503},[500,15331,15332],{"class":3586},"/* navigation is a fixed block */\n",[500,15334,15335,15338],{"class":502,"line":98},[500,15336,15337],{"class":513},"#navigation",[500,15339,690],{"class":506},[500,15341,15342,15345,15348,15351],{"class":502,"line":249},[500,15343,15344],{"class":703}," position",[500,15346,15347],{"class":506},": ",[500,15349,15350],{"class":703},"fixed",[500,15352,3600],{"class":506},[500,15354,15355,15358,15360,15362,15365],{"class":502,"line":583},[500,15356,15357],{"class":703}," top",[500,15359,15347],{"class":506},[500,15361,11608],{"class":703},[500,15363,15364],{"class":677},"px",[500,15366,3600],{"class":506},[500,15368,15369,15372,15374,15376,15378],{"class":502,"line":615},[500,15370,15371],{"class":703}," left",[500,15373,15347],{"class":506},[500,15375,11608],{"class":703},[500,15377,15364],{"class":677},[500,15379,3600],{"class":506},[500,15381,15382,15385,15387,15390,15392],{"class":502,"line":621},[500,15383,15384],{"class":703}," width",[500,15386,15347],{"class":506},[500,15388,15389],{"class":703},"320",[500,15391,15364],{"class":677},[500,15393,3600],{"class":506},[500,15395,15396,15399,15401,15403],{"class":502,"line":631},[500,15397,15398],{"class":703}," margin",[500,15400,15347],{"class":506},[500,15402,11608],{"class":703},[500,15404,3600],{"class":506},[500,15406,15407,15410,15412,15414],{"class":502,"line":641},[500,15408,15409],{"class":703}," padding",[500,15411,15347],{"class":506},[500,15413,11608],{"class":703},[500,15415,3600],{"class":506},[500,15417,15418],{"class":502,"line":647},[500,15419,802],{"class":506},[500,15421,15422,15424,15427],{"class":502,"line":805},[500,15423,15337],{"class":513},[500,15425,15426],{"class":510}," li",[500,15428,690],{"class":506},[500,15430,15431,15434,15436,15439],{"class":502,"line":810},[500,15432,15433],{"class":703}," display",[500,15435,15347],{"class":506},[500,15437,15438],{"class":703},"block",[500,15440,3600],{"class":506},[500,15442,15443,15445,15447,15450],{"class":502,"line":826},[500,15444,15344],{"class":703},[500,15446,15347],{"class":506},[500,15448,15449],{"class":703},"absolute",[500,15451,3600],{"class":506},[500,15453,15454],{"class":502,"line":838},[500,15455,802],{"class":506},[500,15457,15458,15460,15462,15465],{"class":502,"line":937},[500,15459,15337],{"class":513},[500,15461,15426],{"class":510},[500,15463,15464],{"class":510}," a",[500,15466,690],{"class":506},[500,15468,15469,15471,15473,15475],{"class":502,"line":943},[500,15470,15433],{"class":703},[500,15472,15347],{"class":506},[500,15474,15438],{"class":703},[500,15476,3600],{"class":506},[500,15478,15479,15481,15483,15485],{"class":502,"line":948},[500,15480,15344],{"class":703},[500,15482,15347],{"class":506},[500,15484,15449],{"class":703},[500,15486,3600],{"class":506},[500,15488,15489,15492,15494,15497,15499],{"class":502,"line":954},[500,15490,15491],{"class":703}," height",[500,15493,15347],{"class":506},[500,15495,15496],{"class":703},"48",[500,15498,15364],{"class":677},[500,15500,3600],{"class":506},[500,15502,15503,15505,15507,15510,15512],{"class":502,"line":1898},[500,15504,15384],{"class":703},[500,15506,15347],{"class":506},[500,15508,15509],{"class":703},"80",[500,15511,15364],{"class":677},[500,15513,3600],{"class":506},[500,15515,15516,15518,15520,15522,15524],{"class":502,"line":1904},[500,15517,15357],{"class":703},[500,15519,15347],{"class":506},[500,15521,11608],{"class":703},[500,15523,15364],{"class":677},[500,15525,3600],{"class":506},[500,15527,15528,15531,15533,15536,15538],{"class":502,"line":1910},[500,15529,15530],{"class":703}," text-indent",[500,15532,15347],{"class":506},[500,15534,15535],{"class":703},"-9999",[500,15537,15364],{"class":677},[500,15539,3600],{"class":506},[500,15541,15542],{"class":502,"line":1916},[500,15543,802],{"class":506},[500,15545,15546,15548,15551,15554,15556,15558],{"class":502,"line":1922},[500,15547,22],{"class":510},[500,15549,15550],{"class":506},"[",[500,15552,15553],{"class":513},"href",[500,15555,517],{"class":677},[500,15557,15060],{"class":520},[500,15559,15560],{"class":506},"] {\n",[500,15562,15563,15566,15568,15571,15573,15576],{"class":502,"line":1928},[500,15564,15565],{"class":703}," background",[500,15567,15347],{"class":506},[500,15569,15570],{"class":703},"url",[500,15572,713],{"class":506},[500,15574,15575],{"class":728},"home.png",[500,15577,4107],{"class":506},[500,15579,15580,15582,15584,15586,15588],{"class":502,"line":1934},[500,15581,15371],{"class":703},[500,15583,15347],{"class":506},[500,15585,11608],{"class":703},[500,15587,15364],{"class":677},[500,15589,3600],{"class":506},[500,15591,15592],{"class":502,"line":1940},[500,15593,802],{"class":506},[500,15595,15596,15598,15600,15602,15604,15606,15609,15612],{"class":502,"line":1946},[500,15597,22],{"class":510},[500,15599,15550],{"class":506},[500,15601,15553],{"class":513},[500,15603,517],{"class":677},[500,15605,15060],{"class":520},[500,15607,15608],{"class":506},"]",[500,15610,15611],{"class":513},".current",[500,15613,690],{"class":506},[500,15615,15616,15618,15620,15622,15624,15627],{"class":502,"line":1952},[500,15617,15565],{"class":703},[500,15619,15347],{"class":506},[500,15621,15570],{"class":703},[500,15623,713],{"class":506},[500,15625,15626],{"class":728},"home-current.png",[500,15628,4107],{"class":506},[500,15630,15631],{"class":502,"line":1958},[500,15632,802],{"class":506},[500,15634,15635,15637,15639,15641,15643,15645],{"class":502,"line":1964},[500,15636,22],{"class":510},[500,15638,15550],{"class":506},[500,15640,15553],{"class":513},[500,15642,517],{"class":677},[500,15644,15088],{"class":520},[500,15646,15560],{"class":506},[500,15648,15649,15651,15653,15655,15657,15660],{"class":502,"line":1970},[500,15650,15565],{"class":703},[500,15652,15347],{"class":506},[500,15654,15570],{"class":703},[500,15656,713],{"class":506},[500,15658,15659],{"class":728},"new.png",[500,15661,4107],{"class":506},[500,15663,15664,15666,15668,15670,15672],{"class":502,"line":1976},[500,15665,15371],{"class":703},[500,15667,15347],{"class":506},[500,15669,15509],{"class":703},[500,15671,15364],{"class":677},[500,15673,3600],{"class":506},[500,15675,15676],{"class":502,"line":1982},[500,15677,802],{"class":506},[500,15679,15680,15682,15684,15686,15688,15690,15692,15694],{"class":502,"line":1988},[500,15681,22],{"class":510},[500,15683,15550],{"class":506},[500,15685,15553],{"class":513},[500,15687,517],{"class":677},[500,15689,15088],{"class":520},[500,15691,15608],{"class":506},[500,15693,15611],{"class":513},[500,15695,690],{"class":506},[500,15697,15698,15700,15702,15704,15706,15709],{"class":502,"line":1994},[500,15699,15565],{"class":703},[500,15701,15347],{"class":506},[500,15703,15570],{"class":703},[500,15705,713],{"class":506},[500,15707,15708],{"class":728},"new-current.png",[500,15710,4107],{"class":506},[500,15712,15713],{"class":502,"line":2000},[500,15714,802],{"class":506},[500,15716,15717,15719,15721,15723,15725,15727],{"class":502,"line":9047},[500,15718,22],{"class":510},[500,15720,15550],{"class":506},[500,15722,15553],{"class":513},[500,15724,517],{"class":677},[500,15726,15116],{"class":520},[500,15728,15560],{"class":506},[500,15730,15731,15733,15735,15737,15739,15742],{"class":502,"line":9052},[500,15732,15565],{"class":703},[500,15734,15347],{"class":506},[500,15736,15570],{"class":703},[500,15738,713],{"class":506},[500,15740,15741],{"class":728},"favorites.png",[500,15743,4107],{"class":506},[500,15745,15746,15748,15750,15753,15755],{"class":502,"line":9057},[500,15747,15371],{"class":703},[500,15749,15347],{"class":506},[500,15751,15752],{"class":703},"160",[500,15754,15364],{"class":677},[500,15756,3600],{"class":506},[500,15758,15759],{"class":502,"line":9062},[500,15760,802],{"class":506},[500,15762,15763,15765,15767,15769,15771,15773,15775,15777],{"class":502,"line":9067},[500,15764,22],{"class":510},[500,15766,15550],{"class":506},[500,15768,15553],{"class":513},[500,15770,517],{"class":677},[500,15772,15116],{"class":520},[500,15774,15608],{"class":506},[500,15776,15611],{"class":513},[500,15778,690],{"class":506},[500,15780,15781,15783,15785,15787,15789,15792],{"class":502,"line":9072},[500,15782,15565],{"class":703},[500,15784,15347],{"class":506},[500,15786,15570],{"class":703},[500,15788,713],{"class":506},[500,15790,15791],{"class":728},"favorites-current.png",[500,15793,4107],{"class":506},[500,15795,15796],{"class":502,"line":9077},[500,15797,802],{"class":506},[500,15799,15800,15802,15804,15806,15808,15810],{"class":502,"line":9082},[500,15801,22],{"class":510},[500,15803,15550],{"class":506},[500,15805,15553],{"class":513},[500,15807,517],{"class":677},[500,15809,15144],{"class":520},[500,15811,15560],{"class":506},[500,15813,15814,15816,15818,15820,15822,15825],{"class":502,"line":9533},[500,15815,15565],{"class":703},[500,15817,15347],{"class":506},[500,15819,15570],{"class":703},[500,15821,713],{"class":506},[500,15823,15824],{"class":728},"more.png",[500,15826,4107],{"class":506},[500,15828,15829,15831,15833,15836,15838],{"class":502,"line":9539},[500,15830,15371],{"class":703},[500,15832,15347],{"class":506},[500,15834,15835],{"class":703},"240",[500,15837,15364],{"class":677},[500,15839,3600],{"class":506},[500,15841,15842],{"class":502,"line":9545},[500,15843,802],{"class":506},[500,15845,15846,15848,15850,15852,15854,15856],{"class":502,"line":9551},[500,15847,22],{"class":510},[500,15849,15550],{"class":506},[500,15851,15553],{"class":513},[500,15853,517],{"class":677},[500,15855,15172],{"class":520},[500,15857,15560],{"class":506},[500,15859,15860,15862,15864,15866,15868,15871],{"class":502,"line":9557},[500,15861,15565],{"class":703},[500,15863,15347],{"class":506},[500,15865,15570],{"class":703},[500,15867,713],{"class":506},[500,15869,15870],{"class":728},"info.png",[500,15872,4107],{"class":506},[500,15874,15875,15877,15879,15881],{"class":502,"line":9563},[500,15876,15344],{"class":703},[500,15878,15347],{"class":506},[500,15880,15350],{"class":703},[500,15882,3600],{"class":506},[500,15884,15885,15887,15889,15891,15893],{"class":502,"line":9569},[500,15886,15384],{"class":703},[500,15888,15347],{"class":506},[500,15890,15496],{"class":703},[500,15892,15364],{"class":677},[500,15894,3600],{"class":506},[500,15896,15897,15899,15901,15903,15905],{"class":502,"line":9574},[500,15898,15491],{"class":703},[500,15900,15347],{"class":506},[500,15902,15496],{"class":703},[500,15904,15364],{"class":677},[500,15906,3600],{"class":506},[500,15908,15909,15912,15914,15916,15918],{"class":502,"line":9580},[500,15910,15911],{"class":703}," bottom",[500,15913,15347],{"class":506},[500,15915,8240],{"class":703},[500,15917,15364],{"class":677},[500,15919,3600],{"class":506},[500,15921,15922,15925,15927,15929,15931],{"class":502,"line":9586},[500,15923,15924],{"class":703}," right",[500,15926,15347],{"class":506},[500,15928,8240],{"class":703},[500,15930,15364],{"class":677},[500,15932,3600],{"class":506},[500,15934,15935],{"class":502,"line":13503},[500,15936,802],{"class":506},[500,15938,15939],{"class":502,"line":13517},[500,15940,15941],{"class":3586},"/* finally position the views themselves */\n",[500,15943,15944,15946,15949],{"class":502,"line":13531},[500,15945,481],{"class":510},[500,15947,15948],{"class":513},".view",[500,15950,690],{"class":506},[500,15952,15953,15955,15957,15959],{"class":502,"line":13543},[500,15954,15344],{"class":703},[500,15956,15347],{"class":506},[500,15958,15449],{"class":703},[500,15960,3600],{"class":506},[500,15962,15963,15965,15967,15969,15971],{"class":502,"line":13557},[500,15964,15357],{"class":703},[500,15966,15347],{"class":506},[500,15968,15496],{"class":703},[500,15970,15364],{"class":677},[500,15972,3600],{"class":506},[500,15974,15975,15977,15979,15981,15983],{"class":502,"line":13571},[500,15976,15371],{"class":703},[500,15978,15347],{"class":506},[500,15980,11608],{"class":703},[500,15982,15364],{"class":677},[500,15984,3600],{"class":506},[500,15986,15987,15989,15991,15993,15995],{"class":502,"line":13587},[500,15988,15384],{"class":703},[500,15990,15347],{"class":506},[500,15992,15389],{"class":703},[500,15994,15364],{"class":677},[500,15996,3600],{"class":506},[500,15998,15999,16001,16003,16006,16008],{"class":502,"line":13603},[500,16000,15491],{"class":703},[500,16002,15347],{"class":506},[500,16004,16005],{"class":703},"396",[500,16007,15364],{"class":677},[500,16009,3600],{"class":506},[500,16011,16012],{"class":502,"line":13619},[500,16013,802],{"class":506},[18,16015,16016],{},"This can act as a simple framework for an application with multiple views. Now we still need to make our navigation\ncontroller to switch between the different views. jQuery to the rescue — a small snippet initializes our view hierarchy\nand provides switching capabilities:",[492,16018,16020],{"className":668,"code":16019,"language":670,"meta":34,"style":34},"$(document).ready(function () {\n var navitems = $(\"#navigation li a\");\n navitems.click(function () {\n navitems.removeClass(\"current\");\n var ref = $(this).addClass(\"current\").attr(\"href\");\n /* hide the other views, show the one navigated to\n and trigger a custom event */\n $(\"div.view\").hide();\n $(ref).show().trigger(\"becameActive\");\n });\n $(\"div.view\").hide();\n $(\"div.view.current\").show().trigger(\"becameActive\");\n});\n",[405,16021,16022,16039,16058,16072,16086,16122,16127,16132,16149,16172,16176,16191,16214],{"__ignoreMap":34},[500,16023,16024,16027,16030,16033,16035,16037],{"class":502,"line":503},[500,16025,16026],{"class":513},"$",[500,16028,16029],{"class":506},"(document).",[500,16031,16032],{"class":513},"ready",[500,16034,713],{"class":506},[500,16036,3556],{"class":677},[500,16038,3559],{"class":506},[500,16040,16041,16043,16046,16048,16051,16053,16056],{"class":502,"line":98},[500,16042,3564],{"class":677},[500,16044,16045],{"class":506}," navitems ",[500,16047,517],{"class":677},[500,16049,16050],{"class":513}," $",[500,16052,713],{"class":506},[500,16054,16055],{"class":520},"\"#navigation li a\"",[500,16057,4107],{"class":506},[500,16059,16060,16063,16066,16068,16070],{"class":502,"line":249},[500,16061,16062],{"class":506}," navitems.",[500,16064,16065],{"class":513},"click",[500,16067,713],{"class":506},[500,16069,3556],{"class":677},[500,16071,3559],{"class":506},[500,16073,16074,16077,16080,16082,16084],{"class":502,"line":583},[500,16075,16076],{"class":506}," navitems.",[500,16078,16079],{"class":513},"removeClass",[500,16081,713],{"class":506},[500,16083,15052],{"class":520},[500,16085,4107],{"class":506},[500,16087,16088,16090,16093,16095,16097,16099,16101,16103,16106,16108,16110,16112,16115,16117,16120],{"class":502,"line":615},[500,16089,7548],{"class":677},[500,16091,16092],{"class":506}," ref ",[500,16094,517],{"class":677},[500,16096,16050],{"class":513},[500,16098,713],{"class":506},[500,16100,779],{"class":703},[500,16102,352],{"class":506},[500,16104,16105],{"class":513},"addClass",[500,16107,713],{"class":506},[500,16109,15052],{"class":520},[500,16111,352],{"class":506},[500,16113,16114],{"class":513},"attr",[500,16116,713],{"class":506},[500,16118,16119],{"class":520},"\"href\"",[500,16121,4107],{"class":506},[500,16123,16124],{"class":502,"line":621},[500,16125,16126],{"class":3586}," /* hide the other views, show the one navigated to\n",[500,16128,16129],{"class":502,"line":631},[500,16130,16131],{"class":3586}," and trigger a custom event */\n",[500,16133,16134,16137,16139,16142,16144,16147],{"class":502,"line":641},[500,16135,16136],{"class":513}," $",[500,16138,713],{"class":506},[500,16140,16141],{"class":520},"\"div.view\"",[500,16143,352],{"class":506},[500,16145,16146],{"class":513},"hide",[500,16148,748],{"class":506},[500,16150,16151,16153,16156,16159,16162,16165,16167,16170],{"class":502,"line":647},[500,16152,16136],{"class":513},[500,16154,16155],{"class":506},"(ref).",[500,16157,16158],{"class":513},"show",[500,16160,16161],{"class":506},"().",[500,16163,16164],{"class":513},"trigger",[500,16166,713],{"class":506},[500,16168,16169],{"class":520},"\"becameActive\"",[500,16171,4107],{"class":506},[500,16173,16174],{"class":502,"line":805},[500,16175,3605],{"class":506},[500,16177,16178,16181,16183,16185,16187,16189],{"class":502,"line":810},[500,16179,16180],{"class":513}," $",[500,16182,713],{"class":506},[500,16184,16141],{"class":520},[500,16186,352],{"class":506},[500,16188,16146],{"class":513},[500,16190,748],{"class":506},[500,16192,16193,16195,16197,16200,16202,16204,16206,16208,16210,16212],{"class":502,"line":826},[500,16194,16180],{"class":513},[500,16196,713],{"class":506},[500,16198,16199],{"class":520},"\"div.view.current\"",[500,16201,352],{"class":506},[500,16203,16158],{"class":513},[500,16205,16161],{"class":506},[500,16207,16164],{"class":513},[500,16209,713],{"class":506},[500,16211,16169],{"class":520},[500,16213,4107],{"class":506},[500,16215,16216],{"class":502,"line":838},[500,16217,841],{"class":506},[18,16219,16220,16221,16224,16225,16228],{},"With custom events like ",[405,16222,16223],{},"becameActive"," it is easy to create new callbacks that are invoked when state changes occur — in\nthis example when switching the active view through our navigation controller. For brevity we will omit a elaborate\nexample of controller code, but if for example you want code to run when switching to the ",[405,16226,16227],{},"#new"," view, you could simply\nwrite:",[492,16230,16232],{"className":668,"code":16231,"language":670,"meta":34,"style":34},"$(\"#new\").bind(\"becameActive\", function (event) {\n $(event.target).doSomething();\n});\n",[405,16233,16234,16260,16272],{"__ignoreMap":34},[500,16235,16236,16238,16240,16242,16244,16246,16248,16250,16252,16254,16256,16258],{"class":502,"line":503},[500,16237,16026],{"class":513},[500,16239,713],{"class":506},[500,16241,15088],{"class":520},[500,16243,352],{"class":506},[500,16245,7306],{"class":513},[500,16247,713],{"class":506},[500,16249,16169],{"class":520},[500,16251,719],{"class":506},[500,16253,3556],{"class":677},[500,16255,725],{"class":506},[500,16257,729],{"class":728},[500,16259,3688],{"class":506},[500,16261,16262,16264,16267,16270],{"class":502,"line":98},[500,16263,16180],{"class":513},[500,16265,16266],{"class":506},"(event.target).",[500,16268,16269],{"class":513},"doSomething",[500,16271,748],{"class":506},[500,16273,16274],{"class":502,"line":249},[500,16275,841],{"class":506},[18,16277,16278],{},"So what is still missing now? We still haven’t provided a data model to operate on. While data provided via web services\nis not a big deal by the use of JSON-APIs, we would certainly want to have a local data storage too. For quite some\ntime already, web engines provide a local SQLite-based storage system. On application initialization we can simply\nsetup some tables to hold our data and send statements from anywhere in the application later on:",[492,16280,16282],{"className":668,"code":16281,"language":670,"meta":34,"style":34},"// open a database, providing it's name, version,\n// maximum size and display name\nvar database = openDatabase(\"Example\", \"1.0\", 1048576, \"Example Database\");\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\n \"CREATE TABLE IF NOT EXISTS data\" +\n \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\" +\n \"... more declarations ...);\",\n );\n});\n// other statements are issued the same way\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\"SELECT * FROM data;\", function (transaction, result) {\n // do something with 'result'\n });\n});\n",[405,16283,16284,16289,16294,16328,16346,16356,16364,16371,16378,16383,16387,16392,16408,16434,16439,16443],{"__ignoreMap":34},[500,16285,16286],{"class":502,"line":503},[500,16287,16288],{"class":3586},"// open a database, providing it's name, version,\n",[500,16290,16291],{"class":502,"line":98},[500,16292,16293],{"class":3586},"// maximum size and display name\n",[500,16295,16296,16298,16301,16303,16306,16308,16311,16313,16316,16318,16321,16323,16326],{"class":502,"line":249},[500,16297,7841],{"class":677},[500,16299,16300],{"class":506}," database ",[500,16302,517],{"class":677},[500,16304,16305],{"class":513}," openDatabase",[500,16307,713],{"class":506},[500,16309,16310],{"class":520},"\"Example\"",[500,16312,719],{"class":506},[500,16314,16315],{"class":520},"\"1.0\"",[500,16317,719],{"class":506},[500,16319,16320],{"class":703},"1048576",[500,16322,719],{"class":506},[500,16324,16325],{"class":520},"\"Example Database\"",[500,16327,4107],{"class":506},[500,16329,16330,16333,16336,16338,16340,16342,16344],{"class":502,"line":583},[500,16331,16332],{"class":506},"database.",[500,16334,16335],{"class":513},"transaction",[500,16337,713],{"class":506},[500,16339,3556],{"class":677},[500,16341,725],{"class":506},[500,16343,16335],{"class":728},[500,16345,3688],{"class":506},[500,16347,16348,16351,16354],{"class":502,"line":615},[500,16349,16350],{"class":506}," transaction.",[500,16352,16353],{"class":513},"executeSQL",[500,16355,7771],{"class":506},[500,16357,16358,16361],{"class":502,"line":621},[500,16359,16360],{"class":520}," \"CREATE TABLE IF NOT EXISTS data\"",[500,16362,16363],{"class":677}," +\n",[500,16365,16366,16369],{"class":502,"line":631},[500,16367,16368],{"class":520}," \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\"",[500,16370,16363],{"class":677},[500,16372,16373,16376],{"class":502,"line":641},[500,16374,16375],{"class":520}," \"... more declarations ...);\"",[500,16377,835],{"class":506},[500,16379,16380],{"class":502,"line":647},[500,16381,16382],{"class":506}," );\n",[500,16384,16385],{"class":502,"line":805},[500,16386,841],{"class":506},[500,16388,16389],{"class":502,"line":810},[500,16390,16391],{"class":3586},"// other statements are issued the same way\n",[500,16393,16394,16396,16398,16400,16402,16404,16406],{"class":502,"line":826},[500,16395,16332],{"class":506},[500,16397,16335],{"class":513},[500,16399,713],{"class":506},[500,16401,3556],{"class":677},[500,16403,725],{"class":506},[500,16405,16335],{"class":728},[500,16407,3688],{"class":506},[500,16409,16410,16412,16414,16416,16419,16421,16423,16425,16427,16429,16432],{"class":502,"line":838},[500,16411,16350],{"class":506},[500,16413,16353],{"class":513},[500,16415,713],{"class":506},[500,16417,16418],{"class":520},"\"SELECT * FROM data;\"",[500,16420,719],{"class":506},[500,16422,3556],{"class":677},[500,16424,725],{"class":506},[500,16426,16335],{"class":728},[500,16428,719],{"class":506},[500,16430,16431],{"class":728},"result",[500,16433,3688],{"class":506},[500,16435,16436],{"class":502,"line":937},[500,16437,16438],{"class":3586}," // do something with 'result'\n",[500,16440,16441],{"class":502,"line":943},[500,16442,3605],{"class":506},[500,16444,16445],{"class":502,"line":948},[500,16446,841],{"class":506},[335,16448,2072],{"id":2071},[18,16450,16451,16452,180,16455],{},"A question remains: ",[168,16453,16454],{},"when this is the basic skeleton of an application, how do i put this on an actual\ndevice?",[32,16456],{"alt":34,"src":16457},"https://media.synyx.de/uploads//2010/08/jquery-html-css-example-e1281692547570.png",[18,16459,16460],{},"Example in Simulator",[18,16462,16463,16464,16467],{},"While this could be simply served by a web server to a mobile device, the main reason for developing applications\ninstead of websites is the ability to expose them through an application store (e.g. Apples AppStore or the Android\nMarket). We won’t go into much detail here now — this is something to be discussed in the following blog posts in this\nseries — a simple answer is provided by a small framework\nnamed ",[22,16465,14757],{"href":14755,"rel":16466},[26],". PhoneGap provides the developer with\na cross-platform deployment environment, which makes use of the web engines on the different mobile devices. It\nbasically is a fullscreen web browser view, without any UI elements and other decorations but with added support for\naccessing hardware features of the actual device. Next to the already mentioned features, which are part of the modern\nweb engines, it gives access to features like cameras, accelerometer or access to the native phonebook of your mobile\nphone. You simply drop all your HTML, CSS & JavaScript into a PhoneGap application package which is built for your\nparticular device and are ready to deploy through any application store or similar channel you like.",[335,16469,16471],{"id":16470},"whats-next","What’s next?",[18,16473,16474],{},"So developing mobile applications with HTML, CSS & JavaScript is actually easy, isn’t it? Well, with the basic ideas\noutlined above it is — until it isn’t anymore. In the parts of this series which are still to come, we will answer some\nquestions, that might be crucial to your development cycle:",[343,16476,16477,16480,16483,16486],{},[346,16478,16479],{},"What problems might arise with JavaScript as a development language? Is there enough tool support for ease of\ndevelopment?",[346,16481,16482],{},"What do i have to program from scratch and which frameworks are available, that erase my need of boilerplate code?",[346,16484,16485],{},"What about the performance, for example when sophisticated animations are desired?",[346,16487,16488],{},"Is my application really cross-platform, when using these technologies, and does it behave exactly the same on every\ndevice?",[18,16490,16491],{},"Stay tuned for more.",[1017,16493,16494],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":34,"searchDepth":98,"depth":98,"links":16496},[16497,16498,16499,16500],{"id":14996,"depth":98,"text":14997},{"id":15003,"depth":98,"text":15004},{"id":2071,"depth":98,"text":2072},{"id":16470,"depth":98,"text":16471},[14964,16502],"tutorial","2010-08-13T12:17:29","Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\\nseveral mobile platforms at once.","https://synyx.de/blog/on-cross-device-mobile-development-part-1/",{},"/blog/on-cross-device-mobile-development-part-1",{"title":14980,"description":16509},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.","blog/on-cross-device-mobile-development-part-1",[1683,14974,496,14975,670],"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team of developers might be small or knowledge about…","yJGRhWop8a6UwlupSTSgkogMBabnFF7yVveZDcNWM8Y",{"id":16515,"title":16516,"author":16517,"body":16518,"category":16722,"date":16723,"description":16724,"extension":104,"link":16725,"meta":16726,"navigation":107,"path":16727,"seo":16728,"slug":16522,"stem":16730,"tags":16731,"teaser":16735,"__hash__":16736},"blog/blog/dependency-hell-or-including-jsr303-into-a-hibernated-javaee-app.md","Dependency Hell or Including JSR303 into a hibernated JavaEE App",[6392],{"type":11,"value":16519,"toc":16720},[16520,16523,16530,16537,16540,16683,16690,16693,16704,16715,16718],[14,16521,16516],{"id":16522},"dependency-hell-or-including-jsr303-into-a-hibernated-javaee-app",[18,16524,16525,16526,12316],{},"Today i integrated the JSR303 reference implementation, which is Hibernate-Validator 4.x, into an existing JavaEE\napplication. The application is built on Spring 3.0 and uses our Synyx Hades project, which is also based on Spring, as\nan OR-Mapper. (",[22,16527,16528],{"href":16528,"rel":16529},"http://redmine.synyx.org/projects/show/hades",[26],[18,16531,16532,16533,12316],{},"First I just followed the Spring tutorial which is quite simple and straight\nforward. (",[22,16534,16535],{"href":16535,"rel":16536},"http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#validation-beanvalidation-spring",[26],[18,16538,16539],{},"After a redeploy and writing an example bean and a test for it, it was just disappointing because nothing worked and the\nstacktrace was not really helping at a first look.",[492,16541,16543],{"className":3047,"code":16542,"language":3049,"meta":34,"style":34},"testProperty(org.synyx.jsr303.validation.ValidatorTest) Time elapsed: 0.002 sec \u003C\u003C\u003C ERROR!\njava.lang.NoSuchMethodError: javax.persistence.Persistence.getPersistenceUtil()Ljavax/persistence/PersistenceUtil;\nat org.hibernate.validator.engine.resolver.JPATraversableResolver.isReachable(JPATraversableResolver.java:33)\nat org.hibernate.validator.engine.resolver.DefaultTraversableResolver.isReachable(DefaultTraversableResolver.java:112)\nat org.hibernate.validator.engine.resolver.SingleThreadCachedTraversableResolver.isReachable(SingleThreadCachedTraversableResolver.java:47)\nat org.hibernate.validator.engine.ValidatorImpl.isValidationRequired(ValidatorImpl.java:761)\nat org.hibernate.validator.engine.ValidatorImpl.validatePropertyForGroup(ValidatorImpl.java:562)\nat org.hibernate.validator.engine.ValidatorImpl.validateProperty(ValidatorImpl.java:496)\nat org.hibernate.validator.engine.ValidatorImpl.validateProperty(ValidatorImpl.java:131)\nat org.springframework.validation.beanvalidation.SpringValidatorAdapter.validateProperty(SpringValidatorAdapter.java:118)\nat org.synyx.jsr303.validation.ValidatorTest.testProperty(ValidatorTest.java:64)\nat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\nat java.lang.reflect.Method.invoke(Method.java:597)\nat org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)\nat org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)\nat org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)\nat org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)\nat org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)\nat org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)\nat org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)\nat org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:62)\nat org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:140)\nat org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:127)\nat org.apache.maven.surefire.Surefire.run(Surefire.java:177)\nat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\nat java.lang.reflect.Method.invoke(Method.java:597)\nat org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:345)\nat org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1009)\n\n",[405,16544,16545,16550,16555,16560,16565,16570,16575,16580,16585,16590,16595,16600,16605,16610,16615,16620,16625,16630,16635,16640,16645,16650,16655,16660,16665,16669,16673,16678],{"__ignoreMap":34},[500,16546,16547],{"class":502,"line":503},[500,16548,16549],{},"testProperty(org.synyx.jsr303.validation.ValidatorTest) Time elapsed: 0.002 sec \u003C\u003C\u003C ERROR!\n",[500,16551,16552],{"class":502,"line":98},[500,16553,16554],{},"java.lang.NoSuchMethodError: javax.persistence.Persistence.getPersistenceUtil()Ljavax/persistence/PersistenceUtil;\n",[500,16556,16557],{"class":502,"line":249},[500,16558,16559],{},"at org.hibernate.validator.engine.resolver.JPATraversableResolver.isReachable(JPATraversableResolver.java:33)\n",[500,16561,16562],{"class":502,"line":583},[500,16563,16564],{},"at org.hibernate.validator.engine.resolver.DefaultTraversableResolver.isReachable(DefaultTraversableResolver.java:112)\n",[500,16566,16567],{"class":502,"line":615},[500,16568,16569],{},"at org.hibernate.validator.engine.resolver.SingleThreadCachedTraversableResolver.isReachable(SingleThreadCachedTraversableResolver.java:47)\n",[500,16571,16572],{"class":502,"line":621},[500,16573,16574],{},"at org.hibernate.validator.engine.ValidatorImpl.isValidationRequired(ValidatorImpl.java:761)\n",[500,16576,16577],{"class":502,"line":631},[500,16578,16579],{},"at org.hibernate.validator.engine.ValidatorImpl.validatePropertyForGroup(ValidatorImpl.java:562)\n",[500,16581,16582],{"class":502,"line":641},[500,16583,16584],{},"at org.hibernate.validator.engine.ValidatorImpl.validateProperty(ValidatorImpl.java:496)\n",[500,16586,16587],{"class":502,"line":647},[500,16588,16589],{},"at org.hibernate.validator.engine.ValidatorImpl.validateProperty(ValidatorImpl.java:131)\n",[500,16591,16592],{"class":502,"line":805},[500,16593,16594],{},"at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validateProperty(SpringValidatorAdapter.java:118)\n",[500,16596,16597],{"class":502,"line":810},[500,16598,16599],{},"at org.synyx.jsr303.validation.ValidatorTest.testProperty(ValidatorTest.java:64)\n",[500,16601,16602],{"class":502,"line":826},[500,16603,16604],{},"at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n",[500,16606,16607],{"class":502,"line":838},[500,16608,16609],{},"at java.lang.reflect.Method.invoke(Method.java:597)\n",[500,16611,16612],{"class":502,"line":937},[500,16613,16614],{},"at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)\n",[500,16616,16617],{"class":502,"line":943},[500,16618,16619],{},"at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)\n",[500,16621,16622],{"class":502,"line":948},[500,16623,16624],{},"at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)\n",[500,16626,16627],{"class":502,"line":954},[500,16628,16629],{},"at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)\n",[500,16631,16632],{"class":502,"line":1898},[500,16633,16634],{},"at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)\n",[500,16636,16637],{"class":502,"line":1904},[500,16638,16639],{},"at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)\n",[500,16641,16642],{"class":502,"line":1910},[500,16643,16644],{},"at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)\n",[500,16646,16647],{"class":502,"line":1916},[500,16648,16649],{},"at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:62)\n",[500,16651,16652],{"class":502,"line":1922},[500,16653,16654],{},"at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:140)\n",[500,16656,16657],{"class":502,"line":1928},[500,16658,16659],{},"at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:127)\n",[500,16661,16662],{"class":502,"line":1934},[500,16663,16664],{},"at org.apache.maven.surefire.Surefire.run(Surefire.java:177)\n",[500,16666,16667],{"class":502,"line":1940},[500,16668,16604],{},[500,16670,16671],{"class":502,"line":1946},[500,16672,16609],{},[500,16674,16675],{"class":502,"line":1952},[500,16676,16677],{},"at org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:345)\n",[500,16679,16680],{"class":502,"line":1958},[500,16681,16682],{},"at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1009)\n",[18,16684,16685,16686,16689],{},"Why did I get an exception from a class called PersistenceUtil while just doing some validations via JSR303? Well, after\nreading the specification for JSR303 and some related blog entries, the solution is quite simple. JSR303 specification\nmanifests, that if a class PersistenceUtil is in the classpath, the JPATraversalResolver has to be integrated into the\nvalidation as well. If afterwards the method ",[168,16687,16688],{},"getPersistenceUtil()"," is getting called, its obvious that this method is\nnot available due to the fact that it is just a simple name matching.",[18,16691,16692],{},"Now I had THREE questions instead of one 🙂:",[343,16694,16695,16698,16701],{},[346,16696,16697],{},"Why is there a class called PersistenceUtil in my classpath while I DON’T use any JPA2 library (And this class is\nonly relevant for JPA2) – well this is surely just because the Hibernate guys had chosen the same name. Anyway, why the\nJPA2 guys used names like “PersistenceUtil” ????",[346,16699,16700],{},"Why the specification manifests that if a class called PersistenceUtil is in classpath, there must be also a JPA\nvalidation?",[346,16702,16703],{},"Why they do not additionally check against the needed method getPersistenceUtil() as well?",[18,16705,16706,16707,16710,16711,16714],{},"Anyway, as I needed a workaround, I checked the projects classpath… and I found my class ",[168,16708,16709],{},"PersistenceUtil"," but it was in\na jar called “ejb3-persistence-1.0.1GA.jar” – don’t ask me, why there also is a class called PersistenceUtil (btw. I\nhate ANY classes with ",[168,16712,16713],{},"UTIL"," in its name). I googled around a bit and found more people, who had the same problem and\nthe fix is really really easy. The guys who maintained the jar, fixed the problem due a little refactoring in version\n1.0.2GA.",[18,16716,16717],{},"So the only thing to do was to update this dependency in pom.xml to 1.0.2GA and everything went fine… Took me three\nhours to find out 🙁",[1017,16719,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":16721},[],[256],"2010-07-20T16:10:45","Today i integrated the JSR303 reference implementation, which is Hibernate-Validator 4.x, into an existing JavaEE\\napplication. The application is built on Spring 3.0 and uses our Synyx Hades project, which is also based on Spring, as\\nan OR-Mapper. (http://redmine.synyx.org/projects/show/hades)","https://synyx.de/blog/dependency-hell-or-including-jsr303-into-a-hibernated-javaee-app/",{},"/blog/dependency-hell-or-including-jsr303-into-a-hibernated-javaee-app",{"title":16516,"description":16729},"Today i integrated the JSR303 reference implementation, which is Hibernate-Validator 4.x, into an existing JavaEE\napplication. The application is built on Spring 3.0 and uses our Synyx Hades project, which is also based on Spring, as\nan OR-Mapper. (http://redmine.synyx.org/projects/show/hades)","blog/dependency-hell-or-including-jsr303-into-a-hibernated-javaee-app",[16732,16733,16734,869,2534,6488],"architecture","dependencyhell","dependencymanagement","Today i integrated the JSR303 reference implementation, which is Hibernate-Validator 4.x, into an existing JavaEE application. The application is built on Spring 3.0 and uses our Synyx Hades project, which…","jaMqXSr9rHWkAW8ml4UhHnhi_9ybaohJ2bmPFJ0BOnA",{"id":16738,"title":16739,"author":16740,"body":16741,"category":16836,"date":16837,"description":16838,"extension":104,"link":16839,"meta":16840,"navigation":107,"path":16841,"seo":16842,"slug":16745,"stem":16844,"tags":16845,"teaser":16846,"__hash__":16847},"blog/blog/servlet-container-options-for-maven.md","Servlet container options for Maven",[14605],{"type":11,"value":16742,"toc":16834},[16743,16746,16759,16768,16776,16785,16794,16797,16826,16829,16832],[14,16744,16739],{"id":16745},"servlet-container-options-for-maven",[18,16747,16748,16749,16752,16753,16758],{},"When developing web apps with ",[22,16750,12289],{"href":8353,"rel":16751},[26]," the de facto standard for running the app is to use the\nexcellent ",[22,16754,16757],{"href":16755,"rel":16756},"https://web.archive.org/web/20150520205353/https://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin",[26],"Maven Jetty Plugin","\nwhich runs the project in an embedded Jetty server. When configured, it can either run the project from the war file\ndirectly via mvn jetty:run or in exploded mode where the war is unpacked before being run (mvn jetty:run-exploded).\nThis noticably speeds up development as there is no need to manually deploy the artifact to a server.",[18,16760,16761,16762,16767],{},"But if the production system does not run on Jetty but on ",[22,16763,16766],{"href":16764,"rel":16765},"http://tomcat.apache.org/",[26],"Tomcat"," you might run into some\nproblems:",[343,16769,16770,16773],{},[346,16771,16772],{},"Some redirects from AJAX calls do work on Jetty but do not work on Tomcat",[346,16774,16775],{},"When submitting some forms on Jetty the parameters get lost",[18,16777,16778,16779,16784],{},"The latter can be noticed when running ",[22,16780,16783],{"href":16781,"rel":16782},"http://opencms.org",[26],"OpenCms"," on Jetty: Saving from the editor causes an error\nbecause the parameters are not available to OpenCms.",[18,16786,16787,16788,16793],{},"Fortunately there is a viable alternative:\nThe ",[22,16789,16792],{"href":16790,"rel":16791},"https://web.archive.org/web/20150531090420/http://mojo.codehaus.org/tomcat-maven-plugin/",[26],"Tomcat Maven Plugin",".\nBesides providing several options to deploy artifacts to an external server it can also be run in an embedded mode.",[18,16795,16796],{},"This is how the plugin is configured:",[492,16798,16800],{"className":1751,"code":16799,"language":1753,"meta":34,"style":34},"\n\u003Cplugin>\n \u003CgroupId>org.codehaus.mojo\u003C/groupId>\n \u003CartifactId>tomcat-maven-plugin\u003C/artifactId>\n\u003C/plugin>\n\n",[405,16801,16802,16806,16811,16816,16821],{"__ignoreMap":34},[500,16803,16804],{"class":502,"line":503},[500,16805,644],{"emptyLinePlaceholder":107},[500,16807,16808],{"class":502,"line":98},[500,16809,16810],{},"\u003Cplugin>\n",[500,16812,16813],{"class":502,"line":249},[500,16814,16815],{}," \u003CgroupId>org.codehaus.mojo\u003C/groupId>\n",[500,16817,16818],{"class":502,"line":583},[500,16819,16820],{}," \u003CartifactId>tomcat-maven-plugin\u003C/artifactId>\n",[500,16822,16823],{"class":502,"line":615},[500,16824,16825],{},"\u003C/plugin>\n",[18,16827,16828],{},"It can be run using mvn tomcat:run and mvn tomcat:run-war for running in unpacked war mode.",[18,16830,16831],{},"Using this plugin you can be sure that the features your servlet container provides in production are the same during\ndevelopment.",[1017,16833,1667],{},{"title":34,"searchDepth":98,"depth":98,"links":16835},[],[1029],"2010-07-09T08:48:36","When developing web apps with Maven the de facto standard for running the app is to use the\\nexcellent Maven Jetty Plugin\\nwhich runs the project in an embedded Jetty server. When configured, it can either run the project from the war file\\ndirectly via mvn jetty:run or in exploded mode where the war is unpacked before being run (mvn jetty:run-exploded).\\nThis noticably speeds up development as there is no need to manually deploy the artifact to a server.","https://synyx.de/blog/servlet-container-options-for-maven/",{},"/blog/servlet-container-options-for-maven",{"title":16739,"description":16843},"When developing web apps with Maven the de facto standard for running the app is to use the\nexcellent Maven Jetty Plugin\nwhich runs the project in an embedded Jetty server. When configured, it can either run the project from the war file\ndirectly via mvn jetty:run or in exploded mode where the war is unpacked before being run (mvn jetty:run-exploded).\nThis noticably speeds up development as there is no need to manually deploy the artifact to a server.","blog/servlet-container-options-for-maven",[1039,869,9114,2534,9117],"When developing web apps with Maven the de facto standard for running the app is to use the excellent Maven Jetty Plugin which runs the project in an embedded Jetty…","fRYI1HRQAdsyYeA7rnRcKDEpC-Azc63aZtMg04FdDvU",{"id":16849,"title":16850,"author":16851,"body":16852,"category":17000,"date":17001,"description":17002,"extension":104,"link":17003,"meta":17004,"navigation":107,"path":17005,"seo":17006,"slug":16856,"stem":17007,"tags":17008,"teaser":17011,"__hash__":17012},"blog/blog/template-based-document-generation-using-odfdom.md","Template based document generation using ODFDOM",[14605],{"type":11,"value":16853,"toc":16994},[16854,16857,16860,16875,16879,16888,16899,16902,16906,16914,16917,16920,16923,16926,16929,16933,16936,16943,16948,16951,16954,16957,16960,16963,16966,16969,16972,16975,16978,16981,16983,16986,16989,16991],[14,16855,16850],{"id":16856},"template-based-document-generation-using-odfdom",[18,16858,16859],{},"Generating documents from data that is managed by a web application is a quite common need. Think about letters that are\ngenerated for a customer relationship management system or bills that are to be send for membership fees. For corporate\nidentity reasons you don’t want these documents to look like generated from a plain text file but you want to have\nlogos, tables, address labels and so on.",[18,16861,16862,16863,16868,16869,16874],{},"As the people that are designing the look of these documents often are not programmers it is a good idea to provide a\nway to use well know tools for creating and editing templates for these documents. What we have been doing for some time\nis to let the customer create template documents using ",[22,16864,16867],{"href":16865,"rel":16866},"http://www.openoffice.org/",[26],"OpenOffice.org",", the open source\nword processor, and transform these documents programmatically. OpenOffice.org uses the\nstandardized ",[22,16870,16873],{"href":16871,"rel":16872},"http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=office",[26],"Open Document"," format to save its\ndocuments. Open Document files are zip archives that contain some XML documents as well as additional content (images,\nmacros, …).",[133,16876,16878],{"id":16877},"the-uno-approach","The UNO approach",[18,16880,16881,16882,16887],{},"One of the older approaches we have been using for document processing is to access an OpenOffice.org instance running\non the server using the ",[22,16883,16886],{"href":16884,"rel":16885},"http://wiki.services.openoffice.org/wiki/Uno",[26],"UNO API",". UNO is a language agnostic API that\nprovides access to a lot of functionality of OpenOffice.org using an IDL. Though really powerful this approach also\nyields some drawbacks:",[343,16889,16890,16893,16896],{},[346,16891,16892],{},"Understanding and learning the UNO API is hard and takes a lot of time",[346,16894,16895],{},"Some features of a document can’t be accessed using the API (e.g. the id of form control elements is saved in the\ndocument but is not accessible using UNO)",[346,16897,16898],{},"An instance of OpenOffice.org can only be used by one thread at a time so you need some kind of instance pooling.",[18,16900,16901],{},"These drawbacks make it really hard to design and implement a robust system that can handle the load of a typical web\napplication and can still be maintained by a lot of developers.",[133,16903,16905],{"id":16904},"odfdom","ODFDOM",[18,16907,16908,16909,16913],{},"Some time after the standardization of the Open Document format a new project was\nborn: ",[22,16910,16905],{"href":16911,"rel":16912},"http://odftoolkit.org/projects/odfdom/pages/Home",[26],", a sub project of the odftoolkt project. ODFDOM is a\npure Java API that provides both low level DOM access to the Open Document XML format as well as convenience\nfunctionality to manipulate document data.",[18,16915,16916],{},"As with ODFDOM the application and the document generation all run on the Java Virtual Machine it is easier to maintain\nfrom an adminitrators perspective. Also in contrast to the UNO API ODFDOM is really easy to use.",[18,16918,16919],{},"The following snippet creates a new text document, inserts some text and saves the document to a temp file.",[18,16921,16922],{},"`OdfTextDocument doc = OdfTextDocument.newTextDocument();",[18,16924,16925],{},"doc.addText(\"Hello World!\");",[18,16927,16928],{},"doc.save(File.createTempFile(\"odfdom\", \".odt\"));`",[133,16930,16932],{"id":16931},"templating","Templating",[18,16934,16935],{},"To use Odfdom for templating you can choose one of the many placeholder approaches in OpenOffice.org. A very simple one\nis the use of user fields. To insert a user field in OpenOffice.org create a new document and go to Insert => Field\ncommand => Others. There you choose the tab variables and user field. You can add a name and a value. The value in our\ncase is only there to have a visual feedback when designing the document. The user field will be replaced automatically.",[18,16937,16938,16939,16942],{},"Let’s see how we can replace our placeholder value. The values for user fields as inserted above are stored in a node\n",[22,16940,16941],{"href":16941},"text:user-field-decl",". This is an excerpt from the Open Document content.xml for a simple example document:",[18,16944,16945],{},[405,16946,16947],{},"\u003Ctext:user-field-decl office:value-type=\"string\" office:string-value=\"hello\" text:name=\"test\"/>",[18,16949,16950],{},"The user field is named test, it’s initial value for visual feedback is set to “hello”.",[18,16952,16953],{},"Imagine that the data that we want to replace with the values in the template is stored in a simple Map of Strings. To\nreplace all dummy values with values from you application you can access the nodes using the method\ngetElementsByTagName(“element”):",[18,16955,16956],{},"`Map\u003CString, String> values = new HashMap\u003CString, String>();",[18,16958,16959],{},"values.put(\"test\", \"inserted automatically\");",[18,16961,16962],{},"OdfDocument doc = OdfDocument.loadDocument(\"/path/to/template.odt\");",[18,16964,16965],{},"NodeList nodes = doc.getOfficeBody().getElementsByTagName(OdfTextUserFieldDecl.ELEMENT_NAME.getQName());",[18,16967,16968],{},"for (int i = 0; i \u003C nodes.getLength(); i++) {",[18,16970,16971],{},"OdfTextUserFieldDecl element = (OdfTextUserFieldDecl) nodes.item(i);",[18,16973,16974],{},"if (values.containsKey(element.getTextNameAttribute())) {",[18,16976,16977],{},"element.setOfficeStringValueAttribute(values.get(element.getTextNameAttribute()));",[18,16979,16980],{},"}",[18,16982,16980],{},[18,16984,16985],{},"doc.save(\"/path/to/result.odt\");`",[18,16987,16988],{},"When running the code above, the value in the document is replaced with the value set programmatically.",[133,16990,2905],{"id":2904},[18,16992,16993],{},"So far we are running code using ODFDOM for document generation successfully in two larger projects that have been\ndeveloped recently. We believe that ODFDOM will help us delivering additional value for our customers with less\ndevelopment effort.",{"title":34,"searchDepth":98,"depth":98,"links":16995},[16996,16997,16998,16999],{"id":16877,"depth":249,"text":16878},{"id":16904,"depth":249,"text":16905},{"id":16931,"depth":249,"text":16932},{"id":2904,"depth":249,"text":2905},[1029,6722],"2010-06-13T17:57:40","Generating documents from data that is managed by a web application is a quite common need. Think about letters that are\\ngenerated for a customer relationship management system or bills that are to be send for membership fees. For corporate\\nidentity reasons you don’t want these documents to look like generated from a plain text file but you want to have\\nlogos, tables, address labels and so on.","https://synyx.de/blog/template-based-document-generation-using-odfdom/",{},"/blog/template-based-document-generation-using-odfdom",{"title":16850,"description":16859},"blog/template-based-document-generation-using-odfdom",[17009,869,16904,17010],"document-management","openoffice-org","Generating documents from data that is managed by a web application is a quite common need. Think about letters that are generated for a customer relationship management system or bills…","s3AA3WrDYmRY0S6MePU2SvOKGaEOPjNDiiAX_h4Pn5U",{"id":17014,"title":17015,"author":17016,"body":17017,"category":17137,"date":17138,"description":17139,"extension":104,"link":17140,"meta":17141,"navigation":107,"path":17142,"seo":17143,"slug":17021,"stem":17144,"tags":17145,"teaser":17147,"__hash__":17148},"blog/blog/in-my-humble-opinion-froyo-rocks.md","In my humble opinion — FroYo rocks!",[6392],{"type":11,"value":17018,"toc":17135},[17019,17022,17025,17028,17033,17036,17044,17047,17055,17060,17063,17071,17073,17099,17104,17115,17118,17123,17126,17129],[14,17020,17015],{"id":17021},"in-my-humble-opinion-froyo-rocks",[18,17023,17024],{},"FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a\nmixture between API Changes, new Userfeatures and some new cool Apps.",[18,17026,17027],{},"So, let’s divide this review into theese several specific parts and some addons at the end:",[18,17029,17030],{},[469,17031,17032],{},"First, the imho, absolutely most important part, the Enterprise Features:",[18,17034,17035],{},"New API-Features:",[343,17037,17038,17041],{},[346,17039,17040],{},"Data Backup API",[346,17042,17043],{},"Possibility to save passwords secure",[18,17045,17046],{},"New User Features:",[343,17048,17049,17052],{},[346,17050,17051],{},"updated Exchange Features",[346,17053,17054],{},"Remote Wipe",[18,17056,17057],{},[469,17058,17059],{},"Furthermore there are, of course, some more, not so enterprisy features 😉 :",[18,17061,17062],{},"New Apps:",[343,17064,17065,17068],{},[346,17066,17067],{},"Camera and Camcorder updated (possible to enable manually the leds for usage within camcorder)",[346,17069,17070],{},"Android Tethering and Usage as Hotspot",[18,17072,17046],{},[343,17074,17075,17078,17081,17084,17087,17090,17093,17096],{},[346,17076,17077],{},"Flashsupport , Airsupport (useful?)",[346,17079,17080],{},"Possibility to save Apps on SD-Card",[346,17082,17083],{},"JIT Compiler (hey this improves performance 3-5 times!!!)",[346,17085,17086],{},"HTML5 compatible browser?",[346,17088,17089],{},"With FroYo, users will be able to sync their local music collection with their Android device and stream wirelessly.",[346,17091,17092],{},"Users will also be available to backup their Apps in the Cloud",[346,17094,17095],{},"Android 2.2 finally supports multi-language keyboards too!",[346,17097,17098],{},"a lot of cool Marketplace updates (Webmarket like Androidpit.de and Search and Categories! and One click to update\nall, allow automatic update!!!! )",[18,17100,17101],{},[469,17102,17103],{},"And now some more rumors on what helps FroYo being the Apple poison 😉 :",[343,17105,17106,17109,17112],{},[346,17107,17108],{},"Kernel 2.6.32 will also improve speed for snapdragon processor smartphones like the Desire or the Milestone",[346,17110,17111],{},"Developers will be able to implement Services which interact with kind of Push Notifications. The killer here is that\nthis works bidirectional so that apps can also notifiy of feed Webapps in the Cloud with data! Interesting could be if\nGoogle is here open minded enough not to chain this, imho killerfeature, to Chrome.",[346,17113,17114],{},"Browserapps are able to use camera or sensors directly via JS! Is this the end of Tools like PhoneGap?",[18,17116,17117],{},"For those who like it … the new Google Buzz app should be much better than the crappy web version.",[343,17119,17120],{},[346,17121,17122],{},"Rumors told also that the new market is able to keep parts of froyo OS up to date automatically… could help to reduce\ndevice fragmentation!!!",[18,17124,17125],{},"As you can see, the list of new improvements is very long and in my opinion this is only the start of a little\nrevolution. All theese new features will enable Android Smartphone for a wide variety of really cool apps which we\nactually never thaught about because it was just not possible to implement ’em… we will dig more into Froyo if the first\nupdated Smartphone is available for us at Synyx… so stay tuned…",[18,17127,17128],{},"As addon, today the first Nexus One users told about their experience after the update… read more",[18,17130,17131],{},[22,17132,17133],{"href":17133,"rel":17134},"http://forum.xda-developers.com/showthread.php?t=686591",[26],{"title":34,"searchDepth":98,"depth":98,"links":17136},[],[14964],"2010-05-26T11:39:24","FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a\\nmixture between API Changes, new Userfeatures and some new cool Apps.","https://synyx.de/blog/in-my-humble-opinion-froyo-rocks/",{},"/blog/in-my-humble-opinion-froyo-rocks",{"title":17015,"description":17024},"blog/in-my-humble-opinion-froyo-rocks",[1683,17146,869],"froyo","FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a mixture between API Changes, new Userfeatures and some new cool…","pNHe7FxDSHWPlTA-DybrIz7fTVlnHhoV7GHkOZCAr2I",[17150,17153,17155,17158,17160,17163,17166,17169,17171,17174,17177,17180,17183,17185,17188,17191,17194,17197,17200,17203,17206,17209,17211,17214,17217,17220,17223,17225,17228,17231,17234,17237,17240,17243,17246,17249,17252,17254,17257,17260,17263,17265,17268,17271,17273,17276,17279,17282,17285,17287,17290,17292,17295,17298,17301,17303,17306,17308,17311,17314,17317,17320,17323,17326,17329,17332,17335,17338,17340,17343,17345,17347,17350,17353,17356,17359,17361,17364,17367,17370,17373,17376,17378,17381,17384,17387,17390,17393,17396,17398,17401,17404,17407,17410,17413,17416,17419,17421,17424,17426,17429,17432,17435,17438,17440,17442,17445,17447,17450,17453,17456,17458,17460,17463,17466,17469,17472,17475,17478,17481,17484,17487,17489,17492,17495,17497,17500,17503,17506,17509,17510,17512,17515,17518,17520,17523,17526,17528,17531,17534,17537,17540],{"slug":17151,"name":17152},"abel","Jennifer Abel",{"slug":9124,"name":17154},"Otto Allmendinger",{"slug":17156,"name":17157},"antony","Ben Antony",{"slug":6392,"name":17159},"Joachim Arrasz",{"slug":17161,"name":17162},"bauer","David Bauer",{"slug":17164,"name":17165},"bechtold","Janine Bechtold",{"slug":17167,"name":17168},"boersig","Jasmin Börsig",{"slug":17170,"name":12314},"buch",{"slug":17172,"name":17173},"buchloh","Aljona Buchloh",{"slug":17175,"name":17176},"burgard","Julia Burgard",{"slug":17178,"name":17179},"caspar-schwedes","Caspar Schwedes",{"slug":17181,"name":17182},"christina-schmitt","Christina Schmitt",{"slug":4856,"name":17184},"Michael Clausen",{"slug":17186,"name":17187},"contargo_poetzsch","Thomas Pötzsch",{"slug":17189,"name":17190},"damrath","Sebastian Damrath",{"slug":17192,"name":17193},"daniel","Markus Daniel",{"slug":17195,"name":17196},"dasch","Julia Dasch",{"slug":17198,"name":17199},"denman","Joffrey Denman",{"slug":17201,"name":17202},"dfuchs","Daniel Fuchs",{"slug":17204,"name":17205},"dobler","Max Dobler",{"slug":17207,"name":17208},"dobriakov","Vladimir Dobriakov",{"slug":17210,"name":17210},"dreiqbik",{"slug":17212,"name":17213},"dschaefer","Denise Schäfer",{"slug":17215,"name":17216},"dschneider","Dominik Schneider",{"slug":17218,"name":17219},"duerlich","Isabell Duerlich",{"slug":17221,"name":17222},"dutkowski","Bernd Dutkowski",{"slug":17224,"name":17224},"eifler",{"slug":17226,"name":17227},"essig","Tim Essig",{"slug":17229,"name":17230},"ferstl","Maximilian Ferstl",{"slug":17232,"name":17233},"fey","Prisca Fey",{"slug":17235,"name":17236},"frank","Leonard Frank",{"slug":17238,"name":17239},"franke","Arnold Franke",{"slug":17241,"name":17242},"frischer","Nicolette Rudmann",{"slug":17244,"name":17245},"fuchs","Petra Fuchs",{"slug":17247,"name":17248},"gari","Sarah Gari",{"slug":17250,"name":17251},"gast","Gast",{"slug":10429,"name":17253},"Johannes Graf",{"slug":17255,"name":17256},"grammlich","Daniela Grammlich",{"slug":17258,"name":17259},"guthardt","Sabrina Guthardt",{"slug":17261,"name":17262},"haeussler","Johannes Häussler",{"slug":2543,"name":17264},"Daniel Hammann",{"slug":17266,"name":17267},"heetel","Julian Heetel",{"slug":17269,"name":17270},"heft","Florian Heft",{"slug":7083,"name":17272},"Sebastian Heib",{"slug":17274,"name":17275},"heisler","Ida Heisler",{"slug":17277,"name":17278},"helm","Patrick Helm",{"slug":17280,"name":17281},"herbold","Michael Herbold",{"slug":17283,"name":17284},"hofmann","Peter Hofmann",{"slug":14605,"name":17286},"Florian Hopf",{"slug":17288,"name":17289},"jaud","Alina Jaud",{"slug":1047,"name":17291},"Robin De Silva Jayasinghe",{"slug":17293,"name":17294},"jbuch","Jonathan Buch",{"slug":17296,"name":17297},"junghanss","Gitta Junghanß",{"slug":17299,"name":17300},"kadyietska","Khrystyna Kadyietska",{"slug":2944,"name":17302},"Marc Kannegiesser",{"slug":17304,"name":17305},"karoly","Robert Károly",{"slug":5368,"name":17307},"Katja Arrasz-Schepanski",{"slug":17309,"name":17310},"kaufmann","Florian Kaufmann",{"slug":17312,"name":17313},"kesler","Mike Kesler",{"slug":17315,"name":17316},"kirchgaessner","Bettina Kirchgäßner",{"slug":17318,"name":17319},"klem","Yannic Klem",{"slug":17321,"name":17322},"klenk","Timo Klenk",{"slug":17324,"name":17325},"knell","Tobias Knell",{"slug":17327,"name":17328},"knoll","Anna-Lena Knoll",{"slug":17330,"name":17331},"knorre","Matthias Knorre",{"slug":17333,"name":17334},"koenig","Melanie König",{"slug":17336,"name":17337},"kraft","Thomas Kraft",{"slug":14700,"name":17339},"Florian Krupicka",{"slug":17341,"name":17342},"kuehn","Christian Kühn",{"slug":1189,"name":17344},"Christian Lange",{"slug":1692,"name":17346},"Luca Arrasz",{"slug":17348,"name":17349},"leist","Sascha Leist",{"slug":17351,"name":17352},"lihs","Michael Lihs",{"slug":17354,"name":17355},"linsin","David Linsin",{"slug":17357,"name":17358},"maniyar","Christian Maniyar",{"slug":123,"name":17360},"Björnie",{"slug":17362,"name":17363},"martin-koch","Martin Koch",{"slug":17365,"name":17366},"matt","Tobias Matt",{"slug":17368,"name":17369},"mennerich","Christian Mennerich",{"slug":17371,"name":17372},"menz","Alexander Menz",{"slug":17374,"name":17375},"meseck","Frederick Meseck",{"slug":2723,"name":17377},"Oliver Messner",{"slug":17379,"name":17380},"michael-ploed","Michael Plöd",{"slug":17382,"name":17383},"mies","Marius Mies",{"slug":17385,"name":17386},"mihai","Alina Mihai",{"slug":17388,"name":17389},"moeller","Jörg Möller",{"slug":17391,"name":17392},"mohr","Rebecca Mohr",{"slug":17394,"name":17395},"moretti","David Moretti",{"slug":6469,"name":17397},"Sven Müller",{"slug":17399,"name":17400},"muessig","Alexander Müssig",{"slug":17402,"name":17403},"neupokoev","Grigory Neupokoev",{"slug":17405,"name":17406},"nussbaecher","Carmen Nussbächer",{"slug":17408,"name":17409},"ochs","Pascal Ochs",{"slug":17411,"name":17412},"oelhoff","Jan Oelhoff",{"slug":17414,"name":17415},"oengel","Yasin Öngel",{"slug":17417,"name":17418},"oezsoy","Enis Özsoy",{"slug":6740,"name":17420},"Maya Posch",{"slug":17422,"name":17423},"ralfmueller","Ralf Müller",{"slug":17425,"name":17425},"redakteur",{"slug":17427,"name":17428},"reich","Michael Reich",{"slug":17430,"name":17431},"reinhard","Karl-Ludwig Reinhard",{"slug":17433,"name":17434},"rmueller","Rebecca Müller",{"slug":17436,"name":17437},"rosum","Jan Rosum",{"slug":17439,"name":17439},"rueckert",{"slug":11196,"name":17441},"Sascha Rüssel",{"slug":17443,"name":17444},"sauter","Moritz Sauter",{"slug":9,"name":17446},"Julian Schäfer",{"slug":17448,"name":17449},"scherer","Petra Scherer",{"slug":17451,"name":17452},"schlicht","Anne Schlicht",{"slug":17454,"name":17455},"schmidt","Jürgen Schmidt",{"slug":2544,"name":17457},"Tobias Schneider",{"slug":275,"name":17459},"Benjamin Seber",{"slug":17461,"name":17462},"sommer","Marc Sommer",{"slug":17464,"name":17465},"speaker-fels","Jakob Fels",{"slug":17467,"name":17468},"speaker-gierke","Oliver Gierke",{"slug":17470,"name":17471},"speaker-krupa","Malte Krupa",{"slug":17473,"name":17474},"speaker-mader","Jochen Mader",{"slug":17476,"name":17477},"speaker-meusel","Tim Meusel",{"slug":17479,"name":17480},"speaker-milke","Oliver Milke",{"slug":17482,"name":17483},"speaker-paluch","Mark Paluch",{"slug":17485,"name":17486},"speaker-schad","Jörg Schad",{"slug":12590,"name":17488},"Jochen Schalanda",{"slug":17490,"name":17491},"speaker-schauder","Jens Schauder",{"slug":17493,"name":17494},"speaker-unterstein","Johannes Unterstein",{"slug":17496,"name":27},"speaker-wolff",{"slug":17498,"name":17499},"speaker-zoerner","Stefan Zörner",{"slug":17501,"name":17502},"stefan-belger","Stefan Belger",{"slug":17504,"name":17505},"steinegger","Roland Steinegger",{"slug":17507,"name":17508},"stern","sternchen synyx",{"slug":6488,"name":6488},{"slug":6186,"name":17511},"Mateusz Szulc",{"slug":17513,"name":17514},"tamara","Tamara Tunczinger",{"slug":17516,"name":17517},"theuer","Tobias Theuer",{"slug":2724,"name":17519},"Sandra Thieme",{"slug":17521,"name":17522},"thies-clasen","Marudor",{"slug":17524,"name":17525},"toernstroem","Olle Törnström",{"slug":1267,"name":17527},"Max Ullinger",{"slug":17529,"name":17530},"ulrich","Stephan Ulrich",{"slug":17532,"name":17533},"wagner","Stefan Wagner",{"slug":17535,"name":17536},"weigel","Andreas Weigel",{"slug":17538,"name":17539},"werner","Fabian Werner",{"slug":17541,"name":17542},"wolke","Sören Wolke",["Reactive",17544],{"$scookieConsent":17545,"$ssite-config":17547},{"functional":17546,"analytics":17546},false,{"_priority":17548,"env":17552,"name":17553,"url":17554},{"name":17549,"env":17550,"url":17551},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",17557],{"category-java":-1,"authors":-1},"/blog/tags/java"]