"," expects a list of products (actually a JavaScript array). But currently the type of products is a\n",[50,3824,3825],{},"java.util.List"," which doesn’t have the map method. Note the datatype of products in the image below.",[18,3828,3829],{},[2694,3830],{"alt":3831,"src":3832},"nashorn-debugging","https://media.synyx.de/uploads//2016/03/nashorn-debugging.png",[2200,3834,3835],{},[18,3836,3837],{},"“Given a Java array or Collection, this function returns a JavaScript array with a shallow copy of its contents”",[18,3839,3840,3841,3843],{},"So our renderServer function defined in ",[50,3842,3019],{}," must be extended to:",[18,3845,3846],{},"Now we’re ready to go 🙂",[18,3848,3849,3850,3853,3854,3859],{},"Rebuild the frontend with ",[50,3851,3852],{},"npm run build",", restart the Spring Boot application, reload ",[573,3855,3856],{},[585,3857,3068],{"href":3068,"rel":3858},[589]," and\nadmire our awesome product list.",[18,3861,3862],{},[2694,3863],{"alt":3864,"src":3865},"awesome-product-list-001","https://media.synyx.de/uploads//2016/03/awesome-product-list-001.png",[1765,3867,3259],{"id":3258},[577,3869,3870,3873,3879,3887],{},[580,3871,3872],{},"using Nashorn is no rocket science",[580,3874,3875,3876,3878],{},"load js files via ",[50,3877,3570],{}," to enable debugging (at least in IntelliJ)",[580,3880,3881,3883,3884],{},[573,3882,3825],{}," must be converted to JavaScript array with ",[50,3885,3886],{},"Java.from",[580,3888,3889],{},"manually rebuilding and reloading the ReactJS app sucks (autoreload would be cool, right)",[18,3891,3892],{},[2694,3893],{"alt":3894,"src":3895},"js-webpack-nashorn","https://media.synyx.de/uploads//2016/03/js-webpack-nashorn.png",[1765,3897,3293],{"id":3292},[577,3899,3900,3903],{},[580,3901,3902],{},"using webpack to enhance developer experience",[580,3904,3905],{},"implementing the sorting feature",[18,3907,3908],{},[27,3909,3305],{},[607,3911,989],{},{"title":48,"searchDepth":86,"depth":86,"links":3913},[3914,3915,3916,3917,3918],{"id":2929,"depth":86,"text":2930},{"id":3448,"depth":86,"text":3361},{"id":3465,"depth":86,"text":3367},{"id":3258,"depth":86,"text":3259},{"id":3292,"depth":86,"text":3293},[613],"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":3335,"description":3344},"springboot-reactjs-server-side-rendering","blog/springboot-reactjs-server-side-rendering",[290,3327,3328,3329,1010,3330],"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":3932,"title":3933,"author":3934,"body":3936,"category":4119,"date":4120,"description":4121,"extension":617,"link":4122,"meta":4123,"navigation":499,"path":4124,"seo":4125,"slug":3940,"stem":4127,"tags":4128,"teaser":4136,"__hash__":4137},"blog/blog/devoxx-poland-2015-summary.md","Devoxx Poland 2015 Summary",[3935],"szulc",{"type":11,"value":3937,"toc":4117},[3938,3941,3962,3968,3971,3976,3979,3998,4004,4009,4012,4015,4018,4024,4029,4032,4035,4041,4046,4049,4052,4057,4060,4063,4068,4071,4074,4077,4082,4085,4088,4091,4094,4097,4102,4105,4114],[14,3939,3933],{"id":3940},"devoxx-poland-2015-summary",[18,3942,3943,3944,3949,3950,3955,3956,3961],{},"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 ",[585,3945,3948],{"href":3946,"rel":3947},"http://devoxx.pl",[589],"Devoxx Poland"," (previously known\nas ",[585,3951,3954],{"href":3952,"rel":3953},"http://2014.33degree.org",[589],"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 ",[585,3957,3960],{"href":3958,"rel":3959},"http://www.icekrakow.pl/",[589],"ICE Conference Center",", which is located directly by the Vistula\nriver, with beautiful view over the Wawel Royal Castle.",[18,3963,3964],{},[2694,3965],{"alt":3966,"src":3967},"\"Inside the ICE Conference Center \"","https://media.synyx.de/uploads//2015/06/IMG_20150624_110556.jpg",[18,3969,3970],{},"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,3972,3973],{},[27,3974,3975],{},"1. The rise of Microservices",[18,3977,3978],{},"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,3980,3981,3982,3985,3986,3991,3992,3997],{},"I think we are in the peak phase. I remember two years ago at",[27,3983,3984],{},"GeeCON 2013","as I saw the\nfirst ",[585,3987,3990],{"href":3988,"rel":3989},"http://2013.geecon.org/speakers/sam-newman.html",[589],"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 ",[585,3993,3996],{"href":3994,"rel":3995},"https://web.archive.org/web/20150503201315/http://cfp.devoxx.pl:80/2015/talk/MZA-9564/Modularity_in_post_microservice_world",[589],"one","\nlike this.",[18,3999,4000],{},[2694,4001],{"alt":4002,"src":4003},"\"Microservices Live-Coding Demo\"","https://media.synyx.de/uploads//2015/06/IMG_20150623_154822.jpg",[18,4005,4006],{},[27,4007,4008],{},"2. Reactive and Resilient by default",[18,4010,4011],{},"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,4013,4014],{},"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,4016,4017],{},"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,4019,4020],{},[2694,4021],{"alt":4022,"src":4023},"\"Main Room\"","https://media.synyx.de/uploads//2015/06/PANO_20150622_155838.jpg",[18,4025,4026],{},[27,4027,4028],{},"3. Functional Programming breaks (slowly) though",[18,4030,4031],{},"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,4033,4034],{},"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,4036,4037],{},[2694,4038],{"alt":4039,"src":4040},"\"View over Wawel Castle from ICE Center\"","https://media.synyx.de/uploads//2015/06/PANO_20150624_174232.jpg",[18,4042,4043],{},[27,4044,4045],{},"4. Java 8 throttles the rise of the new languages",[18,4047,4048],{},"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,4050,4051],{},"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,4053,4054],{},[27,4055,4056],{},"5. Java-Community focus on backend",[18,4058,4059],{},"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,4061,4062],{},"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,4064,4065],{},[27,4066,4067],{},"6. Big Data and NoSQL are here",[18,4069,4070],{},"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,4072,4073],{},"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,4075,4076],{},"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,4078,4079],{},[27,4080,4081],{},"7. Spring is all what you need",[18,4083,4084],{},"It was probably for the first time I didn’t see any talk about alternative approach to Spring-Based Technologies (Java\nEE excluding).",[18,4086,4087],{},"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,4089,4090],{},"The same applies for the data-persistence solutions like ORMs for instance.",[18,4092,4093],{},"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,4095,4096],{},"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,4098,4099],{},[27,4100,4101],{},"Conclusion",[18,4103,4104],{},"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,4106,4107,4108,4113],{},"I think it might be true that the Java Industry and the IT world in general is in\nthe",[585,4109,4112],{"href":4110,"rel":4111},"https://vimeo.com/130981099#t=2m56s",[589],"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,4115,4116],{},"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":48,"searchDepth":86,"depth":86,"links":4118},[],[613],"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":3933,"description":4126},"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",[4129,4130,4131,290,3327,4132,4133,4134,4135,1010],"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":4139,"title":4140,"author":4141,"body":4143,"category":5097,"date":5098,"description":4150,"extension":617,"link":5099,"meta":5100,"navigation":499,"path":5101,"seo":5102,"slug":5103,"stem":5104,"tags":5105,"teaser":5109,"__hash__":5110},"blog/blog/spock-test-well-and-prosper-from-the-unit-up-to-the-integration-level.md","Spock: Testing from the Unit up to the Integration Level",[4142],"messner",{"type":11,"value":4144,"toc":5095},[4145,4148,4151,4165,4178,4181,4184,4187,4190,4195,4218,4223,4317,4322,4388,4391,4396,4443,4446,4451,4591,4594,4609,4612,4617,4743,4748,4822,4843,4849,4856,4860,4874,4880,4884,4959,4964,5054,5065,5068,5071,5078,5085,5093],[14,4146,4140],{"id":4147},"spock-testing-from-the-unit-up-to-the-integration-level",[18,4149,4150],{},"There are a number of reasons to use the Spock testing framework:",[18,4152,4153,4154,4157,4158,3277,4161,4164],{},"First, tests – ",[573,4155,4156],{},"specifications"," in Spock speak – written in Spock are well structured, expressive and therefore provide\ngood readability. In addition, Spock has built-in features like ",[573,4159,4160],{},"data driven testing",[573,4162,4163],{},"interaction based testing"," (\nmocking). Data driven testing allows your test code to be reused, i.e. to be applied multiple times with different\nparameters.",[18,4166,4167,4168,4171,4172,4177],{},"Second, because Spock is a Groovy based DSL, specification code can become concise where the equivalent Java code would\nbe overly verbose. For example, Groovy provides native syntax for maps, lists and regular expressions. ",[573,4169,4170],{},"Closure\ncoercion"," helps providing stub implementations for one or more interface methods without having to write a stub class.\nAs Groovy and Java can freely be mixed together you can use any Java based library you like, or use Groovy based\nlibraries. For example\nthe ",[585,4173,4176],{"href":4174,"rel":4175},"https://web.archive.org/web/20150313003201/http://groovy.codehaus.org:80/modules/http-builder/home.html",[589],"HTTPBuilder","\nenhances the HttpComponents HttpClient by providing features like various builders & parsers and a streamlined REST\nclient.",[18,4179,4180],{},"Also the Spring framework supports Groovy and – not surprisingly – Spring TestContext framework works well with Spock:\napplication contexts can easily be made available to specifications via annotation, thus enabling integration testing at\nall levels.",[18,4182,4183],{},"Spock specifications can be run from an IDE just like normal JUnit tests and, last but not least, implementing them is a\ngreat opportunity to learn the Groovy language.",[18,4185,4186],{},"For demonstration purposes we’ll create a very simple Spring Boot web application that responds with string “prime” or\n“not prime” dependant on a number given by a request parameter. In case of errors the string “error” should be sent back\nto the client. Then we’ll create Spock specifications, both for unit and integration testing.",[18,4188,4189],{},"We start by defining a service interface, its implementation and a controller class:",[18,4191,4192],{},[50,4193,4194],{},"src/main/java/prime/service/PrimeService.java",[43,4196,4198],{"className":288,"code":4197,"language":290,"meta":48,"style":48},"\npublic interface PrimeService {\n boolean isPrime(int number);\n}\n\n",[50,4199,4200,4204,4209,4214],{"__ignoreMap":48},[53,4201,4202],{"class":55,"line":56},[53,4203,500],{"emptyLinePlaceholder":499},[53,4205,4206],{"class":55,"line":86},[53,4207,4208],{},"public interface PrimeService {\n",[53,4210,4211],{"class":55,"line":126},[53,4212,4213],{}," boolean isPrime(int number);\n",[53,4215,4216],{"class":55,"line":163},[53,4217,282],{},[18,4219,4220],{},[50,4221,4222],{},"src/main/java/prime/service/PrimeServiceImpl.java",[43,4224,4226],{"className":288,"code":4225,"language":290,"meta":48,"style":48},"\n@Service\npublic class PrimeServiceImpl implements PrimeService {\n @Override\n public boolean isPrime(int number) {\n if (number \u003C 0) {\n throw new IllegalArgumentException(\"argument must not be negative\");\n }\n if (number \u003C= 2) {\n return number == 2 ? true : false;\n }\n for (int i = 2; i \u003C Math.sqrt(number) + 1; i++) {\n if (number % i == 0) {\n return false;\n }\n }\n return true;\n }\n}\n\n",[50,4227,4228,4232,4237,4242,4246,4251,4256,4261,4265,4270,4275,4279,4284,4289,4294,4299,4303,4308,4312],{"__ignoreMap":48},[53,4229,4230],{"class":55,"line":56},[53,4231,500],{"emptyLinePlaceholder":499},[53,4233,4234],{"class":55,"line":86},[53,4235,4236],{},"@Service\n",[53,4238,4239],{"class":55,"line":126},[53,4240,4241],{},"public class PrimeServiceImpl implements PrimeService {\n",[53,4243,4244],{"class":55,"line":163},[53,4245,1936],{},[53,4247,4248],{"class":55,"line":186},[53,4249,4250],{}," public boolean isPrime(int number) {\n",[53,4252,4253],{"class":55,"line":221},[53,4254,4255],{}," if (number \u003C 0) {\n",[53,4257,4258],{"class":55,"line":242},[53,4259,4260],{}," throw new IllegalArgumentException(\"argument must not be negative\");\n",[53,4262,4263],{"class":55,"line":273},[53,4264,1966],{},[53,4266,4267],{"class":55,"line":279},[53,4268,4269],{}," if (number \u003C= 2) {\n",[53,4271,4272],{"class":55,"line":496},[53,4273,4274],{}," return number == 2 ? true : false;\n",[53,4276,4277],{"class":55,"line":503},[53,4278,1966],{},[53,4280,4281],{"class":55,"line":509},[53,4282,4283],{}," for (int i = 2; i \u003C Math.sqrt(number) + 1; i++) {\n",[53,4285,4286],{"class":55,"line":515},[53,4287,4288],{}," if (number % i == 0) {\n",[53,4290,4291],{"class":55,"line":521},[53,4292,4293],{}," return false;\n",[53,4295,4296],{"class":55,"line":527},[53,4297,4298],{}," }\n",[53,4300,4301],{"class":55,"line":533},[53,4302,1966],{},[53,4304,4305],{"class":55,"line":539},[53,4306,4307],{}," return true;\n",[53,4309,4310],{"class":55,"line":545},[53,4311,860],{},[53,4313,4315],{"class":55,"line":4314},19,[53,4316,282],{},[18,4318,4319],{},[50,4320,4321],{},"src/main/groovy/prime/web/PrimeController.groovy",[43,4323,4325],{"className":288,"code":4324,"language":290,"meta":48,"style":48},"\n@RestController\nclass PrimeController {\n @Autowired PrimeService primeService;\n @ExceptionHandler(Exception)\n String handleError() {\n 'error';\n }\n @RequestMapping('/prime')\n String isPrime(@RequestParam int n) {\n primeService.isPrime(n) ? 'prime' : 'not prime';\n }\n}\n\n",[50,4326,4327,4331,4336,4341,4346,4351,4356,4361,4365,4370,4375,4380,4384],{"__ignoreMap":48},[53,4328,4329],{"class":55,"line":56},[53,4330,500],{"emptyLinePlaceholder":499},[53,4332,4333],{"class":55,"line":86},[53,4334,4335],{},"@RestController\n",[53,4337,4338],{"class":55,"line":126},[53,4339,4340],{},"class PrimeController {\n",[53,4342,4343],{"class":55,"line":163},[53,4344,4345],{}," @Autowired PrimeService primeService;\n",[53,4347,4348],{"class":55,"line":186},[53,4349,4350],{}," @ExceptionHandler(Exception)\n",[53,4352,4353],{"class":55,"line":221},[53,4354,4355],{}," String handleError() {\n",[53,4357,4358],{"class":55,"line":242},[53,4359,4360],{}," 'error';\n",[53,4362,4363],{"class":55,"line":273},[53,4364,860],{},[53,4366,4367],{"class":55,"line":279},[53,4368,4369],{}," @RequestMapping('/prime')\n",[53,4371,4372],{"class":55,"line":496},[53,4373,4374],{}," String isPrime(@RequestParam int n) {\n",[53,4376,4377],{"class":55,"line":503},[53,4378,4379],{}," primeService.isPrime(n) ? 'prime' : 'not prime';\n",[53,4381,4382],{"class":55,"line":509},[53,4383,860],{},[53,4385,4386],{"class":55,"line":515},[53,4387,282],{},[18,4389,4390],{},"Since the application is based on Spring Boot we also add this class...",[18,4392,4393],{},[50,4394,4395],{},"src/main/java/prime/Application.java",[43,4397,4399],{"className":288,"code":4398,"language":290,"meta":48,"style":48},"\n@Configuration\n@EnableAutoConfiguration\n@ComponentScan\npublic class Application {\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n}\n\n",[50,4400,4401,4405,4410,4415,4420,4425,4430,4435,4439],{"__ignoreMap":48},[53,4402,4403],{"class":55,"line":56},[53,4404,500],{"emptyLinePlaceholder":499},[53,4406,4407],{"class":55,"line":86},[53,4408,4409],{},"@Configuration\n",[53,4411,4412],{"class":55,"line":126},[53,4413,4414],{},"@EnableAutoConfiguration\n",[53,4416,4417],{"class":55,"line":163},[53,4418,4419],{},"@ComponentScan\n",[53,4421,4422],{"class":55,"line":186},[53,4423,4424],{},"public class Application {\n",[53,4426,4427],{"class":55,"line":221},[53,4428,4429],{}," public static void main(String[] args) {\n",[53,4431,4432],{"class":55,"line":242},[53,4433,4434],{}," SpringApplication.run(Application.class, args);\n",[53,4436,4437],{"class":55,"line":273},[53,4438,2543],{},[53,4440,4441],{"class":55,"line":279},[53,4442,282],{},[18,4444,4445],{},"... and a build script:",[18,4447,4448],{},[50,4449,4450],{},"src/build.gradle",[43,4452,4454],{"className":288,"code":4453,"language":290,"meta":48,"style":48},"\nbuildscript {\n repositories {\n mavenCentral()\n }\n dependencies {\n classpath(\"org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE\")\n }\n}\napply plugin: 'groovy'\napply plugin: 'spring-boot'\njar {\n baseName = 'prime'\n version = '0.10.0'\n}\nrepositories {\n mavenCentral()\n}\ndependencies {\n compile(\"org.codehaus.groovy:groovy-all:2.3.6\")\n compile(\"org.springframework.boot:spring-boot-starter-jetty\")\n compile(\"org.springframework.boot:spring-boot-starter-web\") {\n exclude module: \"spring-boot-starter-tomcat\"\n }\n testCompile(\"org.springframework.boot:spring-boot-starter-test\")\n testCompile(\"org.spockframework:spock-core:0.7-groovy-2.0\")\n}\n\n",[50,4455,4456,4460,4465,4470,4475,4479,4484,4489,4493,4497,4502,4507,4512,4517,4522,4526,4531,4536,4540,4545,4551,4557,4563,4569,4574,4580,4586],{"__ignoreMap":48},[53,4457,4458],{"class":55,"line":56},[53,4459,500],{"emptyLinePlaceholder":499},[53,4461,4462],{"class":55,"line":86},[53,4463,4464],{},"buildscript {\n",[53,4466,4467],{"class":55,"line":126},[53,4468,4469],{}," repositories {\n",[53,4471,4472],{"class":55,"line":163},[53,4473,4474],{}," mavenCentral()\n",[53,4476,4477],{"class":55,"line":186},[53,4478,2543],{},[53,4480,4481],{"class":55,"line":221},[53,4482,4483],{}," dependencies {\n",[53,4485,4486],{"class":55,"line":242},[53,4487,4488],{}," classpath(\"org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE\")\n",[53,4490,4491],{"class":55,"line":273},[53,4492,2543],{},[53,4494,4495],{"class":55,"line":279},[53,4496,282],{},[53,4498,4499],{"class":55,"line":496},[53,4500,4501],{},"apply plugin: 'groovy'\n",[53,4503,4504],{"class":55,"line":503},[53,4505,4506],{},"apply plugin: 'spring-boot'\n",[53,4508,4509],{"class":55,"line":509},[53,4510,4511],{},"jar {\n",[53,4513,4514],{"class":55,"line":515},[53,4515,4516],{}," baseName = 'prime'\n",[53,4518,4519],{"class":55,"line":521},[53,4520,4521],{}," version = '0.10.0'\n",[53,4523,4524],{"class":55,"line":527},[53,4525,282],{},[53,4527,4528],{"class":55,"line":533},[53,4529,4530],{},"repositories {\n",[53,4532,4533],{"class":55,"line":539},[53,4534,4535],{}," mavenCentral()\n",[53,4537,4538],{"class":55,"line":545},[53,4539,282],{},[53,4541,4542],{"class":55,"line":4314},[53,4543,4544],{},"dependencies {\n",[53,4546,4548],{"class":55,"line":4547},20,[53,4549,4550],{}," compile(\"org.codehaus.groovy:groovy-all:2.3.6\")\n",[53,4552,4554],{"class":55,"line":4553},21,[53,4555,4556],{}," compile(\"org.springframework.boot:spring-boot-starter-jetty\")\n",[53,4558,4560],{"class":55,"line":4559},22,[53,4561,4562],{}," compile(\"org.springframework.boot:spring-boot-starter-web\") {\n",[53,4564,4566],{"class":55,"line":4565},23,[53,4567,4568],{}," exclude module: \"spring-boot-starter-tomcat\"\n",[53,4570,4572],{"class":55,"line":4571},24,[53,4573,2543],{},[53,4575,4577],{"class":55,"line":4576},25,[53,4578,4579],{}," testCompile(\"org.springframework.boot:spring-boot-starter-test\")\n",[53,4581,4583],{"class":55,"line":4582},26,[53,4584,4585],{}," testCompile(\"org.spockframework:spock-core:0.7-groovy-2.0\")\n",[53,4587,4589],{"class":55,"line":4588},27,[53,4590,282],{},[18,4592,4593],{},"The Groovy plugin handles mixed Groovy and Java code in the project. Not only is our controller class written in Groovy;\nthe specifications for unit and integration testing will be too.",[18,4595,4596,4597,4600,4601,4604,4605,4608],{},"If Groovy is used in production code we have to include the ",[27,4598,4599],{},"groovy-all"," dependency to the ",[50,4602,4603],{},"compile"," configuration,\notherwise this dependency should be added to the ",[50,4606,4607],{},"testCompile"," configuration.",[18,4610,4611],{},"Now we write unit specifications which verify the correctness of service implementation and controller:",[18,4613,4614],{},[50,4615,4616],{},"src/test/groovy/prime/service/PrimeServiceImplSpec.groovy",[43,4618,4620],{"className":288,"code":4619,"language":290,"meta":48,"style":48},"\nclass PrimeServiceImplSpec extends Specification {\n PrimeServiceImpl sut = new PrimeServiceImpl();\n def \"test if the given number is prime\"() {\n expect:\n sut.isPrime(n) == prime\n where:\n n | prime\n 0 | false\n 1 | false\n 2 | true\n 3 | true\n 4 | false\n 5 | true\n 6 | false\n 7 | true\n }\n def \"check method argument constraints\"() {\n when:\n sut.isPrime(-1)\n then:\n def e = thrown(IllegalArgumentException)\n e.message == 'argument must not be negative'\n }\n}\n\n",[50,4621,4622,4626,4631,4636,4641,4646,4651,4656,4661,4666,4671,4676,4681,4686,4691,4696,4701,4705,4710,4715,4720,4725,4730,4735,4739],{"__ignoreMap":48},[53,4623,4624],{"class":55,"line":56},[53,4625,500],{"emptyLinePlaceholder":499},[53,4627,4628],{"class":55,"line":86},[53,4629,4630],{},"class PrimeServiceImplSpec extends Specification {\n",[53,4632,4633],{"class":55,"line":126},[53,4634,4635],{}," PrimeServiceImpl sut = new PrimeServiceImpl();\n",[53,4637,4638],{"class":55,"line":163},[53,4639,4640],{}," def \"test if the given number is prime\"() {\n",[53,4642,4643],{"class":55,"line":186},[53,4644,4645],{}," expect:\n",[53,4647,4648],{"class":55,"line":221},[53,4649,4650],{}," sut.isPrime(n) == prime\n",[53,4652,4653],{"class":55,"line":242},[53,4654,4655],{}," where:\n",[53,4657,4658],{"class":55,"line":273},[53,4659,4660],{}," n | prime\n",[53,4662,4663],{"class":55,"line":279},[53,4664,4665],{}," 0 | false\n",[53,4667,4668],{"class":55,"line":496},[53,4669,4670],{}," 1 | false\n",[53,4672,4673],{"class":55,"line":503},[53,4674,4675],{}," 2 | true\n",[53,4677,4678],{"class":55,"line":509},[53,4679,4680],{}," 3 | true\n",[53,4682,4683],{"class":55,"line":515},[53,4684,4685],{}," 4 | false\n",[53,4687,4688],{"class":55,"line":521},[53,4689,4690],{}," 5 | true\n",[53,4692,4693],{"class":55,"line":527},[53,4694,4695],{}," 6 | false\n",[53,4697,4698],{"class":55,"line":533},[53,4699,4700],{}," 7 | true\n",[53,4702,4703],{"class":55,"line":539},[53,4704,860],{},[53,4706,4707],{"class":55,"line":545},[53,4708,4709],{}," def \"check method argument constraints\"() {\n",[53,4711,4712],{"class":55,"line":4314},[53,4713,4714],{}," when:\n",[53,4716,4717],{"class":55,"line":4547},[53,4718,4719],{}," sut.isPrime(-1)\n",[53,4721,4722],{"class":55,"line":4553},[53,4723,4724],{}," then:\n",[53,4726,4727],{"class":55,"line":4559},[53,4728,4729],{}," def e = thrown(IllegalArgumentException)\n",[53,4731,4732],{"class":55,"line":4565},[53,4733,4734],{}," e.message == 'argument must not be negative'\n",[53,4736,4737],{"class":55,"line":4571},[53,4738,860],{},[53,4740,4741],{"class":55,"line":4576},[53,4742,282],{},[18,4744,4745],{},[50,4746,4747],{},"src/test/groovy/prime/web/PrimeControllerSpec.groovy",[43,4749,4751],{"className":288,"code":4750,"language":290,"meta":48,"style":48},"\nclass PrimeControllerSpec extends Specification {\n def \"returns string 'prime' when service detects prime number\"() {\n int p = 3\n def stub = { it == p ? true : false }\n expect:\n new PrimeController(primeService: stub).isPrime(p) == 'prime'\n }\n def \"returns 'not prime' when service detects non-prime number\"() {\n int n = 4\n def stub = { it == n ? false : true }\n expect:\n new PrimeController(primeService: stub).isPrime(n) == 'not prime'\n }\n}\n\n",[50,4752,4753,4757,4762,4767,4772,4777,4781,4786,4790,4795,4800,4805,4809,4814,4818],{"__ignoreMap":48},[53,4754,4755],{"class":55,"line":56},[53,4756,500],{"emptyLinePlaceholder":499},[53,4758,4759],{"class":55,"line":86},[53,4760,4761],{},"class PrimeControllerSpec extends Specification {\n",[53,4763,4764],{"class":55,"line":126},[53,4765,4766],{}," def \"returns string 'prime' when service detects prime number\"() {\n",[53,4768,4769],{"class":55,"line":163},[53,4770,4771],{}," int p = 3\n",[53,4773,4774],{"class":55,"line":186},[53,4775,4776],{}," def stub = { it == p ? true : false }\n",[53,4778,4779],{"class":55,"line":221},[53,4780,4645],{},[53,4782,4783],{"class":55,"line":242},[53,4784,4785],{}," new PrimeController(primeService: stub).isPrime(p) == 'prime'\n",[53,4787,4788],{"class":55,"line":273},[53,4789,860],{},[53,4791,4792],{"class":55,"line":279},[53,4793,4794],{}," def \"returns 'not prime' when service detects non-prime number\"() {\n",[53,4796,4797],{"class":55,"line":496},[53,4798,4799],{}," int n = 4\n",[53,4801,4802],{"class":55,"line":503},[53,4803,4804],{}," def stub = { it == n ? false : true }\n",[53,4806,4807],{"class":55,"line":509},[53,4808,4645],{},[53,4810,4811],{"class":55,"line":515},[53,4812,4813],{}," new PrimeController(primeService: stub).isPrime(n) == 'not prime'\n",[53,4815,4816],{"class":55,"line":521},[53,4817,860],{},[53,4819,4820],{"class":55,"line":527},[53,4821,282],{},[18,4823,4824,4825,4828,4829,4832,4833,4835,4836,4839,4840,986],{},"The first ",[573,4826,4827],{},"feature"," method in ",[50,4830,4831],{},"PrimeServiceImplSpec"," shows how Spocks ",[573,4834,4160],{}," concept works and in\n",[50,4837,4838],{},"PrimeControllerSpec"," we create service stubs by ",[573,4841,4842],{},"closure coercion",[18,4844,4845,4846,4848],{},"Spock does also provide a means for ",[573,4847,4163],{},", i.e. mocking and stubbing.",[18,4850,4851,4852,4855],{},"Before we implement an integration specification to verify the applications behaviour, we have to add another dependency\nin the build script. The ",[27,4853,4854],{},"spock-spring"," dependency enables to use the Spring TestContext framework together with\nSpock which is required for our integration specification.",[18,4857,4858],{},[50,4859,4450],{},[43,4861,4863],{"className":288,"code":4862,"language":290,"meta":48,"style":48},"\ntestCompile(\"org.spockframework:spock-spring:0.7-groovy-2.0\")\n\n",[50,4864,4865,4869],{"__ignoreMap":48},[53,4866,4867],{"class":55,"line":56},[53,4868,500],{"emptyLinePlaceholder":499},[53,4870,4871],{"class":55,"line":86},[53,4872,4873],{},"testCompile(\"org.spockframework:spock-spring:0.7-groovy-2.0\")\n",[18,4875,4876,4877],{},"In order to separate the long running integration specifications from the unit specifications, we modify the build\nscript by defining a corresponding sourceSet, associated configurations and task. Integration testing can now be\ntriggered with ",[50,4878,4879],{},"gradle integTest",[18,4881,4882],{},[50,4883,4450],{},[43,4885,4887],{"className":288,"code":4886,"language":290,"meta":48,"style":48},"\nsourceSets {\n integTest {\n compileClasspath += main.output + test.output\n runtimeClasspath += main.output + test.output\n }\n}\nconfigurations {\n integTestCompile.extendsFrom testCompile\n integTestRuntime.extendsFrom testRuntime\n}\ntask integTest(type: Test) {\n testClassesDir = sourceSets.integTest.output.classesDir\n classpath = sourceSets.integTest.runtimeClasspath\n}\n\n",[50,4888,4889,4893,4898,4903,4908,4913,4917,4921,4926,4931,4936,4940,4945,4950,4955],{"__ignoreMap":48},[53,4890,4891],{"class":55,"line":56},[53,4892,500],{"emptyLinePlaceholder":499},[53,4894,4895],{"class":55,"line":86},[53,4896,4897],{},"sourceSets {\n",[53,4899,4900],{"class":55,"line":126},[53,4901,4902],{}," integTest {\n",[53,4904,4905],{"class":55,"line":163},[53,4906,4907],{}," compileClasspath += main.output + test.output\n",[53,4909,4910],{"class":55,"line":186},[53,4911,4912],{}," runtimeClasspath += main.output + test.output\n",[53,4914,4915],{"class":55,"line":221},[53,4916,860],{},[53,4918,4919],{"class":55,"line":242},[53,4920,282],{},[53,4922,4923],{"class":55,"line":273},[53,4924,4925],{},"configurations {\n",[53,4927,4928],{"class":55,"line":279},[53,4929,4930],{}," integTestCompile.extendsFrom testCompile\n",[53,4932,4933],{"class":55,"line":496},[53,4934,4935],{}," integTestRuntime.extendsFrom testRuntime\n",[53,4937,4938],{"class":55,"line":503},[53,4939,282],{},[53,4941,4942],{"class":55,"line":509},[53,4943,4944],{},"task integTest(type: Test) {\n",[53,4946,4947],{"class":55,"line":515},[53,4948,4949],{}," testClassesDir = sourceSets.integTest.output.classesDir\n",[53,4951,4952],{"class":55,"line":521},[53,4953,4954],{}," classpath = sourceSets.integTest.runtimeClasspath\n",[53,4956,4957],{"class":55,"line":527},[53,4958,282],{},[18,4960,4961],{},[50,4962,4963],{},"src/integTest/groovy/prime/PrimeSpec.groovy",[43,4965,4967],{"className":288,"code":4966,"language":290,"meta":48,"style":48},"\n@ContextConfiguration(loader = SpringApplicationContextLoader, classes = Application)\n@WebAppConfiguration\n@IntegrationTest\nclass PrimeSpec extends Specification {\n @Value('${local.server.port}')\n int port;\n def \"server answers with 'prime' or 'not prime' or 'error'\"() {\n expect:\n \"http://localhost:$port/prime?n=$n\"\n .toURL().text == response\n where:\n n | response\n 23 | 'prime'\n 42 | 'not prime'\n -1 | 'error'\n }\n}\n\n",[50,4968,4969,4973,4978,4983,4988,4993,4998,5003,5008,5012,5017,5022,5026,5031,5036,5041,5046,5050],{"__ignoreMap":48},[53,4970,4971],{"class":55,"line":56},[53,4972,500],{"emptyLinePlaceholder":499},[53,4974,4975],{"class":55,"line":86},[53,4976,4977],{},"@ContextConfiguration(loader = SpringApplicationContextLoader, classes = Application)\n",[53,4979,4980],{"class":55,"line":126},[53,4981,4982],{},"@WebAppConfiguration\n",[53,4984,4985],{"class":55,"line":163},[53,4986,4987],{},"@IntegrationTest\n",[53,4989,4990],{"class":55,"line":186},[53,4991,4992],{},"class PrimeSpec extends Specification {\n",[53,4994,4995],{"class":55,"line":221},[53,4996,4997],{}," @Value('${local.server.port}')\n",[53,4999,5000],{"class":55,"line":242},[53,5001,5002],{}," int port;\n",[53,5004,5005],{"class":55,"line":273},[53,5006,5007],{}," def \"server answers with 'prime' or 'not prime' or 'error'\"() {\n",[53,5009,5010],{"class":55,"line":279},[53,5011,4645],{},[53,5013,5014],{"class":55,"line":496},[53,5015,5016],{}," \"http://localhost:$port/prime?n=$n\"\n",[53,5018,5019],{"class":55,"line":503},[53,5020,5021],{}," .toURL().text == response\n",[53,5023,5024],{"class":55,"line":509},[53,5025,4655],{},[53,5027,5028],{"class":55,"line":515},[53,5029,5030],{}," n | response\n",[53,5032,5033],{"class":55,"line":521},[53,5034,5035],{}," 23 | 'prime'\n",[53,5037,5038],{"class":55,"line":527},[53,5039,5040],{}," 42 | 'not prime'\n",[53,5042,5043],{"class":55,"line":533},[53,5044,5045],{}," -1 | 'error'\n",[53,5047,5048],{"class":55,"line":539},[53,5049,860],{},[53,5051,5052],{"class":55,"line":545},[53,5053,282],{},[18,5055,5056,5057,5060,5061,5064],{},"In ",[50,5058,5059],{},"PrimeSpec"," the Spring Boot annotation ",[50,5062,5063],{},"@IntegrationTest"," causes the embedded application server to start. As an\nalternative we could use MockMvc to verify application response. Integration testing with MockMvc doesn't require a\nrunning application server.",[18,5066,5067],{},"To summarize, the Spock testing famework is a good example how Groovy can help Java developers. By writing Spock\nspecifications, your test code - whether on the unit oder the integration level - can become concise and expressive.\nIntegration into the build process is easy and your favorite IDE will handle specifications just like regular JUnit\ntests.",[18,5069,5070],{},"Links:",[18,5072,5073],{},[585,5074,5077],{"href":5075,"rel":5076},"https://code.google.com/p/spock/wiki/SpockBasics/",[589],"SpockBasics - Anatomy of a Spock specification",[18,5079,5080],{},[585,5081,5084],{"href":5082,"rel":5083},"http://spock-framework.readthedocs.org/en/latest/",[589],"Spock Framework Reference Documentation",[18,5086,5087,5092],{},[585,5088,5091],{"href":5089,"rel":5090},"http://de.slideshare.net/kousen/spock-friendly-testing",[589],"Spock: Test Well and Prosper"," by Ken Kousen",[607,5094,989],{},{"title":48,"searchDepth":86,"depth":86,"links":5096},[],[613],"2014-09-15T20:19:31","https://synyx.de/blog/spock-test-well-and-prosper-from-the-unit-up-to-the-integration-level/",{},"/blog/spock-test-well-and-prosper-from-the-unit-up-to-the-integration-level",{"title":4140,"description":4150},"spock-test-well-and-prosper-from-the-unit-up-to-the-integration-level","blog/spock-test-well-and-prosper-from-the-unit-up-to-the-integration-level",[5106,5107,1010,5108],"groovy","spock","test","There are a number of reasons to use the Spock testing framework: First, tests – specifications in Spock speak – written in Spock are well structured, expressive and therefore provide…","PUbBjgfCyqC5t0kqqAl71ChX_YmSeT1Y0stRAuBBZHo",{"id":5112,"title":5113,"author":5114,"body":5115,"category":5545,"date":5546,"description":5547,"extension":617,"link":5548,"meta":5549,"navigation":499,"path":5550,"seo":5551,"slug":5119,"stem":5553,"tags":5554,"teaser":5557,"__hash__":5558},"blog/blog/client-code-ignores-repository-implementations-developers-do-not.md","Client code ignores REPOSITORY implementations; developers do not",[4142],{"type":11,"value":5116,"toc":5541},[5117,5120,5135,5141,5144,5208,5223,5232,5260,5264,5271,5279,5286,5306,5317,5358,5369,5382,5404,5434,5440,5446,5475,5489,5500,5507,5510,5515,5518,5521,5531,5536,5539],[14,5118,5113],{"id":5119},"client-code-ignores-repository-implementations-developers-do-not",[18,5121,5122,5123,5126,5127,5130,5131,5134],{},"Our team is working on an application for one of our clients, a service provider for container logistics, shipping cargo\nbetween seaports, terminals and other loading sites. The business domain also includes the calculation of shipping\nprices subjected to the agreements met between the shipping company and its customers. We recently implemented the\nconcept of so called ",[573,5124,5125],{},"offers"," into the application, whereas each offer contains multiple ",[573,5128,5129],{},"terminal-specific prices",".\nOne or more offers may be assigned to a ",[573,5132,5133],{},"customer"," (see diagram below, capturing these domain concepts).",[18,5136,5137],{},[2694,5138],{"alt":5139,"src":5140},"\"domainmodel\"","https://media.synyx.de/uploads//2013/12/domainmodel.png",[18,5142,5143],{},"Our technology stack encompasses Spring Framework and JPA as the persistence technology. All the applications data is\nstored in a relational database.",[43,5145,5147],{"className":288,"code":5146,"language":290,"meta":48,"style":48},"\n@Entity\npublic class Offer {\n @ManyToMany(fetch = FetchType.EAGER)\n private Map\u003C Long, Price> importBargePrices;\n @ManyToMany(fetch = FetchType.EAGER)\n private Map\u003C Long, Price> exportBargePrices;\n @ManyToMany(fetch = FetchType.EAGER)\n private Map\u003C Long, Price> importRailPrices;\n @ManyToMany(fetch = FetchType.EAGER)\n private Map\u003C Long, Price> exportRailPrices;\n ...\n}\n\n",[50,5148,5149,5153,5158,5163,5168,5173,5177,5182,5186,5191,5195,5200,5204],{"__ignoreMap":48},[53,5150,5151],{"class":55,"line":56},[53,5152,500],{"emptyLinePlaceholder":499},[53,5154,5155],{"class":55,"line":86},[53,5156,5157],{},"@Entity\n",[53,5159,5160],{"class":55,"line":126},[53,5161,5162],{},"public class Offer {\n",[53,5164,5165],{"class":55,"line":163},[53,5166,5167],{}," @ManyToMany(fetch = FetchType.EAGER)\n",[53,5169,5170],{"class":55,"line":186},[53,5171,5172],{}," private Map\u003C Long, Price> importBargePrices;\n",[53,5174,5175],{"class":55,"line":221},[53,5176,5167],{},[53,5178,5179],{"class":55,"line":242},[53,5180,5181],{}," private Map\u003C Long, Price> exportBargePrices;\n",[53,5183,5184],{"class":55,"line":273},[53,5185,5167],{},[53,5187,5188],{"class":55,"line":279},[53,5189,5190],{}," private Map\u003C Long, Price> importRailPrices;\n",[53,5192,5193],{"class":55,"line":496},[53,5194,5167],{},[53,5196,5197],{"class":55,"line":503},[53,5198,5199],{}," private Map\u003C Long, Price> exportRailPrices;\n",[53,5201,5202],{"class":55,"line":509},[53,5203,276],{},[53,5205,5206],{"class":55,"line":515},[53,5207,282],{},[18,5209,5210,5211,5214,5215,5218,5219,5222],{},"Note the ",[573,5212,5213],{},"collection-valued associations"," between offer and price: in the current implementation these have been\nconfigured to be ",[573,5216,5217],{},"eagerly"," fetched. Setting the fetch type to ",[573,5220,5221],{},"eager loading"," forces the JPA provider to instantly fetch\nthese entity attributes from the database when an offer is read.",[18,5224,5225,5226,5231],{},"Beyond JPA we also use ",[585,5227,5230],{"href":5228,"rel":5229},"http://projects.spring.io/spring-data/",[589],"Spring Data",". So, there’s a repository representing a\ncollection of offers and encapsulating the internal details of database access:",[43,5233,5235],{"className":288,"code":5234,"language":290,"meta":48,"style":48},"\npublic interface OfferRepository extends\n JpaRepository\u003C Offer, Long> {\n Offer findByLabel(String label);\n}\n\n",[50,5236,5237,5241,5246,5251,5256],{"__ignoreMap":48},[53,5238,5239],{"class":55,"line":56},[53,5240,500],{"emptyLinePlaceholder":499},[53,5242,5243],{"class":55,"line":86},[53,5244,5245],{},"public interface OfferRepository extends\n",[53,5247,5248],{"class":55,"line":126},[53,5249,5250],{}," JpaRepository\u003C Offer, Long> {\n",[53,5252,5253],{"class":55,"line":163},[53,5254,5255],{}," Offer findByLabel(String label);\n",[53,5257,5258],{"class":55,"line":186},[53,5259,282],{},[649,5261,5263],{"id":5262},"when-things-went-wrong","When things went wrong",[18,5265,5266,5267,5270],{},"Before deploying our application to production, we did some ",[573,5268,5269],{},"exploratory testing"," in a production-like environment.\nDuring testing, we realized that the application ran out of memory when doing one of the the following operations:",[577,5272,5273,5276],{},[580,5274,5275],{},"loading an offer from the database (in order to display its details)",[580,5277,5278],{},"assigning an offer to a customer",[18,5280,5281,5282,5285],{},"These two use cases have been tested constantly by our acceptance tests, every time the ",[573,5283,5284],{},"Continuous Integration"," server\nfinnished building and deploying the application into a test server. Nevertheless, both use cases now turn out to be\nbroken in a production-like environment. One obvious difference between the environment for CI acceptance testing and\nthe production-like environment is the amount of data stored in the database.",[18,5287,5288,5289,5294,5295,3610,5298,5301,5302,5305],{},"Java Virtual Machine monitoring and profiling\nwith ",[585,5290,5293],{"href":5291,"rel":5292},"http://docs.oracle.com/javase/7/docs/technotes/tools/share/jvisualvm.html",[589],"jvisualvm"," revealed huge memory\nconsumption when reconstituting a single stored offer entity using the repository method ",[50,5296,5297],{},"findOne",[50,5299,5300],{},"OfferRepository","\nextends ",[50,5303,5304],{},"JpaRepository"," and therefore provides the aforementioned method;",[18,5307,5308,5309,5312,5313,5316],{},"Spring Datas ",[50,5310,5311],{},"SimpleJpaRepository"," implements this method to simply call JPA EntityManagers ",[50,5314,5315],{},"find"," method.",[43,5318,5320],{"className":288,"code":5319,"language":290,"meta":48,"style":48},"\n@Override\npublic T findOne(ID id) {\n ...\n Class\u003C T> domainType = getDomainClass();\n return type == null ? em.find(domainType, id)\n : em.find(domainType, id, type);\n}\n\n",[50,5321,5322,5326,5330,5335,5339,5344,5349,5354],{"__ignoreMap":48},[53,5323,5324],{"class":55,"line":56},[53,5325,500],{"emptyLinePlaceholder":499},[53,5327,5328],{"class":55,"line":86},[53,5329,2812],{},[53,5331,5332],{"class":55,"line":126},[53,5333,5334],{},"public T findOne(ID id) {\n",[53,5336,5337],{"class":55,"line":163},[53,5338,276],{},[53,5340,5341],{"class":55,"line":186},[53,5342,5343],{}," Class\u003C T> domainType = getDomainClass();\n",[53,5345,5346],{"class":55,"line":221},[53,5347,5348],{}," return type == null ? em.find(domainType, id)\n",[53,5350,5351],{"class":55,"line":242},[53,5352,5353],{}," : em.find(domainType, id, type);\n",[53,5355,5356],{"class":55,"line":273},[53,5357,282],{},[18,5359,5360,5361,5364,5365,5368],{},"Analyzing the SQL that gets constructed and executed by the JPA provider discloses a relatively big number of ",[573,5362,5363],{},"outer\njoins"," in the generated ",[573,5366,5367],{},"select"," statement; this is not an issue with Spring Data.",[2200,5370,5371,5374,5377],{},[18,5372,5373],{},"“Spring Data JPA itself does not control the interaction with the database directly. All it does is interacting with\nthe EntityManager, so effectively all behavioral effects are defined by JPA and",[18,5375,5376],{},"the underlying OR-mapper.”",[18,5378,5379],{},[53,5380,5381],{},"Oliver Gierke, Spring Data Project",[18,5383,5384,5385,5388,5389,5391,5392,5394,5395,5397,5398,5400,5401,5403],{},"Introducing the finder method ",[50,5386,5387],{},"findById"," into ",[50,5390,5300],{}," and replacing calls to ",[50,5393,5297],{}," by calls to ",[50,5396,5387],{},"\nhad an effect on the generated SQL: instead of one ",[573,5399,5367],{}," statement with more than twenty outer joins, we now have\nmultiple ",[573,5402,5367],{}," statements each having three outer joins at most.",[43,5405,5407],{"className":288,"code":5406,"language":290,"meta":48,"style":48},"\npublic interface OfferRepository extends\n JpaRepository\u003C Offer, Long> {\n Offer findById(Long id);\n Offer findByLabel(String label);\n}\n\n",[50,5408,5409,5413,5417,5421,5426,5430],{"__ignoreMap":48},[53,5410,5411],{"class":55,"line":56},[53,5412,500],{"emptyLinePlaceholder":499},[53,5414,5415],{"class":55,"line":86},[53,5416,5245],{},[53,5418,5419],{"class":55,"line":126},[53,5420,5250],{},[53,5422,5423],{"class":55,"line":163},[53,5424,5425],{}," Offer findById(Long id);\n",[53,5427,5428],{"class":55,"line":186},[53,5429,5255],{},[53,5431,5432],{"class":55,"line":221},[53,5433,282],{},[18,5435,5436,5437,5439],{},"In both cases, because the offers collection-valued associations are configured to be eagerly loaded, price data is\nimplicitly fetched from the database when an offer is initially read. And in case of using ",[50,5438,5297],{},", the generated SQL\nstatement causes the application to crash with an OutOfMemoryError.",[18,5441,5442,5443,5445],{},"But just replacing any call of ",[50,5444,5297],{}," falls too short. Look at the following code that assigns the specified offer to\na customer:",[43,5447,5449],{"className":288,"code":5448,"language":290,"meta":48,"style":48},"\nOffer offer = offerRepository.findById(offerId);\nCustomer customer = customerRepository.findById(customerId);\ncustomer.addOffer(offer);\ncustomerRepository.save(customer);\n\n",[50,5450,5451,5455,5460,5465,5470],{"__ignoreMap":48},[53,5452,5453],{"class":55,"line":56},[53,5454,500],{"emptyLinePlaceholder":499},[53,5456,5457],{"class":55,"line":86},[53,5458,5459],{},"Offer offer = offerRepository.findById(offerId);\n",[53,5461,5462],{"class":55,"line":126},[53,5463,5464],{},"Customer customer = customerRepository.findById(customerId);\n",[53,5466,5467],{"class":55,"line":163},[53,5468,5469],{},"customer.addOffer(offer);\n",[53,5471,5472],{"class":55,"line":186},[53,5473,5474],{},"customerRepository.save(customer);\n",[18,5476,5477,5478,5481,5482,5485,5486,986],{},"First both, the offer and the customer (each specified by Id) are queried from the database. Then, the offer is assigned\nto the customer and finally the customer gets updated. A programmer not aware of JPA and Spring Data internals might\noverlook that ",[50,5479,5480],{},"save",", which is actually provided by Spring Data, invokes the JPA EntityManagers ",[50,5483,5484],{},"merge"," method and he\nmight not know the details of the ",[27,5487,5488],{},"JPA concepts of detachment, merging and transaction-scoped persistence contexts",[18,5490,5491,5492,5495,5496,5499],{},"Now, assume that retrieving the offer and customer and eventually updating the customer are executed not within the same\n",[573,5493,5494],{},"transaction",". This means that the offer entity is no longer in managed state when the JPA merge operation does its\nwork. JPA ",[573,5497,5498],{},"persistence contexts"," are tied to the lifecycle of a transaction which implies that the JPA provider again\nreads the offer from database, instantly fetching all of its collection-valued associations, which in our case causes\nthe application to run out of memory (again, there are lots of outer joins in the select statement sent to the\ndatabase).",[18,5501,5502,5503,5506],{},"Ensuring that reading offers and updating the customer are handled in the same transaction fixes the memory issue, but\nthe better approach would have been to avoid eager loading at all. This not only decreases the impact on memory but also\nreduces the amount of SQL and speeds up the queries and object loading. These are the reasons that JPA defaults to ",[573,5504,5505],{},"lazy\nloading"," (aka deferred loading, on-demand fetching) for collection-valued associations; but keep in mind that lazy\nloading is just a hint to the JPA provider, i.e. behaviour will depend on its implementation.",[649,5508,4101],{"id":5509},"conclusion",[18,5511,5512],{},[27,5513,5514],{},"Deepen the knowledge of the technologies you are using",[18,5516,5517],{},"Care about the internal details of the persistence technology. Generally speaking, decoupling clients from repository\nimplementation and the underlying technology is great, but this does not relieve the developers from the need to\nunderstand the consequences of using encapsulated behaviour, including its performance and resource-related\nimplications.",[18,5519,5520],{},"In other words:",[2200,5522,5523,5526],{},[18,5524,5525],{},"“Client code ignores REPOSITORY implementations; developers do not”",[18,5527,5528],{},[53,5529,5530],{},"Eric Evans, Domain-Driven Design",[18,5532,5533],{},[27,5534,5535],{},"Measure, don’t guess",[18,5537,5538],{},"Testing the application should not only be done on a regular basis and in an automated fashion, but equally important,\nperformance measuring and memory monitoring in production-like environments is essential and must not be ignored. In an\niterative development process, these tests should be done in each iteration.",[607,5540,989],{},{"title":48,"searchDepth":86,"depth":86,"links":5542},[5543,5544],{"id":5262,"depth":126,"text":5263},{"id":5509,"depth":126,"text":4101},[613],"2013-12-28T15:21:04","Our team is working on an application for one of our clients, a service provider for container logistics, shipping cargo\\nbetween seaports, terminals and other loading sites. The business domain also includes the calculation of shipping\\nprices subjected to the agreements met between the shipping company and its customers. We recently implemented the\\nconcept of so called offers into the application, whereas each offer contains multiple terminal-specific prices.\\nOne or more offers may be assigned to a customer (see diagram below, capturing these domain concepts).","https://synyx.de/blog/client-code-ignores-repository-implementations-developers-do-not/",{},"/blog/client-code-ignores-repository-implementations-developers-do-not",{"title":5113,"description":5552},"Our team is working on an application for one of our clients, a service provider for container logistics, shipping cargo\nbetween seaports, terminals and other loading sites. The business domain also includes the calculation of shipping\nprices subjected to the agreements met between the shipping company and its customers. We recently implemented the\nconcept of so called offers into the application, whereas each offer contains multiple terminal-specific prices.\nOne or more offers may be assigned to a customer (see diagram below, capturing these domain concepts).","blog/client-code-ignores-repository-implementations-developers-do-not",[5555,1011,5556],"jpa","testing","Our team is working on an application for one of our clients, a service provider for container logistics, shipping cargo between seaports, terminals and other loading sites. The business domain…","BRuzgHD8_0uoriwcr4dYqNX0E6AtuiiaImID2mFvZ_g",{"id":5560,"title":5561,"author":5562,"body":5563,"category":5687,"date":5688,"description":5689,"extension":617,"link":5690,"meta":5691,"navigation":499,"path":5692,"seo":5693,"slug":5567,"stem":5694,"tags":5695,"teaser":5698,"__hash__":5699},"blog/blog/database-migration-using-flyway-and-spring-and-existing-data.md","Database Migration using Flyway and Spring (and existing Data)",[2486],{"type":11,"value":5564,"toc":5685},[5565,5568,5571,5580,5583,5586,5594,5597,5602,5605,5608,5614,5618,5622,5626,5629,5635],[14,5566,5561],{"id":5567},"database-migration-using-flyway-and-spring-and-existing-data",[18,5569,5570],{},"My team and I are currently working on an project we first started in early 2010. The application is in production\nsince sometime late 2010 and there has been no active development except for minor enhancements and bugfixes since then.\nEven if our code, processes and tools were good in 2010 we’ve improved a lot since then. Working on my old projects is\none of the occasions, where this becomes most evident.",[18,5572,5573,5574,5579],{},"When we start a new project today we usually use the database migration tool ",[585,5575,5578],{"href":5576,"rel":5577},"http://www.liquibase.org/",[589],"Liquibase","\nright from the beginning. The tool keeps code and the database schema in sync and usually takes care of automatic\nmigration during application startup.",[18,5581,5582],{},"Back then we usually used SQL scripts, which had to be executed manually during deployment, to keep the database up to\ndate. Out of laziness or lack of time, this was also the first approach we took this week to handle database changes.\nThese scripts are checked into version control along with any code changes.",[18,5584,5585],{},"This may work pretty well in the beginning, but can also become annoying very fast: Everything you’ve to do manually is\ndestined to fail some time. ALL THE TIME! It fails on my colleagues working machines, it fails on our continuous\nintegration server (Jenkins) and it will probably fail hard on production, if you don’t pay enough attention during a\ndeployment.",[18,5587,5588,5589,986],{},"So there we were, about 60 minutes ago, standing there with a database dump from production and a bunch of SQL scripts,\nwhich accumulated during this week of development. Well, it is friday and I wanted to test something new so I remembered\na talk I attended earlier this year about an alternative to Liquibase: ",[585,5590,5593],{"href":5591,"rel":5592},"http://code.google.com/p/flyway/",[589],"flyway",[18,5595,5596],{},"What it basically does, is to execute a bunch of SQL scripts it hasn’t already executed on the given database. To get\nstarted I saved the dump of the production system into the db/migration/ package of our web application:",[18,5598,5599],{},[50,5600,5601],{},"mkdir -p src/main/respources/db/migration/ && cp prod_dump.sql src/main/resources/db/migration/V1_initial_import.sql",[18,5603,5604],{},"As many of our applications, this one too is based on Spring and Maven. So I added the flyway dependency to our pom.xml\nand also some XML to the bean configuration.",[18,5606,5607],{},"pom.xml:",[18,5609,5610,5611],{},"`",[5612,5613],"dependency",{},[5615,5616,5617],"group-id",{},"\ncom.googlecode.flyway\n",[5619,5620,5621],"artifact-id",{},"\nflyway-core\n",[5623,5624,5625],"version",{},"\n1.7\n",[18,5627,5628],{},"Even if there is a Maven plugin to execute the migration scripts, we got used to migrating the database during the\napplication boot process (because you will never have to think about it again, it simply migrates…). So we add the\nflyway bean to our bean configuration file. It is important that the flyway bean is instantiated early because it has to\nmigrate the database before anyone else uses it. In our case “anyone” is actually the EntityManager, so i configured the\npersistenceUnitManager to depend on flyway (which means flyway is running first):",[18,5630,5610,5631],{},[5632,5633],"bean",{"id":5593,"init-method":5634},"migrate",[5636,5637,5639,5642,5645],"property",{"name":5638,"ref":5638},"dataSource",[18,5640,5641],{},"\u003Cbean id=\"persistenceUnitManager\" depends-on=\"flyway\"",[18,5643,5644],{},"class=\"org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager\">",[5636,5646,5648,5649,5657,5664,5667,5670,5673,5676,5679,5682],{"name":5647,"ref":5638},"defaultDataSource","\n\n`\n",[18,5650,5651,5652,5656],{},"Of course there are several configuration options for the flyway object. You can refer to\nthe ",[585,5653,2183],{"href":5654,"rel":5655},"http://code.google.com/p/flyway/wiki/ApplicationIntegration",[589]," for further details.",[18,5658,5659,5660,5663],{},"By default, flyway will now search for SQL scripts in your classpath. It expects the scripts in the db.migration\npackage, following a particular naming scheme: Vxxx",[27,5661,5662],{},"description.sql, just like the one we already created earlier (\nV1_initial_import.sql). It will also remember at which version the database currently is and will only execute scripts\nit has not executed so far. So when we start our application flyway will find our script and will execute it. Afterwards\nit will know, that the database is at version 1 and will not execute the V1"," file again. This will only work on an\nempty database so you should drop and create your local database at this point.",[18,5665,5666],{},"`5:29:11,362 INFO .flyway.core.metadatatable.MetaDataTable: 111 - Creating Metadata table: schema_version (Schema: mydb)",[18,5668,5669],{},"15:29:11,408 INFO glecode.flyway.core.migration.DbMigrator: 120 - Current schema version: null",[18,5671,5672],{},"15:29:11,412 INFO glecode.flyway.core.migration.DbMigrator: 205 - Migrating to version 1",[18,5674,5675],{},"15:29:24,694 INFO glecode.flyway.core.migration.DbMigrator: 191 - Successfully applied 1 migrations (execution time 00:\n1.290s).`",[18,5677,5678],{},"Now if I have database changes, I simply add a new SQL file containing the change with the prefix V2__ and so on. If\nmy colleagues update their working copy they will also get my SQL changes and flyway will execute it during application\nbootup (or integration-test) and nobody has to do this manually anymore.",[18,5680,5681],{},"Ok, nice. But what about production? When we deploy the new version of the app we also want the scripts to be executed\nbut not the initial import, right? I dont want to execute “drop database dbname; create database dbname;” there. Flyway\ninitializes itself on the first start but only if the database it writes to is empty. So the migration will fail on\nproduction.",[18,5683,5684],{},"For this case flyway also comes with a goal that creates the metadata tables. It comes with the ability, to initialize\nyour metadata tables at any given version. You can accomplish this by code (call init() on the flyway object), via the\nMaven plugin (flyway:init) or on the commandline. Because I did not want to install any extra software on the production\nmachine, I simply prepared an SQL dump of the metadata table (schema_version) right after the initial import was\nexecuted. This will now be executed against the production database right before the next deployment. Yes, manually… but\nfor the last time ;).",{"title":48,"searchDepth":86,"depth":86,"links":5686},[],[613],"2012-10-05T18:37:36","My team and I are currently working on an project we first started in early 2010. The application is in production\\nsince sometime late 2010 and there has been no active development except for minor enhancements and bugfixes since then.\\nEven if our code, processes and tools were good in 2010 we’ve improved a lot since then. Working on my old projects is\\none of the occasions, where this becomes most evident.","https://synyx.de/blog/database-migration-using-flyway-and-spring-and-existing-data/",{},"/blog/database-migration-using-flyway-and-spring-and-existing-data",{"title":5561,"description":5570},"blog/database-migration-using-flyway-and-spring-and-existing-data",[5696,5593,5697,1010],"database","liquibase","My team and I are currently working on an project we first started in early 2010. The application is in production since sometime late 2010 and there has been no…","TnwY5J1dIhZAwIJnQj9D6OgKSWLYQ5REWmVXTVhYu3U",{"id":5701,"title":5702,"author":5703,"body":5705,"category":6072,"date":6074,"description":6075,"extension":617,"link":6076,"meta":6077,"navigation":499,"path":6078,"seo":6079,"slug":5709,"stem":6080,"tags":6081,"teaser":6084,"__hash__":6085},"blog/blog/studien-projektarbeit-mit-grails.md","Studien-Projektarbeit mit Grails",[5704],"matt",{"type":11,"value":5706,"toc":6070},[5707,5710,5713,5716,5719,5722,5731,5740,5745,5759,5768,5771,5780,5783,5802,5805,5808,5817,5820,5823,5832,5835,5845,5943,5946,5949,5996,5999,6005,6008,6017,6020,6030,6065,6068],[14,5708,5702],{"id":5709},"studien-projektarbeit-mit-grails",[18,5711,5712],{},"Das Informatik-Studium beinhaltet in der Hochschule Karlsruhe auch praktische Übungen. In der Regel bemühen sich Anfang\nOktober eine Vielzahl von junger (meist) hoch motivierter Informatik-Studenten um möglichst einen passendes Projekt für\ndas 5te Semester zu bekommen.",[18,5714,5715],{},"Das Gedränge kann durchaus groß sein, da hierbei die goldene Regel gilt: „wer zuerst kommt, malt zuerst“. Zudem ist der\nBegriff ‘passend’ auf den jeweiligen Studenten gemünzt. Manche Studenten suchen nach einem für Ihn möglichst\ninteressanten Thema, manche wollen von ihrem meist-geschätzten Professor betreut werden und wiederum andere wollen\nlieber eine ruhige Kugel schieben. Soweit so gut, nun gibt es allerdings noch eine Alternative zu den aufgeführten\nMöglichkeiten an eine Projektarbeit zu kommen. Der Student überlegt sich ein Thema und spricht einen Professor an, ob\ndieser das ausgewählte Thema betreuen möchte.",[18,5717,5718],{},"Da ich zu Beginn des Semesters noch nichts von der letzten Möglichkeit gewusst hatte und ich ansonsten wenig begeistert\nvon den angebotenen Themen war, musste ich mir etwas einfallen lassen. Nach einem aufschlussreichen Gespräch in der\nFachschaft, wusste ich nun auch von der Möglichkeit ein Projekt direkt mit einem Professor auszuhandeln.",[18,5720,5721],{},"Da ich die „neue“ Welt der dynamischen Sprachen im Java-Umfeld schon immer sehr reizvoll gefunden hatte legte ich mich\nauf das Schreiben einer einfachen Webapplikation mit dem Grails-Framework fest. Die Überlegung dahinter war recht\neinfach, es musste neu und spannend sein (ansonsten würde der betreuende Professor das Projekt abweisen) und ich hatte\nschon länger eine Projektidee, die durch das Studium nun vorangetrieben wurde. Nun ist es aber bekanntermaßen so, dass\nbei einer Studienarbeit der Anteil der Dokumentation und Planung recht hoch sein kann. Dementsprechend wählte ich auch\nkeine brandneue Technologie. Die Dokumentation und die erhältlichen Tutorials würden den Einsteig sicherlich stark\nvereinfachen.",[18,5723,5724,5725,5730],{},"Es stellt sich nun die Frage wie ich überhaupt zum Thema ",[585,5726,5729],{"href":5727,"rel":5728,"title":5729},"http://grails.org/",[589],"Grails"," bekommen bin. Die Antwort\nist ziemlich simpel – Mundpropaganda. Ich kann mich noch gut an einen Abend in der Kneipe erinnern. Ich saß mit\nbefreundeten ehemaligen Arbeitskollegen bei einem kühlen Bier und wir debattierten wie so des öfteren über die\nWebentwicklung mit Grails. “Wir können zu dritt in einem Monat das Projekt an dem wir nun schon jahrelang mit einem\nvollen Entwicklerteam schrauben nachbauen“. Mit diesem Satz im Hinterkopf ist mein Studienprojekt entstanden.",[18,5732,5733,5734,5739],{},"Das sollte genug zur Vorgeschichte des Studien-Projekts sein,. In einem kleinen Tutorial zum das\nGrails ",[585,5735,5738],{"href":5736,"rel":5737},"https://web.archive.org/web/20170106062111/http://www.grails.org:80/plugin/spring-security-core",[589],"Spring-Security -Plugin","\ns möchte das Plugin-System von Grails demonstrieren:",[18,5741,5742],{},[27,5743,5744],{},"Meine Umgebung:",[577,5746,5747,5750,5753,5756],{},[580,5748,5749],{},"Ubuntu 12.04",[580,5751,5752],{},"Eclipse IDE (Groovy/Grails Tool Suite von SpringSource)",[580,5754,5755],{},"Grails 2.1.0",[580,5757,5758],{},"Java 1.6.0_24",[18,5760,5761,5762,5767],{},"Um das Grails-Plugin nutzen zu können, ist es nötig das hierzu\nbenötigte ",[585,5763,5766],{"href":5736,"rel":5764,"title":5765},[589],"SpringSecurityPlugin","Plugin","\nzu installieren. Es gibt 2 Möglichkeiten dies zu bewerkstelligen:",[18,5769,5770],{},"Über die Komandozeile:",[43,5772,5774],{"className":2285,"code":5773,"language":2287,"meta":48,"style":48},"grails install-plugin spring-security-core\n",[50,5775,5776],{"__ignoreMap":48},[53,5777,5778],{"class":55,"line":56},[53,5779,5773],{},[18,5781,5782],{},"oder über einen Eintrag in der grails-app/conf/BuildConfig.groovy:",[43,5784,5786],{"className":2285,"code":5785,"language":2287,"meta":48,"style":48},"plugins {\n compile \":spring-security-core:1.2.7.1\"\n}\n",[50,5787,5788,5793,5798],{"__ignoreMap":48},[53,5789,5790],{"class":55,"line":56},[53,5791,5792],{},"plugins {\n",[53,5794,5795],{"class":55,"line":86},[53,5796,5797],{}," compile \":spring-security-core:1.2.7.1\"\n",[53,5799,5800],{"class":55,"line":126},[53,5801,282],{},[18,5803,5804],{},"Da ich die Übersicht der BuildConfig inzwischen zu schätzen weiß, wählte ich die zweite Variante.",[18,5806,5807],{},"Zur Überprüfung/Installation des Plugins muss nun der Befehl",[43,5809,5811],{"className":2285,"code":5810,"language":2287,"meta":48,"style":48},"grails compile\n",[50,5812,5813],{"__ignoreMap":48},[53,5814,5815],{"class":55,"line":56},[53,5816,5810],{},[18,5818,5819],{},"ausgeführt werden. Dieser Befehl löst die eingetragene Plugin-Dependency auf.",[18,5821,5822],{},"Der nächste Schritt nutzt nun gleich ein Skript des installierten Plugin für die Erstellung der nötigen Domain-Klassen.",[43,5824,5826],{"className":2285,"code":5825,"language":2287,"meta":48,"style":48},"grails s2-quickstart \u003CmeinePackageStruktur> User Role\n",[50,5827,5828],{"__ignoreMap":48},[53,5829,5830],{"class":55,"line":56},[53,5831,5825],{},[18,5833,5834],{},"Diese 3 kleinen Befehle reichen für die Grundfunktionalität von SpringSecurity aus.",[18,5836,5837,5838,5844],{},"Zur Überprüfung der Login-Funktionalität macht es Sinn in\nder ",[585,5839,5843],{"href":5840,"rel":5841,"title":5842},"https://web.archive.org/web/20140413052251/http://grails.org:80/Bootstrap+Classes",[589],"Bootstrap","grails-app/conf/Bootstrap.groovy","\nein paar Testdaten einzufügen:",[43,5846,5849],{"className":5847,"code":5848,"language":5106,"meta":48,"style":48},"language-groovy shiki shiki-themes github-light github-dark","class BootStrap{\n def init={servletContext->\n RoleuserRole=Role.findByAuthority('ROLE_USER')?:newRole(authority:'ROLE_USER').save(failOnError:true)\n Role adminRole=Role.findByAuthority('ROLE_ADMIN')?:newRole(authority:'ROLE_ADMIN').save(failOnError:true)\n UseradminUser=User.findByUsername('adminUser')?:newUser(username:'adminUser',enabled:true,password:'topSecret').save(failOnError:true)\n if(!adminUser .authorities.contains(userRole))\n UserRole.create(adminUser,userRole,true)\n if(!adminUser .authorities.contains(adminRole))\n UserRole.create(adminUser,adminRole,true)\n User dummyUser=User.findByUsername('dummyUser')?:newUser(username:'dummyUser',enabled:true,password:'notSoSecret').save(failOnError:true)\n if(!dummyUser.authorities.contains(userRole))\n UserRole.create(dummyUser,userRole,true)\n ...\n }\n assertUser.count()==2\n assertRole.count()==2\n assertUserRole.count()==3\n …\n}\n",[50,5850,5851,5856,5861,5866,5871,5876,5881,5886,5891,5896,5901,5906,5911,5915,5919,5924,5929,5934,5939],{"__ignoreMap":48},[53,5852,5853],{"class":55,"line":56},[53,5854,5855],{},"class BootStrap{\n",[53,5857,5858],{"class":55,"line":86},[53,5859,5860],{}," def init={servletContext->\n",[53,5862,5863],{"class":55,"line":126},[53,5864,5865],{}," RoleuserRole=Role.findByAuthority('ROLE_USER')?:newRole(authority:'ROLE_USER').save(failOnError:true)\n",[53,5867,5868],{"class":55,"line":163},[53,5869,5870],{}," Role adminRole=Role.findByAuthority('ROLE_ADMIN')?:newRole(authority:'ROLE_ADMIN').save(failOnError:true)\n",[53,5872,5873],{"class":55,"line":186},[53,5874,5875],{}," UseradminUser=User.findByUsername('adminUser')?:newUser(username:'adminUser',enabled:true,password:'topSecret').save(failOnError:true)\n",[53,5877,5878],{"class":55,"line":221},[53,5879,5880],{}," if(!adminUser .authorities.contains(userRole))\n",[53,5882,5883],{"class":55,"line":242},[53,5884,5885],{}," UserRole.create(adminUser,userRole,true)\n",[53,5887,5888],{"class":55,"line":273},[53,5889,5890],{}," if(!adminUser .authorities.contains(adminRole))\n",[53,5892,5893],{"class":55,"line":279},[53,5894,5895],{}," UserRole.create(adminUser,adminRole,true)\n",[53,5897,5898],{"class":55,"line":496},[53,5899,5900],{}," User dummyUser=User.findByUsername('dummyUser')?:newUser(username:'dummyUser',enabled:true,password:'notSoSecret').save(failOnError:true)\n",[53,5902,5903],{"class":55,"line":503},[53,5904,5905],{}," if(!dummyUser.authorities.contains(userRole))\n",[53,5907,5908],{"class":55,"line":509},[53,5909,5910],{}," UserRole.create(dummyUser,userRole,true)\n",[53,5912,5913],{"class":55,"line":515},[53,5914,322],{},[53,5916,5917],{"class":55,"line":521},[53,5918,2543],{},[53,5920,5921],{"class":55,"line":527},[53,5922,5923],{}," assertUser.count()==2\n",[53,5925,5926],{"class":55,"line":533},[53,5927,5928],{}," assertRole.count()==2\n",[53,5930,5931],{"class":55,"line":539},[53,5932,5933],{}," assertUserRole.count()==3\n",[53,5935,5936],{"class":55,"line":545},[53,5937,5938],{}," …\n",[53,5940,5941],{"class":55,"line":4314},[53,5942,282],{},[18,5944,5945],{},"Nun kann man beliebige Teile seiner Applikation von SpringSecurity überwachen lassen.",[18,5947,5948],{},"Ob auf Klassenebene/Methodenebene kann einfach per Annotation gesteuert werden.",[43,5950,5952],{"className":5847,"code":5951,"language":5106,"meta":48,"style":48},"import org.springframework.dao.DataIntegrityViolationException\nimport grails.plugins.springsecurity.Secured\n@Secured(['ROLE_USER'])\nclass MyFancyController {\n...\n@Secured(['ROLE_ADMIN'])\ndef mySuperfancyAdminOnlyMethod(){\n...\n}\n",[50,5953,5954,5959,5964,5969,5974,5978,5983,5988,5992],{"__ignoreMap":48},[53,5955,5956],{"class":55,"line":56},[53,5957,5958],{},"import org.springframework.dao.DataIntegrityViolationException\n",[53,5960,5961],{"class":55,"line":86},[53,5962,5963],{},"import grails.plugins.springsecurity.Secured\n",[53,5965,5966],{"class":55,"line":126},[53,5967,5968],{},"@Secured(['ROLE_USER'])\n",[53,5970,5971],{"class":55,"line":163},[53,5972,5973],{},"class MyFancyController {\n",[53,5975,5976],{"class":55,"line":186},[53,5977,2294],{},[53,5979,5980],{"class":55,"line":221},[53,5981,5982],{},"@Secured(['ROLE_ADMIN'])\n",[53,5984,5985],{"class":55,"line":242},[53,5986,5987],{},"def mySuperfancyAdminOnlyMethod(){\n",[53,5989,5990],{"class":55,"line":273},[53,5991,2294],{},[53,5993,5994],{"class":55,"line":279},[53,5995,282],{},[18,5997,5998],{},"Ein kurzer Funktionstest auf der Weboberfläche überzeugt von der Funktionalität.",[18,6000,6001],{},[2694,6002],{"alt":6003,"src":6004},"\"Grails SpringSecurity login screen\"","https://media.synyx.de/uploads//2012/09/Bildschirmfoto-vom-2012-09-10-1801541.png",[18,6006,6007],{},"Über den Controller:",[43,6009,6011],{"className":2285,"code":6010,"language":2287,"meta":48,"style":48}," localhost:\u003CmyPort>/\u003CmyApp>/dbconsole\n",[50,6012,6013],{"__ignoreMap":48},[53,6014,6015],{"class":55,"line":56},[53,6016,6010],{},[18,6018,6019],{},"lässt sich die Struktur von der Datenhaltung in SpringSecurity leicht nachvollziehen.",[18,6021,6022,6023,6029],{},"Um noch mehr Funktionalität und auch generierte Admin-Oberflächen zu bekommen, ist es möglich\ndas ",[585,6024,6028],{"href":6025,"rel":6026,"title":6027},"https://web.archive.org/web/20170104185319/http://grails.org/plugin/spring-security-ui",[589],"Spring Security UI","spring-security-ui","\nplugin zu installieren. Dies stellt eine AJAX fähige Oberfläche zum Login bereit, sowie eine ganzen Haufen von\nAdmin-Funktionalität. Weiterhin gibt es zusätzlich noch einige weitere Plugins rund um das Thema Spring-Security:",[577,6031,6032,6035,6038,6041,6044,6047,6050,6053,6056,6059,6062],{},[580,6033,6034],{},"Spring Security ACL which adds support for object-level and method-level authorization using ACLs (access control\nlist)",[580,6036,6037],{},"Spring Security AppInfo which provides a basic UI to view the security configuration",[580,6039,6040],{},"Spring Security CAS which adds support for single sign-on using Jasig CAS",[580,6042,6043],{},"Spring Security OpenID which adds support for OpenID authentication",[580,6045,6046],{},"Spring Security Facebook which adds support for Facebook authentication",[580,6048,6049],{},"Spring Security Kerberos which adds support for single sign-on using Kerberos",[580,6051,6052],{},"Spring Security LDAP which adds support for LDAP and ActiveDirectory authentication",[580,6054,6055],{},"Spring Security Mock which adds support for fake/mock authentication during developement",[580,6057,6058],{},"Spring Security RADIUS which adds support for RADIUS authentication",[580,6060,6061],{},"Spring Security Shibboleth Native SP which adds support for container provided Shibboleth authentication.",[580,6063,6064],{},"Spring Security Twitter which adds support for Twitter authentication",[18,6066,6067],{},"Dies sollte einen kleine Überblick über den Aufbau und die Installation eines Grails-Plugins im Allgemeinen und von\nspringSecurity im speziellen geben können.",[607,6069,989],{},{"title":48,"searchDepth":86,"depth":86,"links":6071},[],[6073],"azubi-blog","2012-09-10T18:35:43","Das Informatik-Studium beinhaltet in der Hochschule Karlsruhe auch praktische Übungen. In der Regel bemühen sich Anfang\\nOktober eine Vielzahl von junger (meist) hoch motivierter Informatik-Studenten um möglichst einen passendes Projekt für\\ndas 5te Semester zu bekommen.","https://synyx.de/blog/studien-projektarbeit-mit-grails/",{},"/blog/studien-projektarbeit-mit-grails",{"title":5702,"description":5712},"blog/studien-projektarbeit-mit-grails",[6082,6083,625],"grails","grailsplugin","Das Informatik-Studium beinhaltet in der Hochschule Karlsruhe auch praktische Übungen. In der Regel bemühen sich Anfang Oktober eine Vielzahl von junger (meist) hoch motivierter Informatik-Studenten um möglichst einen passendes Projekt…","tdjMkPlIVQL35Q9ecSB0rUESlMxdBITjcE_BHdDrQbI",{"id":6087,"title":6088,"author":6089,"body":6091,"category":6345,"date":6346,"description":6347,"extension":617,"link":6348,"meta":6349,"navigation":499,"path":6350,"seo":6351,"slug":6095,"stem":6352,"tags":6353,"teaser":6359,"__hash__":6360},"blog/blog/scheduling-and-asynchronous-execution-with-spring.md","Scheduling and asynchronous execution with Spring",[6090],"buchloh",{"type":11,"value":6092,"toc":6339},[6093,6096,6099,6103,6106,6133,6137,6140,6143,6167,6170,6192,6195,6217,6220,6242,6245,6254,6257,6266,6270,6273,6276,6300,6303,6325,6329,6337],[14,6094,6088],{"id":6095},"scheduling-and-asynchronous-execution-with-spring",[18,6097,6098],{},"You want to execute cron jobs or call your methods asynchronously? Thanks to Spring’s annotation support for scheduling\nand asynchronous execution you can achieve this in a few minutes.",[649,6100,6102],{"id":6101},"some-xml-magic","Some xml magic",[18,6104,6105],{},"At first define your task executor and scheduler. The following lines will create an instance of ThreadPoolTaskExecutor\nand an instance of ThreadPoolTaskScheduler with the given pool sizes. The task element annotation-driven allows you to\nuse Spring’s annotations for scheduling and asynchronous execution within the beans defined in your application context.",[43,6107,6111],{"className":6108,"code":6109,"language":6110,"meta":48,"style":48},"language-xml shiki shiki-themes github-light github-dark","\u003Cbean id=\"myClass\" class=\"my.project.path.myClass\" />\n\u003Ctask:annotation-driven executor=\"myExecutor\" scheduler=\"myScheduler\" />\n\u003Ctask:executor id=\"myExecutor\" pool-size=\"5\" />\n\u003Ctask:scheduler id=\"myScheduler\" pool-size=\"10\" />\n","xml",[50,6112,6113,6118,6123,6128],{"__ignoreMap":48},[53,6114,6115],{"class":55,"line":56},[53,6116,6117],{},"\u003Cbean id=\"myClass\" class=\"my.project.path.myClass\" />\n",[53,6119,6120],{"class":55,"line":86},[53,6121,6122],{},"\u003Ctask:annotation-driven executor=\"myExecutor\" scheduler=\"myScheduler\" />\n",[53,6124,6125],{"class":55,"line":126},[53,6126,6127],{},"\u003Ctask:executor id=\"myExecutor\" pool-size=\"5\" />\n",[53,6129,6130],{"class":55,"line":163},[53,6131,6132],{},"\u003Ctask:scheduler id=\"myScheduler\" pool-size=\"10\" />\n",[649,6134,6136],{"id":6135},"the-scheduled-annotation","The @Scheduled annotation",[18,6138,6139],{},"With the @Scheduled annotation you can execute your method as a cron job. Using this annotation requires that the method\nto be scheduled must be of type void and must not expect any arguments. The following examples show you how to use the\n@Scheduled annotation.",[18,6141,6142],{},"If you want periodic scheduling you can use the property fixedRate. In this example the method would be executed every\n42 seconds.",[43,6144,6146],{"className":288,"code":6145,"language":290,"meta":48,"style":48},"@Scheduled(fixedRate = 42000)\npublic void execute() {\n // do something\n}\n",[50,6147,6148,6153,6158,6163],{"__ignoreMap":48},[53,6149,6150],{"class":55,"line":56},[53,6151,6152],{},"@Scheduled(fixedRate = 42000)\n",[53,6154,6155],{"class":55,"line":86},[53,6156,6157],{},"public void execute() {\n",[53,6159,6160],{"class":55,"line":126},[53,6161,6162],{}," // do something\n",[53,6164,6165],{"class":55,"line":163},[53,6166,282],{},[18,6168,6169],{},"If you prefer cron expressions you can use them either. The following example is analogue to the example above only\nusing cron expressions. The annotated method would be executed each full minute and every 7 seconds.",[43,6171,6173],{"className":288,"code":6172,"language":290,"meta":48,"style":48},"@Scheduled(cron = \"*/7 * * * * *\")\npublic void execute() {\n // do something\n}\n",[50,6174,6175,6180,6184,6188],{"__ignoreMap":48},[53,6176,6177],{"class":55,"line":56},[53,6178,6179],{},"@Scheduled(cron = \"*/7 * * * * *\")\n",[53,6181,6182],{"class":55,"line":86},[53,6183,6157],{},[53,6185,6186],{"class":55,"line":126},[53,6187,6162],{},[53,6189,6190],{"class":55,"line":163},[53,6191,282],{},[18,6193,6194],{},"Without question you have much more possibilities with cron expressions than with periodic scheduling. In this example\nyour method would be executed every weekday (Monday to Friday) on 9.45 am.",[43,6196,6198],{"className":288,"code":6197,"language":290,"meta":48,"style":48},"@Scheduled(cron = \"0 45 9 * * MON-FRI\")\npublic void execute() {\n // do something\n}\n",[50,6199,6200,6205,6209,6213],{"__ignoreMap":48},[53,6201,6202],{"class":55,"line":56},[53,6203,6204],{},"@Scheduled(cron = \"0 45 9 * * MON-FRI\")\n",[53,6206,6207],{"class":55,"line":86},[53,6208,6157],{},[53,6210,6211],{"class":55,"line":126},[53,6212,6162],{},[53,6214,6215],{"class":55,"line":163},[53,6216,282],{},[18,6218,6219],{},"A pretty cool feature is that you even can use placeholders for your cron expression which are resolved against the\nconfigured property-placeholder.",[43,6221,6223],{"className":288,"code":6222,"language":290,"meta":48,"style":48},"@Scheduled(cron = \"${myclass.cron.execute.sth}\")\npublic void execute() {\n // do something\n}\n",[50,6224,6225,6230,6234,6238],{"__ignoreMap":48},[53,6226,6227],{"class":55,"line":56},[53,6228,6229],{},"@Scheduled(cron = \"${myclass.cron.execute.sth}\")\n",[53,6231,6232],{"class":55,"line":86},[53,6233,6157],{},[53,6235,6236],{"class":55,"line":126},[53,6237,6162],{},[53,6239,6240],{"class":55,"line":163},[53,6241,282],{},[18,6243,6244],{},"Define where the properties are loaded from within your application context:",[43,6246,6248],{"className":6108,"code":6247,"language":6110,"meta":48,"style":48},"\u003Ccontext:property-placeholder location=\"classpath:application.properties\"/>\n",[50,6249,6250],{"__ignoreMap":48},[53,6251,6252],{"class":55,"line":56},[53,6253,6247],{},[18,6255,6256],{},"Then the properties are loaded from the file which contains your cron-expressions like this:",[43,6258,6260],{"className":2285,"code":6259,"language":2287,"meta":48,"style":48},"myclass.cron.execute.sth=0 45 9 * * MON-FRI\n",[50,6261,6262],{"__ignoreMap":48},[53,6263,6264],{"class":55,"line":56},[53,6265,6259],{},[649,6267,6269],{"id":6268},"the-async-annotation","The @Async annotation",[18,6271,6272],{},"The @Async annotation allows you to invoke your method asynchronously. The execution of the method will occur in a task\nthat has been submitted to the TaskExecutor defined in your application context. Contrary to the methods annotated with\nthe @Scheduled annotations the methods you annotate with @Async may be of other type than void and can expect arguments.",[18,6274,6275],{},"This is a simple example of a @Async annotated method without a return value.",[43,6277,6279],{"className":288,"code":6278,"language":290,"meta":48,"style":48},"@Async\nvoid execute(String string) {\n // do something asynchronously\n}\n",[50,6280,6281,6286,6291,6296],{"__ignoreMap":48},[53,6282,6283],{"class":55,"line":56},[53,6284,6285],{},"@Async\n",[53,6287,6288],{"class":55,"line":86},[53,6289,6290],{},"void execute(String string) {\n",[53,6292,6293],{"class":55,"line":126},[53,6294,6295],{}," // do something asynchronously\n",[53,6297,6298],{"class":55,"line":163},[53,6299,282],{},[18,6301,6302],{},"Like mentioned above your @Async annotated method may have a return value. However this return value must be of type\nFuture. This means that first the other tasks are performed and then is called get() on that Future.",[43,6304,6306],{"className":288,"code":6305,"language":290,"meta":48,"style":48},"@Async\nFuture\u003CString> execute(String string) {\n // do something asynchronously\n}\n",[50,6307,6308,6312,6317,6321],{"__ignoreMap":48},[53,6309,6310],{"class":55,"line":56},[53,6311,6285],{},[53,6313,6314],{"class":55,"line":86},[53,6315,6316],{},"Future\u003CString> execute(String string) {\n",[53,6318,6319],{"class":55,"line":126},[53,6320,6295],{},[53,6322,6323],{"class":55,"line":163},[53,6324,282],{},[649,6326,6328],{"id":6327},"further-information","Further information",[18,6330,6331,6332],{},"Have a look at\nthe ",[585,6333,6336],{"href":6334,"rel":6335},"http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#scheduling",[589],"Spring Framework Reference Documentation",[607,6338,989],{},{"title":48,"searchDepth":86,"depth":86,"links":6340},[6341,6342,6343,6344],{"id":6101,"depth":126,"text":6102},{"id":6135,"depth":126,"text":6136},{"id":6268,"depth":126,"text":6269},{"id":6327,"depth":126,"text":6328},[6073,613],"2012-06-13T15:49:15","You want to execute cron jobs or call your methods asynchronously? Thanks to Spring’s annotation support for scheduling\\nand asynchronous execution you can achieve this in a few minutes.","https://synyx.de/blog/scheduling-and-asynchronous-execution-with-spring/",{},"/blog/scheduling-and-asynchronous-execution-with-spring",{"title":6088,"description":6098},"blog/scheduling-and-asynchronous-execution-with-spring",[6354,6355,6356,6357,1010,6358],"async","scheduled","asynchronous","scheduling","spring-annotations","You want to execute cron jobs or call your methods asynchronously? Thanks to Spring’s annotation support for scheduling and asynchronous execution you can achieve this in a few minutes. Some…","fzW4lb1yw0kNbXB83aAmQzrzjrslpT-gtiBvUzbdR7Q",{"id":6362,"title":6363,"author":6364,"body":6365,"category":7016,"date":7017,"description":7018,"extension":617,"link":7019,"meta":7020,"navigation":499,"path":7021,"seo":7022,"slug":6369,"stem":7023,"tags":7024,"teaser":7031,"__hash__":7032},"blog/blog/how-to-monitor-and-manage-your-java-application-with-jmx.md","How to monitor and manage your Java application with JMX",[6090],{"type":11,"value":6366,"toc":7009},[6367,6370,6373,6388,6463,6466,6470,6473,6563,6566,6573,6577,6591,6596,6601,6624,6627,6632,6637,6661,6664,6687,6692,6697,6721,6727,6766,6775,6779,6782,6787,6790,6796,6801,6804,6810,6813,6816,6850,6853,6903,6907,6910,6913,6973,6976,6980,6986,6993,7000,7007],[14,6368,6363],{"id":6369},"how-to-monitor-and-manage-your-java-application-with-jmx",[18,6371,6372],{},"JMX (Java Management Extensions) provides the infrastructure to support monitoring and management of your Java\napplications. Resources you manage with JMX are called Managed Beans (MBeans). I want to show you how to quickly\nregister your own Service as MBean using Spring and Source-Level Metadata (JDK 5.0+ annotations).",[18,6374,6375,6376,6379,6380,6383,6384,6387],{},"The following sample is built on a tool that allows to manage the staffs’ applications for vacation digitally instead of\nusing paper. If a staff member applies for leave, the application gets the status ",[573,6377,6378],{},"waiting",". Then an authorized person (\nthe boss) has to decide about this application. It may be set to ",[573,6381,6382],{},"allowed"," or to ",[573,6385,6386],{},"rejected",". It might be that you want\nto have an overview of the applications and their status and you may even want to remind the authorized persons via\nemail to review the pending applications. Even if the vacation management tool has a web-based frontend for doing the\nmost of the actions, I think it still makes a good example for describing how to use JMX in your Java application. The\nfollowing class is a skeleton of the class which shall be exposed to JMX as MBean.",[43,6389,6391],{"className":288,"code":6390,"language":290,"meta":48,"style":48},"public class JmxDemo {\n private long numberOfWaitingApplications;\n public long getNumberOfWaitingApplications() {\n return numberOfWaitingApplications;\n }\n public long countApplicationsInStatus(String status) {\n // do something and return number of applications with the given status\n }\n public List\u003CString> showWaitingApplications() {\n // do something and return a list of all waiting applications\n }\n public String remindBossAboutWaitingApplications() {\n // remind the boss via email to decide about the waiting applications\n }\n}\n",[50,6392,6393,6398,6403,6408,6413,6417,6422,6427,6431,6436,6441,6445,6450,6455,6459],{"__ignoreMap":48},[53,6394,6395],{"class":55,"line":56},[53,6396,6397],{},"public class JmxDemo {\n",[53,6399,6400],{"class":55,"line":86},[53,6401,6402],{}," private long numberOfWaitingApplications;\n",[53,6404,6405],{"class":55,"line":126},[53,6406,6407],{}," public long getNumberOfWaitingApplications() {\n",[53,6409,6410],{"class":55,"line":163},[53,6411,6412],{}," return numberOfWaitingApplications;\n",[53,6414,6415],{"class":55,"line":186},[53,6416,860],{},[53,6418,6419],{"class":55,"line":221},[53,6420,6421],{}," public long countApplicationsInStatus(String status) {\n",[53,6423,6424],{"class":55,"line":242},[53,6425,6426],{}," // do something and return number of applications with the given status\n",[53,6428,6429],{"class":55,"line":273},[53,6430,860],{},[53,6432,6433],{"class":55,"line":279},[53,6434,6435],{}," public List\u003CString> showWaitingApplications() {\n",[53,6437,6438],{"class":55,"line":496},[53,6439,6440],{}," // do something and return a list of all waiting applications\n",[53,6442,6443],{"class":55,"line":503},[53,6444,860],{},[53,6446,6447],{"class":55,"line":509},[53,6448,6449],{}," public String remindBossAboutWaitingApplications() {\n",[53,6451,6452],{"class":55,"line":515},[53,6453,6454],{}," // remind the boss via email to decide about the waiting applications\n",[53,6456,6457],{"class":55,"line":521},[53,6458,860],{},[53,6460,6461],{"class":55,"line":527},[53,6462,282],{},[18,6464,6465],{},"If you want to use this class as a MBean, a few steps are necessary.",[649,6467,6469],{"id":6468},"_1-not-yet-another-xml-file","1. Not yet another xml file…",[18,6471,6472],{},"It’s best you create an extra xml file (let’s call it jmxContext.xml) for JMX configuration and import it in your\napplicationContext.xml. In your jmxContext.xml you define your MBean and the MBeanExporter.",[43,6474,6476],{"className":6108,"code":6475,"language":6110,"meta":48,"style":48},"\n\u003Cbean id=\"jmxDemo\" class=\"org.synyx.urlaubsverwaltung.jmx.JmxDemo\">\n \u003C!-- maybe you need contructor-injection -->\n \u003C!-- \u003Cconstructor-arg ref=\"myService\" /> -->\n\u003C/bean>\n \u003C!-- you may just copy the following lines -->\n\u003Cbean id=\"exporter\" class=\"org.springframework.jmx.export.MBeanExporter\" lazy-init=\"false\">\n\u003Cproperty name=\"autodetect\" value=\"true\"/>\n\u003Cproperty name=\"namingStrategy\" ref=\"namingStrategy\"/>\n\u003Cproperty name=\"assembler\" ref=\"assembler\"/>\n\u003C/bean>\n\u003Cbean id=\"jmxAttributeSource\" class=\"org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource\"/>\n\u003Cbean id=\"assembler\" class=\"org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler\">\n\u003Cproperty name=\"attributeSource\" ref=\"jmxAttributeSource\"/>\n\u003C/bean>\n\u003Cbean id=\"namingStrategy\" class=\"org.springframework.jmx.export.naming.MetadataNamingStrategy\">\n\u003Cproperty name=\"attributeSource\" ref=\"jmxAttributeSource\"/>\n\u003C/bean>\n",[50,6477,6478,6482,6487,6492,6497,6502,6507,6512,6517,6522,6527,6531,6536,6541,6546,6550,6555,6559],{"__ignoreMap":48},[53,6479,6480],{"class":55,"line":56},[53,6481,500],{"emptyLinePlaceholder":499},[53,6483,6484],{"class":55,"line":86},[53,6485,6486],{},"\u003Cbean id=\"jmxDemo\" class=\"org.synyx.urlaubsverwaltung.jmx.JmxDemo\">\n",[53,6488,6489],{"class":55,"line":126},[53,6490,6491],{}," \u003C!-- maybe you need contructor-injection -->\n",[53,6493,6494],{"class":55,"line":163},[53,6495,6496],{}," \u003C!-- \u003Cconstructor-arg ref=\"myService\" /> -->\n",[53,6498,6499],{"class":55,"line":186},[53,6500,6501],{},"\u003C/bean>\n",[53,6503,6504],{"class":55,"line":221},[53,6505,6506],{}," \u003C!-- you may just copy the following lines -->\n",[53,6508,6509],{"class":55,"line":242},[53,6510,6511],{},"\u003Cbean id=\"exporter\" class=\"org.springframework.jmx.export.MBeanExporter\" lazy-init=\"false\">\n",[53,6513,6514],{"class":55,"line":273},[53,6515,6516],{},"\u003Cproperty name=\"autodetect\" value=\"true\"/>\n",[53,6518,6519],{"class":55,"line":279},[53,6520,6521],{},"\u003Cproperty name=\"namingStrategy\" ref=\"namingStrategy\"/>\n",[53,6523,6524],{"class":55,"line":496},[53,6525,6526],{},"\u003Cproperty name=\"assembler\" ref=\"assembler\"/>\n",[53,6528,6529],{"class":55,"line":503},[53,6530,6501],{},[53,6532,6533],{"class":55,"line":509},[53,6534,6535],{},"\u003Cbean id=\"jmxAttributeSource\" class=\"org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource\"/>\n",[53,6537,6538],{"class":55,"line":515},[53,6539,6540],{},"\u003Cbean id=\"assembler\" class=\"org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler\">\n",[53,6542,6543],{"class":55,"line":521},[53,6544,6545],{},"\u003Cproperty name=\"attributeSource\" ref=\"jmxAttributeSource\"/>\n",[53,6547,6548],{"class":55,"line":527},[53,6549,6501],{},[53,6551,6552],{"class":55,"line":533},[53,6553,6554],{},"\u003Cbean id=\"namingStrategy\" class=\"org.springframework.jmx.export.naming.MetadataNamingStrategy\">\n",[53,6556,6557],{"class":55,"line":539},[53,6558,6545],{},[53,6560,6561],{"class":55,"line":545},[53,6562,6501],{},[18,6564,6565],{},"If your application is running inside a container such as Tomcat, you even don’t have to configure the MBeanServer\nbecause the container has its own one.",[18,6567,6568,6569,6572],{},"Setting MBeanExporter’s property ",[573,6570,6571],{},"autodetect"," to true, means that the MBeanExporter will register all the Beans within\nyour application’s context that are annotated in the way described in the next section as MBeans.",[649,6574,6576],{"id":6575},"_2-lets-transform-your-spring-bean-to-a-managed-bean","2. Let’s transform your Spring Bean to a Managed Bean!",[18,6578,6579,6580,6583,6584,6587,6588,986],{},"Spring uses information provided by annotations to generate MBeans. The attributes of the annotations are speaking for\nthemselves so further description isn’t necessary. To mark a Bean for export, it has to be annotated with\n",[573,6581,6582],{},"@ManagedResource",", Attributes are annotated with ",[573,6585,6586],{},"@ManagedAttribute"," and Methods with ",[573,6589,6590],{},"@ManagedOperation",[18,6592,6593],{},[27,6594,6595],{},"2.1 Bean",[18,6597,6598,6599,986],{},"Mark your Bean with ",[573,6600,6582],{},[43,6602,6604],{"className":288,"code":6603,"language":290,"meta":48,"style":48},"@ManagedResource(objectName = \"mbeans:name=myJmxDemoBean\", description = \"My managed Bean.\")\npublic class JmxDemo {\n // lot of stuff\n}\n",[50,6605,6606,6611,6615,6620],{"__ignoreMap":48},[53,6607,6608],{"class":55,"line":56},[53,6609,6610],{},"@ManagedResource(objectName = \"mbeans:name=myJmxDemoBean\", description = \"My managed Bean.\")\n",[53,6612,6613],{"class":55,"line":86},[53,6614,6397],{},[53,6616,6617],{"class":55,"line":126},[53,6618,6619],{}," // lot of stuff\n",[53,6621,6622],{"class":55,"line":163},[53,6623,282],{},[18,6625,6626],{},"Make sure that your MBean doesn’t contain ‘MBean’ in its name since it would be treated as a StandardMBean causing your\nannotations not to work.",[18,6628,6629],{},[27,6630,6631],{},"2.2 Attributes",[18,6633,6634,6635,986],{},"Annotate the Getter and Setter with ",[573,6636,6586],{},[43,6638,6640],{"className":288,"code":6639,"language":290,"meta":48,"style":48},"@ManagedAttribute(description = \"Get the number of all waiting applications\" )\npublic long getNumberOfWaitingApplications() {\n return numberOfWaitingApplications;\n}\n",[50,6641,6642,6647,6652,6657],{"__ignoreMap":48},[53,6643,6644],{"class":55,"line":56},[53,6645,6646],{},"@ManagedAttribute(description = \"Get the number of all waiting applications\" )\n",[53,6648,6649],{"class":55,"line":86},[53,6650,6651],{},"public long getNumberOfWaitingApplications() {\n",[53,6653,6654],{"class":55,"line":126},[53,6655,6656],{}," return numberOfWaitingApplications;\n",[53,6658,6659],{"class":55,"line":163},[53,6660,282],{},[18,6662,6663],{},"Exposing attributes may be:",[577,6665,6666,6669,6672,6675,6678,6681,6684],{},[580,6667,6668],{},"Basic types",[580,6670,6671],{},"Primitives and their wrappers",[580,6673,6674],{},"String",[580,6676,6677],{},"BigDecimal",[580,6679,6680],{},"BigInteger",[580,6682,6683],{},"Date",[580,6685,6686],{},"Arrays and collections of basic types",[18,6688,6689],{},[27,6690,6691],{},"2.2 Methods",[18,6693,6694,6695,986],{},"Annotate each method you wish to expose with ",[573,6696,6590],{},[43,6698,6700],{"className":288,"code":6699,"language":290,"meta":48,"style":48},"@ManagedOperation(description = \"Shows a list of all waiting applications with some information.\")\npublic List\u003CString> showWaitingApplications() {\n // do something and return a list of all waiting applications\n}\n",[50,6701,6702,6707,6712,6717],{"__ignoreMap":48},[53,6703,6704],{"class":55,"line":56},[53,6705,6706],{},"@ManagedOperation(description = \"Shows a list of all waiting applications with some information.\")\n",[53,6708,6709],{"class":55,"line":86},[53,6710,6711],{},"public List\u003CString> showWaitingApplications() {\n",[53,6713,6714],{"class":55,"line":126},[53,6715,6716],{}," // do something and return a list of all waiting applications\n",[53,6718,6719],{"class":55,"line":163},[53,6720,282],{},[18,6722,6723,6724,986],{},"If your methods have parameters you can describe them further with ",[573,6725,6726],{},"@ManagedOperationParameters",[43,6728,6730],{"className":288,"code":6729,"language":290,"meta":48,"style":48},"@ManagedOperation(description = \"Get the number of all applications that have the given status.\")\n@ManagedOperationParameters({\n @ManagedOperationParameter(name = \"status\", description = \"The status may be waiting, allowed, rejected or cancelled.\")\n})\npublic long countApplicationsInStatus(String state) {\n // do something and return number of applications with the given status\n}\n",[50,6731,6732,6737,6742,6747,6752,6757,6762],{"__ignoreMap":48},[53,6733,6734],{"class":55,"line":56},[53,6735,6736],{},"@ManagedOperation(description = \"Get the number of all applications that have the given status.\")\n",[53,6738,6739],{"class":55,"line":86},[53,6740,6741],{},"@ManagedOperationParameters({\n",[53,6743,6744],{"class":55,"line":126},[53,6745,6746],{}," @ManagedOperationParameter(name = \"status\", description = \"The status may be waiting, allowed, rejected or cancelled.\")\n",[53,6748,6749],{"class":55,"line":163},[53,6750,6751],{},"})\n",[53,6753,6754],{"class":55,"line":186},[53,6755,6756],{},"public long countApplicationsInStatus(String state) {\n",[53,6758,6759],{"class":55,"line":221},[53,6760,6761],{}," // do something and return number of applications with the given status\n",[53,6763,6764],{"class":55,"line":242},[53,6765,282],{},[18,6767,6768,6769,6771,6772,6774],{},"Make sure to annotate your Getter/Setter with ",[573,6770,6586],{}," and not with ",[573,6773,6590],{},". Otherwise your\nmethods won’t work.",[649,6776,6778],{"id":6777},"_3-try-it","3. Try it!",[18,6780,6781],{},"You can now use the functions of your MBean either with JConsole or with other tools. (e.g. JMinix)",[18,6783,6784],{},[27,6785,6786],{},"3.1 JConsole",[18,6788,6789],{},"JConsole is part of Oracle’s JDK, so you can just start it by executing the JConsole command in your JDK’s\nbinary-folder. You can connect to local or to remote Java Virtual Machines. If you are running your application on the\nsame host as JConsole it should show up at the ‘Local Process’ section.",[18,6791,6792],{},[2694,6793],{"alt":6794,"src":6795},"\"jconsole\"","https://media.synyx.de/uploads//2012/04/jconsole.png",[18,6797,6798],{},[27,6799,6800],{},"3.2 JMinix",[18,6802,6803],{},"If you want to have a JMX entry point in your web application instead of using JConsole, JMinix might be the right\nchoice for you.",[18,6805,6806],{},[2694,6807],{"alt":6808,"src":6809},"\"JMinix\"","https://media.synyx.de/uploads//2012/04/jminix.png",[18,6811,6812],{},"You can include it easily in your Maven based web application:",[18,6814,6815],{},"Add JMinix as dependency in your pom.xml",[43,6817,6819],{"className":6108,"code":6818,"language":6110,"meta":48,"style":48},"\n\u003Cdependency>\n \u003CgroupId>org.jminix\u003C/groupId>\n \u003CartifactId>jminix\u003C/artifactId>\n \u003Cversion>1.0.0\u003C/version>\n\u003C/dependency>\n",[50,6820,6821,6825,6830,6835,6840,6845],{"__ignoreMap":48},[53,6822,6823],{"class":55,"line":56},[53,6824,500],{"emptyLinePlaceholder":499},[53,6826,6827],{"class":55,"line":86},[53,6828,6829],{},"\u003Cdependency>\n",[53,6831,6832],{"class":55,"line":126},[53,6833,6834],{}," \u003CgroupId>org.jminix\u003C/groupId>\n",[53,6836,6837],{"class":55,"line":163},[53,6838,6839],{}," \u003CartifactId>jminix\u003C/artifactId>\n",[53,6841,6842],{"class":55,"line":186},[53,6843,6844],{}," \u003Cversion>1.0.0\u003C/version>\n",[53,6846,6847],{"class":55,"line":221},[53,6848,6849],{},"\u003C/dependency>\n",[18,6851,6852],{},"JMinix uses a simple HttpServlet that you have to register and map to an url-pattern in your web.xml",[43,6854,6856],{"className":6108,"code":6855,"language":6110,"meta":48,"style":48},"\u003C!-- JMX -->\n\u003Cservlet>\n \u003Cservlet-name>JmxMiniConsoleServlet\u003C/servlet-name>\n \u003Cservlet-class>org.jminix.console.servlet.MiniConsoleServlet\u003C/servlet-class>\n\u003C/servlet>\n\u003Cservlet-mapping>\n\u003Cservlet-name>JmxMiniConsoleServlet\u003C/servlet-name>\n\u003Curl-pattern>/jmx/*\u003C/url-pattern>\n\u003C/servlet-mapping>\n",[50,6857,6858,6863,6868,6873,6878,6883,6888,6893,6898],{"__ignoreMap":48},[53,6859,6860],{"class":55,"line":56},[53,6861,6862],{},"\u003C!-- JMX -->\n",[53,6864,6865],{"class":55,"line":86},[53,6866,6867],{},"\u003Cservlet>\n",[53,6869,6870],{"class":55,"line":126},[53,6871,6872],{}," \u003Cservlet-name>JmxMiniConsoleServlet\u003C/servlet-name>\n",[53,6874,6875],{"class":55,"line":163},[53,6876,6877],{}," \u003Cservlet-class>org.jminix.console.servlet.MiniConsoleServlet\u003C/servlet-class>\n",[53,6879,6880],{"class":55,"line":186},[53,6881,6882],{},"\u003C/servlet>\n",[53,6884,6885],{"class":55,"line":221},[53,6886,6887],{},"\u003Cservlet-mapping>\n",[53,6889,6890],{"class":55,"line":242},[53,6891,6892],{},"\u003Cservlet-name>JmxMiniConsoleServlet\u003C/servlet-name>\n",[53,6894,6895],{"class":55,"line":273},[53,6896,6897],{},"\u003Curl-pattern>/jmx/*\u003C/url-pattern>\n",[53,6899,6900],{"class":55,"line":279},[53,6901,6902],{},"\u003C/servlet-mapping>\n",[649,6904,6906],{"id":6905},"_4-notifications","4. Notifications",[18,6908,6909],{},"Notifications (javax.management.Notification) can be broadcast from your component to notify about something interesting\nhappening. This is only a simple example of using Notifications.",[18,6911,6912],{},"Example: You want to be notified if a user logs in.",[43,6914,6916],{"className":288,"code":6915,"language":290,"meta":48,"style":48},"@ManagedResource(objectName = \"mbeans:name=myJmxDemoBean\", description = \"Manage some 'Urlaubsverwaltung' problems.\")\npublic class JmxDemoReady implements NotificationPublisherAware {\n // lot of stuff\n private NotificationPublisher notificationPublisher;\n public void notifyAboutLogin(String msg) {\n notificationPublisher.sendNotification(new Notification(\"Login Action\", this, 0, msg));\n }\n @Override\n public void setNotificationPublisher(NotificationPublisher notificationPublisher) {\n this.notificationPublisher = notificationPublisher;\n }\n}\n",[50,6917,6918,6923,6928,6932,6937,6942,6947,6951,6955,6960,6965,6969],{"__ignoreMap":48},[53,6919,6920],{"class":55,"line":56},[53,6921,6922],{},"@ManagedResource(objectName = \"mbeans:name=myJmxDemoBean\", description = \"Manage some 'Urlaubsverwaltung' problems.\")\n",[53,6924,6925],{"class":55,"line":86},[53,6926,6927],{},"public class JmxDemoReady implements NotificationPublisherAware {\n",[53,6929,6930],{"class":55,"line":126},[53,6931,6619],{},[53,6933,6934],{"class":55,"line":163},[53,6935,6936],{}," private NotificationPublisher notificationPublisher;\n",[53,6938,6939],{"class":55,"line":186},[53,6940,6941],{}," public void notifyAboutLogin(String msg) {\n",[53,6943,6944],{"class":55,"line":221},[53,6945,6946],{}," notificationPublisher.sendNotification(new Notification(\"Login Action\", this, 0, msg));\n",[53,6948,6949],{"class":55,"line":242},[53,6950,860],{},[53,6952,6953],{"class":55,"line":273},[53,6954,1936],{},[53,6956,6957],{"class":55,"line":279},[53,6958,6959],{}," public void setNotificationPublisher(NotificationPublisher notificationPublisher) {\n",[53,6961,6962],{"class":55,"line":496},[53,6963,6964],{}," this.notificationPublisher = notificationPublisher;\n",[53,6966,6967],{"class":55,"line":503},[53,6968,860],{},[53,6970,6971],{"class":55,"line":509},[53,6972,282],{},[18,6974,6975],{},"With the NotificationPublisher you are able to create Notifications in a very simple way. At the right place in your\ncode, you inject your JmxDemo Bean and call the method notifyAboutLogin() when a user logs in. JConsole now displays a\nthird menu item called ‘Notifications’, besides ‘Attributes’ and ‘Operations’. If you click on ‘Subscribe’, you get a\nNotification every time a user logs in your web application.",[649,6977,6979],{"id":6978},"_5-further-information","5. Further information:",[18,6981,6982],{},[585,6983,6336],{"href":6984,"rel":6985},"http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/jmx.html",[589],[18,6987,6988],{},[585,6989,6992],{"href":6990,"rel":6991},"http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html",[589],"About JConsole",[18,6994,6995],{},[585,6996,6999],{"href":6997,"rel":6998},"http://code.google.com/p/jminix/",[589],"About JMinix",[18,7001,7002],{},[585,7003,7006],{"href":7004,"rel":7005},"http://blog.synyx.de/2011/11/elektronische-urlaubsverwaltung-made-by-youngsters",[589],"About the vacation management web application",[607,7008,989],{},{"title":48,"searchDepth":86,"depth":86,"links":7010},[7011,7012,7013,7014,7015],{"id":6468,"depth":126,"text":6469},{"id":6575,"depth":126,"text":6576},{"id":6777,"depth":126,"text":6778},{"id":6905,"depth":126,"text":6906},{"id":6978,"depth":126,"text":6979},[6073,613],"2012-05-07T17:56:12","JMX (Java Management Extensions) provides the infrastructure to support monitoring and management of your Java\\napplications. Resources you manage with JMX are called Managed Beans (MBeans). I want to show you how to quickly\\nregister your own Service as MBean using Spring and Source-Level Metadata (JDK 5.0+ annotations).","https://synyx.de/blog/how-to-monitor-and-manage-your-java-application-with-jmx/",{},"/blog/how-to-monitor-and-manage-your-java-application-with-jmx",{"title":6363,"description":6372},"blog/how-to-monitor-and-manage-your-java-application-with-jmx",[7025,7026,7027,7028,7029,7030,2099,1010],"annotation","jconsole","jminix","jmx","mbeans","metadata","JMX (Java Management Extensions) provides the infrastructure to support monitoring and management of your Java applications. Resources you manage with JMX are called Managed Beans (MBeans). I want to show…","Lf-pqL8fxMUtFEesPQZ2fYbpNoUAqH8DOQaGCkCDsBA",{"id":7034,"title":7035,"author":7036,"body":7037,"category":7156,"date":7157,"description":7158,"extension":617,"link":7159,"meta":7160,"navigation":499,"path":7161,"seo":7162,"slug":7041,"stem":7163,"tags":7164,"teaser":7172,"__hash__":7173},"blog/blog/elektronische-urlaubsverwaltung-made-by-youngsters.md","Elektronische Urlaubsverwaltung made by Youngsters",[6090],{"type":11,"value":7038,"toc":7154},[7039,7042,7045,7048,7051,7056,7059,7062,7065,7068,7071,7091,7094,7097,7100,7103,7106,7109,7112,7115,7118,7124,7127,7130,7133,7136,7139,7142,7145,7148,7151],[14,7040,7035],{"id":7041},"elektronische-urlaubsverwaltung-made-by-youngsters",[18,7043,7044],{},"Für die erfolgreiche Genehmigung von Urlaub muss der synyx’sche Mitarbeiter sich bisher mit einem Stück Papier alias\n‘schriftlich ausgefüllter Urlaubsantrag’ bewaffnen und sich an einen der drei Chefs anpirschen, um eine Unterschrift zu\nergattern.",[18,7046,7047],{},"Dies ist nicht nur zeitaufwändig, sondern auch einfach nicht zeitgemäß für eine junge Software-Schmiede wie Synyx.",[18,7049,7050],{},"So erhielten Johannes Reuter (Studentische Hilfskraft seit 1. August 2011) und ich, Aljona Murygina (Auszubildende zur\nFachinformatikerin seit 1. August 2011), den Auftrag, das ganze Prozedere zu modernisieren, wir befinden uns\nschließlich im Jahr 2011:",[18,7052,7053],{},[27,7054,7055],{},"Ein Urlaubsverwaltungs-Tool als Webapplikation muss her.",[18,7057,7058],{},"Als Grundgerüst der Applikation wurden drei verschiedene Arten von Usern festgelegt:",[18,7060,7061],{},"der normale User (Antragssteller: Urlaub beantragen),",[18,7063,7064],{},"der Chef (Vergabeberechtigter: Urlaubsantrag genehmigen/ablehnen) und",[18,7066,7067],{},"das Office (eine Art Super-User mit Verwaltungs- und Editierfunktion: Urlaubsantrag bearbeiten).",[18,7069,7070],{},"Essentielle Grundfunktionen des Urlaubsverwaltungs-Tool sollen sein:",[577,7072,7073,7076,7079,7082,7085,7088],{},[580,7074,7075],{},"Die Authentifizierung soll mittels bereits vorhandenen LDAP-Benutzerdaten der Mitarbeiter erfolgen.",[580,7077,7078],{},"Urlaubsanträge sollen digital validiert werden, das heißt Antragssteller und Antraggenehmigender müssen jeweils über\neinen einzigartigen Identifikationsnachweis (‘digitale Unterschrift’) verfügen.",[580,7080,7081],{},"Eine Interaktion mit dem firmeninternen Google-Kalender soll für automatisierte Übersichtlichkeit sorgen, welche\nMitarbeiter in welchem Zeitraum im Urlaub sind.",[580,7083,7084],{},"Transparenz ist die Quintessenz einer Open Source Firma und hört auch nicht bei firmeninternen Vorgängen auf. Daher\nsollen jegliche Schritte der Bearbeitung von Anträgen durch eine Log-Datei nachvollziehbar sein können.",[580,7086,7087],{},"Via E-Mail sollen die jeweils Beteiligten über den laufenden Antragsvorgang informiert werden, ob zum Beispiel ein\nneuer zu genehmigender Antrag vorliegt oder ein Antrag genehmigt wurde.",[580,7089,7090],{},"Urlaubstage per Hand abzuzählen ist ziemlich retro, weshalb im digitalen Urlaubsantrag nur noch via Datumseingabe ein\nZeitraum angegeben werden soll. Über einen Feiertagskalender berechnet dann die Applikation statt der Antragssteller,\nwie viele Urlaubstage netto für diesen Zeitraum vom Urlaubskonto des Mitarbeiters abgezogen werden.",[18,7092,7093],{},"Was die programmatischen Werkzeuge betrifft, wurde uns freie Hand gelassen.",[18,7095,7096],{},"Wir entschieden uns, den Kern des Tools in Java zu schreiben und für die ‘äußere Hülle’ der Webapplikation das Framework\nSpring MVC zu benutzen.",[18,7098,7099],{},"Zunächst erschien uns der Arbeitsauftrag ziemlich trivial.",[18,7101,7102],{},"Bei der Konzeption des Tools allerdings stießen wir doch schnell an einige Stolpersteine.",[18,7104,7105],{},"Zu nennen wäre hier beispielsweise:",[18,7107,7108],{},"Die LDAP-Authentifizierung erschien auf den ersten Blick einfacher, bis wir feststellten, was für ein komplexes und\nweites Feld Spring Security eigentlich darstellt.",[18,7110,7111],{},"Bei der digitalen Validierung wechselten wir mehrmals unsere Meinung. Zur Auswahl verfügbar wäre erstens ein Photo der\neigenen Unterschrift, verknüpft mit der jeweiligen Person, oder zweitens die Signatur mittels eines persönlichen\nPGP-Schlüssels. (momentan favorisieren wir letztere Möglichkeit)",[18,7113,7114],{},"Auch die Interaktion mit einem Google-Kalender ist ein Feld, in das es sich noch einzulesen gilt.",[18,7116,7117],{},"Den inneren Kern unseres Tools, die Domain-Objekte, haben wir nun letztendlich in die Klassen Antrag, Person,\nUrlaubsanspruch und Urlaubskonto gegliedert.",[18,7119,7120],{},[2694,7121],{"alt":7122,"src":7123},"\"Domainobjekte\"","https://media.synyx.de/uploads//2011/11/Domainobjekte1.png",[18,7125,7126],{},"Als formelle Stolpersteine bei der Konzeption und Implementierung der Domain-Objekte und Services erwies sich die\nUnterscheidung in ‘normalen’ Urlaub und Resturlaub.",[18,7128,7129],{},"Resturlaub ist übrig gebliebener Jahresurlaub aus dem vorherigen Jahr, den ein Mitarbeiter mit ins neue Jahr nimmt.",[18,7131,7132],{},"Zwei wichtige Daten sind also der 1. Januar und der 1. April.",[18,7134,7135],{},"Am 1. Januar wird aus dem verbliebenem Urlaub des vorigen Jahres Resturlaub, während am 1. April dieser Resturlaub\nverfällt.",[18,7137,7138],{},"Wenn der Urlaubszeitraum also über einen dieser beiden Termine fällt (z.B. Urlaub vom 25.03. – 04.04.), erfordert\ndie Berechnung des Urlaubskontos des Mitarbeiters eine besondere Logik.",[18,7140,7141],{},"Die entsprechenden Services (AntragService, PersonService, UrlaubskontoService) sind großteils schon ausimplementiert,\njedoch ergeben sich aufgrund der Herausforderung der besonderen datumsabhängigen Logik immer wieder Veränderungen im\nCode.",[18,7143,7144],{},"Der MailService ist bisher gelöst mit dem JavaMailSender und MimeMessagePreparator (\norg.springframework.mail.javamail.*), benötigt sicherlich jedoch auch noch eine Überarbeitung der genauen Details.",[18,7146,7147],{},"Controller und die zugehörigen JSPs sind ebenfalls bereits großteils funktionsfähig, z.B. Darstellung von Mitarbeiter-\noder Antragslisten (zum Beispiel nach Status, Person, Datum), Formular zur Antragsstellung.",[18,7149,7150],{},"Das Design betreffend (z.B. dynamische Menü-Navigation oder Kalenderdarstellung ‘DatePicker’) werden wir aller\nWahrscheinlichkeit nach zu Bibliotheken von jQuery greifen.",[18,7152,7153],{},"Bevor wir allerdings überhaupt an das i-Tüpfelchen Design denken können, haben wir noch einen langen Weg vor uns….",{"title":48,"searchDepth":86,"depth":86,"links":7155},[],[6073],"2011-11-07T20:13:06","Für die erfolgreiche Genehmigung von Urlaub muss der synyx’sche Mitarbeiter sich bisher mit einem Stück Papier alias\\n‘schriftlich ausgefüllter Urlaubsantrag’ bewaffnen und sich an einen der drei Chefs anpirschen, um eine Unterschrift zu\\nergattern.","https://synyx.de/blog/elektronische-urlaubsverwaltung-made-by-youngsters/",{},"/blog/elektronische-urlaubsverwaltung-made-by-youngsters",{"title":7035,"description":7044},"blog/elektronische-urlaubsverwaltung-made-by-youngsters",[7165,7166,7167,7168,7169,7170,7171],"ausbildung","azubi-2","spring-mvc","synyx","urlaubsantrag","urlaubsverwaltung","webapplikation","Für die erfolgreiche Genehmigung von Urlaub muss der synyx’sche Mitarbeiter sich bisher mit einem Stück Papier alias ‘schriftlich ausgefüllter Urlaubsantrag’ bewaffnen und sich an einen der drei Chefs anpirschen, um…","Iciij-U2SfXBfMkPBt4jEMrE_4Z6koLV-sClbDtNMa0",{"id":7175,"title":7176,"author":7177,"body":7178,"category":7236,"date":7237,"description":7238,"extension":617,"link":7239,"meta":7240,"navigation":499,"path":7241,"seo":7242,"slug":7182,"stem":7244,"tags":7245,"teaser":7250,"__hash__":7251},"blog/blog/synyx-messagesource-load-your-i18n-messages-from-database.md","Synyx MessageSource: Load your i18n messages from database",[2486],{"type":11,"value":7179,"toc":7234},[7180,7183,7192,7199,7214],[14,7181,7176],{"id":7182},"synyx-messagesource-load-your-i18n-messages-from-database",[18,7184,7185,7186,7191],{},"A while ago we wanted to store internationalisation for a project in database to allow (a subset of) users to create and\nupdate internationalisation using the application itself. When we searched the web for existing projects that allow to\ndo this we did not find a good and ready to use solution. This is why we decided to write the code ourselves and make it\navailable to others, especially since there was some public demand and it will probably not be shipped by SpringSource (\ncheck out ",[585,7187,7190],{"href":7188,"rel":7189},"http://www.google.de/search?q=spring+i18n+database",[589],"Google"," for details).",[18,7193,7194,7195,7198],{},"So today I’d like to announce our new Open Source project ",[27,7196,7197],{},"Synyx Messagesource for Spring"," business-friendly\npublished using Apache License, Version 2.0.",[18,7200,7201,7202,7205,7206,7209,7210,7213],{},"When you want to store internationalisation of your Spring-backed application in database, the project is the right\nthing to use. It provides an implementation of Springs ",[50,7203,7204],{},"MessageSource"," interface that is able to load and cache a set of\nmessages at once using a ",[50,7207,7208],{},"MessageProvider",". The project brings a configurable one that is able to read (and write) your\ni18n to database using JDBC. There is also support to import and export your messages to the “standard” i18n\n",[50,7211,7212],{},".properties"," files.",[18,7215,7216,7217,7222,7223,7228,7229,986],{},"You can find the projects homepage including documentation how to get started and how everything\nworks ",[585,7218,7221],{"href":7219,"rel":7220},"http://messagesource.synyx.org",[589],"here",". You should not get problems to get up and running after reading the\ninformation from the ",[585,7224,7227],{"href":7225,"rel":7226},"https://github.com/synyx/messagesource/wiki",[589],"projects Wiki",". If you’re having any trouble or\nfeature request feel free to contact us\nor ",[585,7230,7233],{"href":7231,"rel":7232},"https://github.com/synyx/messagesource/issues",[589],"create a ticket in the projects issue tracker",{"title":48,"searchDepth":86,"depth":86,"links":7235},[],[613,996],"2011-02-14T18:06:55","A while ago we wanted to store internationalisation for a project in database to allow (a subset of) users to create and\\nupdate internationalisation using the application itself. When we searched the web for existing projects that allow to\\ndo this we did not find a good and ready to use solution. This is why we decided to write the code ourselves and make it\\navailable to others, especially since there was some public demand and it will probably not be shipped by SpringSource (\\ncheck out Google for details).","https://synyx.de/blog/synyx-messagesource-load-your-i18n-messages-from-database/",{},"/blog/synyx-messagesource-load-your-i18n-messages-from-database",{"title":7176,"description":7243},"A while ago we wanted to store internationalisation for a project in database to allow (a subset of) users to create and\nupdate internationalisation using the application itself. When we searched the web for existing projects that allow to\ndo this we did not find a good and ready to use solution. This is why we decided to write the code ourselves and make it\navailable to others, especially since there was some public demand and it will probably not be shipped by SpringSource (\ncheck out Google for details).","blog/synyx-messagesource-load-your-i18n-messages-from-database",[5696,7246,7247,7248,7249,1010],"i18n","internationalisation","internationalization","open-source","A while ago we wanted to store internationalisation for a project in database to allow (a subset of) users to create and update internationalisation using the application itself. When we…","nyZcMPq1I7-esYkZoVwAgntWZKJrIJkCdwQvpagdS20",{"id":7253,"title":7254,"author":7255,"body":7257,"category":7307,"date":7308,"description":7309,"extension":617,"link":7310,"meta":7311,"navigation":499,"path":7312,"seo":7313,"slug":7261,"stem":7315,"tags":7316,"teaser":7318,"__hash__":7319},"blog/blog/synyx-open-source-projekt-hades-schafft-den-sprung-zu-springsource.md","Synyx Open Source Projekt Hades schafft den Sprung zu SpringSource",[7256],"karrasz",{"type":11,"value":7258,"toc":7305},[7259,7262,7276,7279,7282,7285,7288],[14,7260,7254],{"id":7261},"synyx-open-source-projekt-hades-schafft-den-sprung-zu-springsource",[18,7263,7264,7265,7270,7271,986],{},"Am Donnerstag hat SpringSource das erste Milestone-Release von Spring Data\nJPA ",[585,7266,7269],{"href":7267,"rel":7268},"http://www.springsource.org/node/3022",[589],"veröffentlicht",". Spring Data JPA ist die Weiterentwicklung der Synyx\nOpenSource ",[585,7272,7275],{"href":7273,"rel":7274},"http://hades.synyx.org",[589],"Bibliothek Hades",[18,7277,7278],{},"Hades wurde hauptsächlich von Oliver Gierke entwickelt, welcher bis Anfang 2010 als Software Architekt bei Synyx tätig\nwar. Die Entwicklung des Projekts wurde von Synyx vollumfänglich unterstützt und gefördert, die Bibliothek in vielen\nProjekten erfolgreich eingesetzt.",[18,7280,7281],{},"Oliver hat mittlerweile neue Wege eingeschlagen und arbeitet nun direkt für SpringSource. Die Entwicklung an Hades\nführte er, auch in Zusammenarbeit mit Synyx, fort. In den letzten Monaten hat er sich darum gekümmert, die Zukunft von\nHades bei SpringSource zu sichern. Deshalb freut es uns natürlich sehr, dass die Bibliothek nun in Spring Data\nintegriert wurde, dort das JPA Modul und darüber hinaus die Basis für die Unterstützung weiterer Persistenztechnologien\nbildet.",[18,7283,7284],{},"Wir hoffen, dass Hades so einer noch breiteren Masse an Entwicklern des Implementieren von Persistenzschichten\nerleichtern wird. Natürlich wird auch Synyx das neue Spring Data JPA mit Vergnügen und Erfolg einsetzen.",[18,7286,7287],{},"Nähere Infos:",[577,7289,7290,7297],{},[580,7291,7292],{},[585,7293,7296],{"href":7294,"rel":7295},"http://synyx.org/",[589],"Synyx.org – Open Source Projekte",[580,7298,7299,7300],{},"Tutorial zu Spring Data JPA von Oliver\nim ",[585,7301,7304],{"href":7302,"rel":7303},"http://blog.springsource.com/2011/02/10/getting-started-with-spring-data-jpa/",[589],"SpringSource Blog",{"title":48,"searchDepth":86,"depth":86,"links":7306},[],[996],"2011-02-13T10:07:31","Am Donnerstag hat SpringSource das erste Milestone-Release von Spring Data\\nJPA veröffentlicht. Spring Data JPA ist die Weiterentwicklung der Synyx\\nOpenSource Bibliothek Hades.","https://synyx.de/blog/synyx-open-source-projekt-hades-schafft-den-sprung-zu-springsource/",{},"/blog/synyx-open-source-projekt-hades-schafft-den-sprung-zu-springsource",{"title":7254,"description":7314},"Am Donnerstag hat SpringSource das erste Milestone-Release von Spring Data\nJPA veröffentlicht. Spring Data JPA ist die Weiterentwicklung der Synyx\nOpenSource Bibliothek Hades.","blog/synyx-open-source-projekt-hades-schafft-den-sprung-zu-springsource",[7317,1010],"hades","Am Donnerstag hat SpringSource das erste Milestone-Release von Spring Data JPA veröffentlicht. Spring Data JPA ist die Weiterentwicklung der Synyx OpenSource Bibliothek Hades. Hades wurde hauptsächlich von Oliver Gierke entwickelt,…","0l5QYp6kg1FYAQd1i8BEizzQ0V_uvxv2TDCb_vaCBe0",{"id":7321,"title":7322,"author":7323,"body":7325,"category":7407,"date":7408,"description":7409,"extension":617,"link":7410,"meta":7411,"navigation":499,"path":7412,"seo":7413,"slug":7329,"stem":7415,"tags":7416,"teaser":7418,"__hash__":7419},"blog/blog/spring-ide-into-eclipse.md","Spring IDE into eclipse",[7324],"ruessel",{"type":11,"value":7326,"toc":7405},[7327,7330,7345,7360,7363,7366,7371,7386,7389,7392],[14,7328,7322],{"id":7329},"spring-ide-into-eclipse",[18,7331,7332,7333,7338,7339,7344],{},"Today, I tried to install parts of the ",[585,7334,7337],{"href":7335,"rel":7336},"http://www.springsource.com/developer/sts",[589],"SpringSource Tool Suite","\ninto ",[585,7340,7343],{"href":7341,"rel":7342},"http://www.eclipse.org/",[589],"Eclipse"," Helios SR1 via update-site.",[18,7346,7347,7348,7354,7355,7359],{},"After finding the right update-site of the STS for version 3.6 of eclipse (namely: *\n",[573,7349,7350],{},[585,7351,7352],{"href":7352,"rel":7353},"http://dist.springsource.com/release/TOOLS/update/e3.6",[589],"* and * *",[585,7356,7357],{"href":7357,"rel":7358},"http://dist.springsource.com/release/TOOLS/composite/e3.6",[589],"**) and following the installation instructions carefully, I\nhad Spring IDE 2.5.2 successfully integrated.",[18,7361,7362],{},"But after restarting eclipse I was shocked, it seemed that the update-manager broke down…",[18,7364,7365],{},"when opening the update-manager the log gasped out:",[18,7367,1038,7368,1042],{},[573,7369,7370],{},"An internal error occurred during: Contacting Software Sites. java.lang.NullPointerException…",[18,7372,7373,7374,7379,7380,7385],{},"didn’t sound good. After quite a lot of researching and try and failing, I was very glad to find\na ",[585,7375,7378],{"href":7376,"rel":7377},"https://jira.springsource.org/browse/IDE-1163?page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#issue-tabs",[589],"bug report",",\nwhich mentioned that this occurs on updating Spring IDE. Thanks\nto ",[585,7381,7384],{"href":7382,"rel":7383},"http://www.springsource.com/people/cdupuis",[589],"Christian Dupuis"," from SpringSource, who provided a workaround\nyesterday. For some reason a second org.apache.commons.httpclient_3.1.0….jar was shipped and confused eclipse. But\nafter commenting one of them out in “configuration/org.eclipse.equinox.simpleconfigurator/bundles.info”",[18,7387,7388],{},"everything was working well again.",[18,7390,7391],{},"Now I can enjoy my Spring IDE and perhaps you can so, too.",[18,7393,7394,7395,7398,7399,7404],{},"edit: in the meantime ",[585,7396,7384],{"href":7382,"rel":7397},[589]," already delivered\na ",[585,7400,7403],{"href":7401,"rel":7402},"http://dist.springsource.com/release/TOOLS/update/2.5.2.SR1/e3.6/springsource-tool-suite-2.5.2.RELEASE-e3.6-updatesite.zip",[589],"quickly fixed update-archive","\nwhich makes the file manipulating superfluous… Hell these guys are quite fast!",{"title":48,"searchDepth":86,"depth":86,"links":7406},[],[613],"2011-01-12T18:08:02","Today, I tried to install parts of the SpringSource Tool Suite\\ninto Eclipse Helios SR1 via update-site.","https://synyx.de/blog/spring-ide-into-eclipse/",{},"/blog/spring-ide-into-eclipse",{"title":7322,"description":7414},"Today, I tried to install parts of the SpringSource Tool Suite\ninto Eclipse Helios SR1 via update-site.","blog/spring-ide-into-eclipse",[2897,7417,1010],"eclipse","Today, I tried to install parts of the SpringSource Tool Suite into Eclipse Helios SR1 via update-site. After finding the right update-site of the STS for version 3.6 of eclipse…","pYIVv5Z_1S__iGMQhek2t48OhdaNC3Lw0q-CD9EVE-M",{"id":7421,"title":7422,"author":7423,"body":7424,"category":7622,"date":7623,"description":7624,"extension":617,"link":7434,"meta":7625,"navigation":499,"path":7626,"seo":7627,"slug":7428,"stem":7629,"tags":7630,"teaser":7635,"__hash__":7636},"blog/blog/modular-web-applications-based-on-spring.md","Modular Web-Applications based on Spring",[2486],{"type":11,"value":7425,"toc":7620},[7426,7429,7438,7441,7450,7464,7467,7489,7495,7503,7513,7547,7564,7570,7608,7618],[14,7427,7422],{"id":7428},"modular-web-applications-based-on-spring",[18,7430,7431,7432,7437],{},"Many of the Web-Applications we develop for our customers are based upon our small Framework on top\nof ",[585,7433,7436],{"href":7434,"rel":7435},"https://synyx.de/blog/modular-web-applications-based-on-spring/",[589],"Spring / Spring MVC",". This framework basically\nbrings often used components ready-to-use (or ready to customize) and – of course – makes things even simpler than\nSpring already does.",[18,7439,7440],{},"Modular design of applications brings a lot of advantages but – as always – also some disadvantages. A modular structure\ncan help to increase cohesion and let developers focus on the function their concrete module has. Another big thing is\nreusability. So the core framework already brings functionality that is used in all projects that depend on the\nframework so far: user management for example. On the other hand modular design also brings complexity. I think its\nbusiness of a framework to hide this complexity from the user (developer in this case). Nevertheless its good when the\ndeveloper knows (and understands) what goes on under the hood and (even more important) can easily extend the framework\nwhere he needs to.",[18,7442,7443,7444,7449],{},"As I mentioned we use Spring / Spring MVC as a base for many projects. Spring provides a lot of points where you can\nextend the framework by implementing interfaces and defining or injecting these implementations to the classes that do\nthe real work (like interceptors\nin ",[585,7445,7448],{"href":7446,"rel":7447},"http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.html",[589],"AnnotationHandlerMapping",").\nIn most cases this is enough. If you start to develop a modular application it is not. At least it was not for our case.",[18,7451,7452,7453,7456,7457,7460,7461],{},"Our Web-Applications based on the mentioned Framework always use at least two “modules”: The core-module (that brings\nuser management and some nice features to make coders happy) and the application itself. Each of these modules brings\nits own bean-configurations which are loaded all together using a wildcard resource like the following that reads all\n",[50,7454,7455],{},"beans.xml"," files within any subfolder of",[50,7458,7459],{},"META-INF"," in the applications classpath: ",[50,7462,7463],{},"classpath*:META-INF/**/beans.xml",[18,7465,7466],{},"This is a really simple way how the modules can interact since one module can provide a Service another depends on.",[18,7468,7469,7470,7475,7476,7478,7479,7482,7483,7485,7486,7488],{},"Now, the big problem is, that each module often brings its own components that have to be registered to Springs\n“services”. Let me explain the problem to you by talking\nabout",[585,7471,7474],{"href":7472,"rel":7473},"http://blog.synyx.de/2010/04/21/know-your-apis-lessons-learned-from-resourcebundle/",[589],"internationalization again",".\nEach module brings its own resource bundle containing the internationalization for its web-interface components. Spring\nprovides a simple way to register a",[50,7477,7204],{}," by defining one bean with id",[50,7480,7481],{},"messageSource"," in your context. And that\nis exactly the problem. It is",[27,7484,3996],{},"bean. So you need a way where each module can register its own",[50,7487,7204],{},", even\nif Spring only supports one. So our framework has to handle this, because it also introduces the modular structure.",[18,7490,7491,7492,7494],{},"The “out of the box” way that would work is that the application, that assembles all these modules together defines the\n",[50,7493,7204],{}," with all basenames (of the properties-files) the application uses. But this would be part of the\nmentioned complexity that should be kept away from the daily business and brings some other problems in (what, if one\nmodule wants to store its internationalization within the database?…).",[18,7496,7497,7498],{},"So what did we do? We use a Simple plugin-mechanism a colleague developed and Synyx publishes\nOpenSource:",[585,7499,7502],{"href":7500,"rel":7501},"http://hera.synyx.org",[589],"Hera",[18,7504,7505,7506,7508,7509,7512],{},"We use a bean that gets registered as",[50,7507,7204],{}," to Spring that takes care of dispatching the message-resolving\nrequests to the real ",[50,7510,7511],{},"MessageSources"," implementations spread all over the modules.",[43,7514,7516],{"className":2285,"code":7515,"language":2287,"meta":48,"style":48},"\u003Cbean id=\"messageSource\">\n \u003Cproperty name=\"sources\">\n \u003Cplugin:list class=\"org.synyx.minos.message.ModuleMessageSource\"/>\n \u003C/property>\n \u003Cproperty name=\"useCodeAsDefaultMessage\" value=\"true\" />\n\u003C/bean>\n",[50,7517,7518,7523,7528,7533,7538,7543],{"__ignoreMap":48},[53,7519,7520],{"class":55,"line":56},[53,7521,7522],{},"\u003Cbean id=\"messageSource\">\n",[53,7524,7525],{"class":55,"line":86},[53,7526,7527],{}," \u003Cproperty name=\"sources\">\n",[53,7529,7530],{"class":55,"line":126},[53,7531,7532],{}," \u003Cplugin:list class=\"org.synyx.minos.message.ModuleMessageSource\"/>\n",[53,7534,7535],{"class":55,"line":163},[53,7536,7537],{}," \u003C/property>\n",[53,7539,7540],{"class":55,"line":186},[53,7541,7542],{}," \u003Cproperty name=\"useCodeAsDefaultMessage\" value=\"true\" />\n",[53,7544,7545],{"class":55,"line":221},[53,7546,6501],{},[18,7548,7549,7550,7553,7554,7557,7558,7560,7561,7563],{},"So this registers our",[50,7551,7552],{},"DispatchingMessageSource"," that gets injected into all beans within the context, implementing\n",[50,7555,7556],{},"ModuleMessageSource"," by Hera. This pretty much does the trick. The reason that we use",[50,7559,7556],{}," instead of\nSprings built-in",[50,7562,7204],{},"-interface is on the one hand so that we can do some performance-tweaks and on the\nother hand so that we dont get any “unwanted” implementations, which get to the context somehow.",[18,7565,7566,7567,7569],{},"With some simple dispatching logic within",[50,7568,7552],{}," we found a powerful way to conquer the insufficiency\nof Spring, in conjunction with our modular system.",[43,7571,7573],{"className":2285,"code":7572,"language":2287,"meta":48,"style":48},"List candidates = sources.getPluginsFor(getPrefixFromCode(code));\nfor (MessageSourcePlugin source : candidates) {\n MessageFormat format = resolveMessageWithSource(source, code, locale);\n if (null != format) {\n return format;\n }\n}\n",[50,7574,7575,7580,7585,7590,7595,7600,7604],{"__ignoreMap":48},[53,7576,7577],{"class":55,"line":56},[53,7578,7579],{},"List candidates = sources.getPluginsFor(getPrefixFromCode(code));\n",[53,7581,7582],{"class":55,"line":86},[53,7583,7584],{},"for (MessageSourcePlugin source : candidates) {\n",[53,7586,7587],{"class":55,"line":126},[53,7588,7589],{}," MessageFormat format = resolveMessageWithSource(source, code, locale);\n",[53,7591,7592],{"class":55,"line":163},[53,7593,7594],{}," if (null != format) {\n",[53,7596,7597],{"class":55,"line":186},[53,7598,7599],{}," return format;\n",[53,7601,7602],{"class":55,"line":221},[53,7603,860],{},[53,7605,7606],{"class":55,"line":242},[53,7607,282],{},[18,7609,7610,7611,1073,7614,7617],{},"By the way, we use this mechanism a lot when it comes to easily extending functionality of the framework-core including\n",[50,7612,7613],{},"HandlerInterceptor",[50,7615,7616],{},"PropertyEditorRegistrar"," and our Modules itself.",[607,7619,989],{},{"title":48,"searchDepth":86,"depth":86,"links":7621},[],[613],"2010-04-23T11:45:49","Many of the Web-Applications we develop for our customers are based upon our small Framework on top\\nof Spring / Spring MVC. This framework basically\\nbrings often used components ready-to-use (or ready to customize) and – of course – makes things even simpler than\\nSpring already does.",{},"/blog/modular-web-applications-based-on-spring",{"title":7422,"description":7628},"Many of the Web-Applications we develop for our customers are based upon our small Framework on top\nof Spring / Spring MVC. This framework basically\nbrings often used components ready-to-use (or ready to customize) and – of course – makes things even simpler than\nSpring already does.","blog/modular-web-applications-based-on-spring",[7631,7632,7248,7633,7634,1010],"architecture","framework","modular","plugin","Many of the Web-Applications we develop for our customers are based upon our small Framework on top of Spring / Spring MVC. This framework basically brings often used components ready-to-use…","9CCujFfUkmvCk9K2-lTvameRh4QQAhnYiCvpeGTJeT8",[7638,7641,7644,7647,7650,7653,7656,7659,7662,7664,7666,7669,7672,7675,7678,7681,7684,7687,7690,7693,7696,7699,7701,7704,7707,7710,7713,7715,7717,7720,7723,7726,7729,7732,7735,7738,7741,7744,7747,7750,7753,7756,7759,7762,7765,7768,7771,7774,7777,7780,7783,7785,7788,7791,7794,7796,7799,7801,7804,7807,7810,7813,7816,7819,7822,7825,7828,7831,7834,7837,7840,7843,7846,7849,7852,7855,7858,7861,7863,7866,7869,7872,7874,7877,7880,7883,7886,7889,7892,7895,7898,7901,7904,7907,7910,7913,7916,7919,7922,7924,7927,7930,7933,7936,7938,7940,7943,7946,7949,7952,7955,7957,7959,7962,7965,7968,7971,7974,7977,7980,7983,7986,7989,7992,7995,7998,8001,8004,8007,8010,8011,8013,8016,8019,8022,8025,8028,8031,8034,8037,8040,8043],{"slug":7639,"name":7640},"abel","Jennifer Abel",{"slug":7642,"name":7643},"allmendinger","Otto Allmendinger",{"slug":7645,"name":7646},"antony","Ben Antony",{"slug":7648,"name":7649},"arrasz","Joachim Arrasz",{"slug":7651,"name":7652},"bauer","David Bauer",{"slug":7654,"name":7655},"bechtold","Janine Bechtold",{"slug":7657,"name":7658},"boersig","Jasmin Börsig",{"slug":7660,"name":7661},"buch","Fabian Buch",{"slug":6090,"name":7663},"Aljona Buchloh",{"slug":9,"name":7665},"Julia Burgard",{"slug":7667,"name":7668},"caspar-schwedes","Caspar Schwedes",{"slug":7670,"name":7671},"christina-schmitt","Christina Schmitt",{"slug":7673,"name":7674},"clausen","Michael Clausen",{"slug":7676,"name":7677},"contargo_poetzsch","Thomas Pötzsch",{"slug":7679,"name":7680},"damrath","Sebastian Damrath",{"slug":7682,"name":7683},"daniel","Markus Daniel",{"slug":7685,"name":7686},"dasch","Julia Dasch",{"slug":7688,"name":7689},"denman","Joffrey Denman",{"slug":7691,"name":7692},"dfuchs","Daniel Fuchs",{"slug":7694,"name":7695},"dobler","Max Dobler",{"slug":7697,"name":7698},"dobriakov","Vladimir Dobriakov",{"slug":7700,"name":7700},"dreiqbik",{"slug":7702,"name":7703},"dschaefer","Denise Schäfer",{"slug":7705,"name":7706},"dschneider","Dominik Schneider",{"slug":7708,"name":7709},"duerlich","Isabell Duerlich",{"slug":7711,"name":7712},"dutkowski","Bernd Dutkowski",{"slug":7714,"name":7714},"eifler",{"slug":633,"name":7716},"Tim Essig",{"slug":7718,"name":7719},"ferstl","Maximilian Ferstl",{"slug":7721,"name":7722},"fey","Prisca Fey",{"slug":7724,"name":7725},"frank","Leonard Frank",{"slug":7727,"name":7728},"franke","Arnold Franke",{"slug":7730,"name":7731},"frischer","Nicolette Rudmann",{"slug":7733,"name":7734},"fuchs","Petra Fuchs",{"slug":7736,"name":7737},"gari","Sarah Gari",{"slug":7739,"name":7740},"gast","Gast",{"slug":7742,"name":7743},"graf","Johannes Graf",{"slug":7745,"name":7746},"grammlich","Daniela Grammlich",{"slug":7748,"name":7749},"guthardt","Sabrina Guthardt",{"slug":7751,"name":7752},"haeussler","Johannes Häussler",{"slug":7754,"name":7755},"hammann","Daniel Hammann",{"slug":7757,"name":7758},"heetel","Julian Heetel",{"slug":7760,"name":7761},"heft","Florian Heft",{"slug":7763,"name":7764},"heib","Sebastian Heib",{"slug":7766,"name":7767},"heisler","Ida Heisler",{"slug":7769,"name":7770},"helm","Patrick Helm",{"slug":7772,"name":7773},"herbold","Michael Herbold",{"slug":7775,"name":7776},"hofmann","Peter Hofmann",{"slug":7778,"name":7779},"hopf","Florian Hopf",{"slug":7781,"name":7782},"jaud","Alina Jaud",{"slug":1758,"name":7784},"Robin De Silva Jayasinghe",{"slug":7786,"name":7787},"jbuch","Jonathan Buch",{"slug":7789,"name":7790},"junghanss","Gitta Junghanß",{"slug":7792,"name":7793},"kadyietska","Khrystyna Kadyietska",{"slug":2486,"name":7795},"Marc Kannegiesser",{"slug":7797,"name":7798},"karoly","Robert Károly",{"slug":7256,"name":7800},"Katja Arrasz-Schepanski",{"slug":7802,"name":7803},"kaufmann","Florian Kaufmann",{"slug":7805,"name":7806},"kesler","Mike Kesler",{"slug":7808,"name":7809},"kirchgaessner","Bettina Kirchgäßner",{"slug":7811,"name":7812},"klem","Yannic Klem",{"slug":7814,"name":7815},"klenk","Timo Klenk",{"slug":7817,"name":7818},"knell","Tobias Knell",{"slug":7820,"name":7821},"knoll","Anna-Lena Knoll",{"slug":7823,"name":7824},"knorre","Matthias Knorre",{"slug":7826,"name":7827},"koenig","Melanie König",{"slug":7829,"name":7830},"kraft","Thomas Kraft",{"slug":7832,"name":7833},"krupicka","Florian Krupicka",{"slug":7835,"name":7836},"kuehn","Christian Kühn",{"slug":7838,"name":7839},"lange","Christian Lange",{"slug":7841,"name":7842},"larrasz","Luca Arrasz",{"slug":7844,"name":7845},"leist","Sascha Leist",{"slug":7847,"name":7848},"lihs","Michael Lihs",{"slug":7850,"name":7851},"linsin","David Linsin",{"slug":7853,"name":7854},"maniyar","Christian Maniyar",{"slug":7856,"name":7857},"martin","Björnie",{"slug":7859,"name":7860},"martin-koch","Martin Koch",{"slug":5704,"name":7862},"Tobias Matt",{"slug":7864,"name":7865},"mennerich","Christian Mennerich",{"slug":7867,"name":7868},"menz","Alexander Menz",{"slug":7870,"name":7871},"meseck","Frederick Meseck",{"slug":4142,"name":7873},"Oliver Messner",{"slug":7875,"name":7876},"michael-ploed","Michael Plöd",{"slug":7878,"name":7879},"mies","Marius Mies",{"slug":7881,"name":7882},"mihai","Alina Mihai",{"slug":7884,"name":7885},"moeller","Jörg Möller",{"slug":7887,"name":7888},"mohr","Rebecca Mohr",{"slug":7890,"name":7891},"moretti","David Moretti",{"slug":7893,"name":7894},"mueller","Sven Müller",{"slug":7896,"name":7897},"muessig","Alexander Müssig",{"slug":7899,"name":7900},"neupokoev","Grigory Neupokoev",{"slug":7902,"name":7903},"nussbaecher","Carmen Nussbächer",{"slug":7905,"name":7906},"ochs","Pascal Ochs",{"slug":7908,"name":7909},"oelhoff","Jan Oelhoff",{"slug":7911,"name":7912},"oengel","Yasin Öngel",{"slug":7914,"name":7915},"oezsoy","Enis Özsoy",{"slug":7917,"name":7918},"posch","Maya Posch",{"slug":7920,"name":7921},"ralfmueller","Ralf Müller",{"slug":7923,"name":7923},"redakteur",{"slug":7925,"name":7926},"reich","Michael Reich",{"slug":7928,"name":7929},"reinhard","Karl-Ludwig Reinhard",{"slug":7931,"name":7932},"rmueller","Rebecca Müller",{"slug":7934,"name":7935},"rosum","Jan Rosum",{"slug":7937,"name":7937},"rueckert",{"slug":7324,"name":7939},"Sascha Rüssel",{"slug":7941,"name":7942},"sauter","Moritz Sauter",{"slug":7944,"name":7945},"schaefer","Julian Schäfer",{"slug":7947,"name":7948},"scherer","Petra Scherer",{"slug":7950,"name":7951},"schlicht","Anne Schlicht",{"slug":7953,"name":7954},"schmidt","Jürgen Schmidt",{"slug":2149,"name":7956},"Tobias Schneider",{"slug":2906,"name":7958},"Benjamin Seber",{"slug":7960,"name":7961},"sommer","Marc Sommer",{"slug":7963,"name":7964},"speaker-fels","Jakob Fels",{"slug":7966,"name":7967},"speaker-gierke","Oliver Gierke",{"slug":7969,"name":7970},"speaker-krupa","Malte Krupa",{"slug":7972,"name":7973},"speaker-mader","Jochen Mader",{"slug":7975,"name":7976},"speaker-meusel","Tim Meusel",{"slug":7978,"name":7979},"speaker-milke","Oliver Milke",{"slug":7981,"name":7982},"speaker-paluch","Mark Paluch",{"slug":7984,"name":7985},"speaker-schad","Jörg Schad",{"slug":7987,"name":7988},"speaker-schalanda","Jochen Schalanda",{"slug":7990,"name":7991},"speaker-schauder","Jens Schauder",{"slug":7993,"name":7994},"speaker-unterstein","Johannes Unterstein",{"slug":7996,"name":7997},"speaker-wolff","Eberhard Wolff",{"slug":7999,"name":8000},"speaker-zoerner","Stefan Zörner",{"slug":8002,"name":8003},"stefan-belger","Stefan Belger",{"slug":8005,"name":8006},"steinegger","Roland Steinegger",{"slug":8008,"name":8009},"stern","sternchen synyx",{"slug":7168,"name":7168},{"slug":3935,"name":8012},"Mateusz Szulc",{"slug":8014,"name":8015},"tamara","Tamara Tunczinger",{"slug":8017,"name":8018},"theuer","Tobias Theuer",{"slug":8020,"name":8021},"thieme","Sandra Thieme",{"slug":8023,"name":8024},"thies-clasen","Marudor",{"slug":8026,"name":8027},"toernstroem","Olle Törnström",{"slug":8029,"name":8030},"ullinger","Max Ullinger",{"slug":8032,"name":8033},"ulrich","Stephan Ulrich",{"slug":8035,"name":8036},"wagner","Stefan Wagner",{"slug":8038,"name":8039},"weigel","Andreas Weigel",{"slug":8041,"name":8042},"werner","Fabian Werner",{"slug":8044,"name":8045},"wolke","Sören Wolke",["Reactive",8047],{"$scookieConsent":8048,"$ssite-config":8050},{"functional":8049,"analytics":8049},false,{"_priority":8051,"env":8055,"name":8056,"url":8057},{"name":8052,"env":8053,"url":8054},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",8060],{"category-spring":-1,"authors":-1},"/blog/tags/spring"]