Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Aspecio - aspect-oriented programming meets the OSGi service model - Simon Chemouil

317 views

Published on

OSGi Community Event 2016 Presentation by Simon Chemouil (Lambdacube)

Aspect-oriented programming is a paradigm meant to provide “horizontal” modularity: by encapsulating cross-cutting concerns such as access control or performance metrics away from business logic, it was supposed to be a new tool for developers that would not only prevent copy-paste and guard methods, but allow to stack semantic models on top of single-concern implementations.

The early excitement somewhat faded when the freedom provided by rich aspect languages proved to make understanding of code and its debugging harder because of scattered logic and altered bytecode. Today in the Java world, it is used mostly in a lighter form in the Spring framework, but it has traditionally been difficult to integrate properly with OSGi.

When OSGi R5 introduced the ServiceHook API, one of its promises was to enable OSGi-powered implementations of aspect frameworks. Aspecio[1] is such a framework, taking an opinionated approach to aspects to make them predictable while giving a lot of control to developers and keeping the overhead minimal:

Fully OSGi-compliant: Interceptors are services, dynamism is fully supported ;
Define your aspects in plain Java ;
Works with any OSGi component framework, such as Declarative Services, Blueprint, or plain OSGi core APIs ;
Minimal overhead: on-demand bytecode generation, no primitive boxing or Method#invoke, and a “pay only for what you use” approach through a Java 8 mixin-like advice definition API ;
Reasonably easy to debug: no change to existing bytecode, generated proxies are very thin and expose a well-documented behavior.
The OSGi service model is so versatile that aspects could feel useless at first glance; it turns out they are a handy and complementary tool in OSGi application design.
In this talk, I will present Aspecio and what we can accomplish by mixing the OSGi service model with aspects, and demonstrate how anyone can add aspects to any OSGi application in a few minutes without refactoring existing code.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Aspecio - aspect-oriented programming meets the OSGi service model - Simon Chemouil

  1. 1. Motivation Aspecio Summary Aspecio aspect-oriented programming meets the OSGi service model Simon Chemouil Lambdacube OSGi Community Event, 2016 1 / 35 Simon Chemouil Aspecio
  2. 2. Motivation Aspecio Summary Aspecio Aspecio A Java/OSGi R6+ ’micro-framework’ that brings a mix of component-oriented and aspect-oriented programming to your application. http: // lambdacube. github. io/ aspecio/ 2 / 35 Simon Chemouil Aspecio
  3. 3. Motivation Aspecio Summary Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 3 / 35 Simon Chemouil Aspecio
  4. 4. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 4 / 35 Simon Chemouil Aspecio
  5. 5. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns The DRY Principle Do Not Repeat Yourself Mostly a rule-of-thumb to organize code Benefits: Share code / Fix problems once (modularity) Limit verbosity (readability) 5 / 35 Simon Chemouil Aspecio
  6. 6. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns The DRY Principle Do Not Repeat Yourself Mostly a rule-of-thumb to organize code Benefits: Share code / Fix problems once (modularity) Limit verbosity (readability) 5 / 35 Simon Chemouil Aspecio
  7. 7. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns The DRY Principle Do Not Repeat Yourself Mostly a rule-of-thumb to organize code Benefits: Share code / Fix problems once (modularity) Limit verbosity (readability) 5 / 35 Simon Chemouil Aspecio
  8. 8. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns The DRY Principle Do Not Repeat Yourself Mostly a rule-of-thumb to organize code Benefits: Share code / Fix problems once (modularity) Limit verbosity (readability) 5 / 35 Simon Chemouil Aspecio
  9. 9. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns The DRY Principle Do Not Repeat Yourself Mostly a rule-of-thumb to organize code Benefits: Share code / Fix problems once (modularity) Limit verbosity (readability) 5 / 35 Simon Chemouil Aspecio
  10. 10. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Motivation: modularizing implementations OSGi solves API & service modularity What about implementations? 6 / 35 Simon Chemouil Aspecio
  11. 11. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Motivation: modularizing implementations OSGi solves API & service modularity What about implementations? 6 / 35 Simon Chemouil Aspecio
  12. 12. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 7 / 35 Simon Chemouil Aspecio
  13. 13. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Modularizing implementations Example package com.mylibrary.book; @Component public final class BookManagerImpl implements BookManager { @Reference AccessControl accessControl ; public Promise <Book > getBook(String isbn) { try { accessControl . ensureAuthorized (...); /* business code here */ return actuallyGetBookNow (isbn ); } catch (Exception e) { return Promises.failed(e); } } } 8 / 35 Simon Chemouil Aspecio
  14. 14. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Modularizing implementations Example package com.mylibrary.book; @Component public final class BookManagerImpl implements BookManager { @ EnsureAuthorized public Promise <Book > getBook(String isbn) { /* business code here */ return actuallyGetBookNow (isbn ); } } 9 / 35 Simon Chemouil Aspecio
  15. 15. Motivation Aspecio Summary DRY and modularity Dealing with cross-cutting concerns Modularizing implementations Example package com.mylibrary.book; @Component public final class BookManagerImpl implements BookManager { @ EnsureAuthorized @Measured public Promise <Book > getBook(String isbn) { /* business code here */ return actuallyGetBookNow (isbn ); } } 10 / 35 Simon Chemouil Aspecio
  16. 16. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 11 / 35 Simon Chemouil Aspecio
  17. 17. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Aspecio Aspects at injection-time! No modification of existing bytecode: dynamic proxy injection instead Built with OSGi in mind Component-framework agnostic ; also works with plain APIs Minimal proxy overhead No primitive boxing, pay for what you use 12 / 35 Simon Chemouil Aspecio
  18. 18. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Aspecio Aspects at injection-time! No modification of existing bytecode: dynamic proxy injection instead Built with OSGi in mind Component-framework agnostic ; also works with plain APIs Minimal proxy overhead No primitive boxing, pay for what you use 12 / 35 Simon Chemouil Aspecio
  19. 19. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Aspecio Aspects at injection-time! No modification of existing bytecode: dynamic proxy injection instead Built with OSGi in mind Component-framework agnostic ; also works with plain APIs Minimal proxy overhead No primitive boxing, pay for what you use 12 / 35 Simon Chemouil Aspecio
  20. 20. The Big Picture
  21. 21. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Framework Hooks Since OSGi r5 (updated in r6) Weaving Hooks Ability to alter the bytecode of a class when it is first loaded. Useful for “traditional” aspect frameworks, or bytecode manipulation frameworks in general. Service Hooks Intercept queries to BundleContext#getService, service events, service listeners registration 14 / 35 Simon Chemouil Aspecio
  22. 22. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Framework Hooks Since OSGi r5 (updated in r6) Weaving Hooks Ability to alter the bytecode of a class when it is first loaded. Useful for “traditional” aspect frameworks, or bytecode manipulation frameworks in general. Service Hooks Intercept queries to BundleContext#getService, service events, service listeners registration 14 / 35 Simon Chemouil Aspecio
  23. 23. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Framework Hooks Since OSGi r5 (updated in r6) Weaving Hooks Ability to alter the bytecode of a class when it is first loaded. Useful for “traditional” aspect frameworks, or bytecode manipulation frameworks in general. Service Hooks Intercept queries to BundleContext#getService, service events, service listeners registration 14 / 35 Simon Chemouil Aspecio
  24. 24. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 15 / 35 Simon Chemouil Aspecio
  25. 25. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Applying Aspects In Aspecio, service implementations decide if they want aspects Two service properties: service.aspect.weave: “I can only be consumed as a service if that aspect is woven” service.aspect.weave.optional: “I can be consumed anyhow, but if that aspect is available, use it” Aspects are similar to service interfaces, they represent a concept, not necessarily a specific implementation They are implemented with interceptors that are OSGi services and may come and go 16 / 35 Simon Chemouil Aspecio
  26. 26. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Applying Aspects In Aspecio, service implementations decide if they want aspects Two service properties: service.aspect.weave: “I can only be consumed as a service if that aspect is woven” service.aspect.weave.optional: “I can be consumed anyhow, but if that aspect is available, use it” Aspects are similar to service interfaces, they represent a concept, not necessarily a specific implementation They are implemented with interceptors that are OSGi services and may come and go 16 / 35 Simon Chemouil Aspecio
  27. 27. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Applying Aspects Example package com.mylibrary.book; @Component(properties = { "service.aspect.weave=com.mylibrary.auth.AuthAspect", "service.aspect.weave=com.metrics. MetricsAspect " }) public final class BookManagerImpl implements BookManager { @ EnsureAuthorized @Measured public Promise <Book > getBook(String isbn) { /* business code here */ return actuallyGetBookNow (isbn ); } } 17 / 35 Simon Chemouil Aspecio
  28. 28. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Applying Aspects Example package com.mylibrary.book; @Component @Weave(required = { AuthAspect.class , MetricAspect .class }) public final class BookManagerImpl implements BookManager { @ EnsureAuthorized @Measured public Promise <Book > getBook(String isbn) { /* business code here */ return actuallyGetBookNow (isbn ); } } 18 / 35 Simon Chemouil Aspecio
  29. 29. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Applying Aspects Example package com.mylibrary.book; @Component @Weave(required = AuthAspect.class , optional = MetricAspect .class) public final class BookManagerImpl implements BookManager { @ EnsureAuthorized @Measured public Promise <Book > getBook(String isbn) { /* business code here */ return actuallyGetBookNow (isbn ); } } 19 / 35 Simon Chemouil Aspecio
  30. 30. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Defining Aspects Aspects can have any name, but we use classes by convention to avoid conflicts. To publish an Aspect, we register an OSGi service implementing Interceptor (or one of its derivatives) With the String property service.aspect containing the name of the Aspect Optionally, it is possible to define the property service.aspect.extraProperties 20 / 35 Simon Chemouil Aspecio
  31. 31. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Defining Aspects Aspects can have any name, but we use classes by convention to avoid conflicts. To publish an Aspect, we register an OSGi service implementing Interceptor (or one of its derivatives) With the String property service.aspect containing the name of the Aspect Optionally, it is possible to define the property service.aspect.extraProperties 20 / 35 Simon Chemouil Aspecio
  32. 32. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Defining Aspects Example package com.metrics; // Marker interface public interface MetricAspect { } 21 / 35 Simon Chemouil Aspecio
  33. 33. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Defining Aspects Example package com.metrics; @Component @Aspect(provides = MetricAspect .class , extraProperties = "measured ") public final class AnnotatedMetricInterceptorImpl implements AnnotationInterceptor <Measured > { @Reference Metrics metrics; public Class <Measured > intercept () { return Measured.class; } public Advice onCall(Measured annotation , CallContext callContext) { String methodName = callContext .target.getName () + "::" + callContext .method.getName (); Context syncTimer = metrics.timer(methodName ). time (); return new AdviceAdapter () { public int afterPhases () { return Finally.PHASE; } public void runFinally () { syncTimer.close (); } }; } } 22 / 35 Simon Chemouil Aspecio
  34. 34. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 23 / 35 Simon Chemouil Aspecio
  35. 35. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started A Closer Look At Interceptors In Aspecio, services providing aspects are called Interceptors Interceptors always intercept all service methods We can narrow it down by matching some annotations If multiple interceptors provide the same aspect, Aspecio chooses the one with the highest service ranking If the rankings are equal, the one with the lowest service id is selected 24 / 35 Simon Chemouil Aspecio
  36. 36. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started A Closer Look At Interceptors In Aspecio, services providing aspects are called Interceptors Interceptors always intercept all service methods We can narrow it down by matching some annotations If multiple interceptors provide the same aspect, Aspecio chooses the one with the highest service ranking If the rankings are equal, the one with the lowest service id is selected 24 / 35 Simon Chemouil Aspecio
  37. 37. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Each time a method of the aspect proxy is called, the methods onCall of the active interceptors are called The method onCall returns an Advice Advices tell Aspecio what to do when a proxy method is called 25 / 35 Simon Chemouil Aspecio
  38. 38. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Each time a method of the aspect proxy is called, the methods onCall of the active interceptors are called The method onCall returns an Advice Advices tell Aspecio what to do when a proxy method is called 25 / 35 Simon Chemouil Aspecio
  39. 39. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Optionally request the actual arguments passed to the method, and alter them; Skip the call to the original method and return a value... or proceed with the call; Obtain throwables potentially thrown by the proxied method and optionally alter them, but not swallow them; Obtain and alter the return value All of these for reference types and all primitive types to prevent boxing! 26 / 35 Simon Chemouil Aspecio
  40. 40. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Optionally request the actual arguments passed to the method, and alter them; Skip the call to the original method and return a value... or proceed with the call; Obtain throwables potentially thrown by the proxied method and optionally alter them, but not swallow them; Obtain and alter the return value All of these for reference types and all primitive types to prevent boxing! 26 / 35 Simon Chemouil Aspecio
  41. 41. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Optionally request the actual arguments passed to the method, and alter them; Skip the call to the original method and return a value... or proceed with the call; Obtain throwables potentially thrown by the proxied method and optionally alter them, but not swallow them; Obtain and alter the return value All of these for reference types and all primitive types to prevent boxing! 26 / 35 Simon Chemouil Aspecio
  42. 42. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Optionally request the actual arguments passed to the method, and alter them; Skip the call to the original method and return a value... or proceed with the call; Obtain throwables potentially thrown by the proxied method and optionally alter them, but not swallow them; Obtain and alter the return value All of these for reference types and all primitive types to prevent boxing! 26 / 35 Simon Chemouil Aspecio
  43. 43. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Advices Optionally request the actual arguments passed to the method, and alter them; Skip the call to the original method and return a value... or proceed with the call; Obtain throwables potentially thrown by the proxied method and optionally alter them, but not swallow them; Obtain and alter the return value All of these for reference types and all primitive types to prevent boxing! 26 / 35 Simon Chemouil Aspecio
  44. 44. The Big Picture Again
  45. 45. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Aspecio’s Dynamic Proxies Aspecio generates proxies with the ASM bytecode generation library Proxies might generate several classes lazily e.g if arguments are requested, an immutable data class in generated for these unboxed arguments, with proper hashCode and equals definitions. Proxies generated by Aspecio expose the same binary signature as the class they proxy Including generic signature, annotations, etc, so that framework relying on introspection work transparently 28 / 35 Simon Chemouil Aspecio
  46. 46. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Aspecio’s Dynamic Proxies Aspecio generates proxies with the ASM bytecode generation library Proxies might generate several classes lazily e.g if arguments are requested, an immutable data class in generated for these unboxed arguments, with proper hashCode and equals definitions. Proxies generated by Aspecio expose the same binary signature as the class they proxy Including generic signature, annotations, etc, so that framework relying on introspection work transparently 28 / 35 Simon Chemouil Aspecio
  47. 47. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Proxies and Services Aspecio can proxy services implementing multiple service interfaces just fine However Aspecio will ignore services registered as a class or an abstract class Service proxies registered by Aspecio share the same properties as the woven service Aspecio handles different service scopes intelligently (including prototype) It will not generate different proxies for a singleton service scoped as bundle for lazy-loading reasons (e.g, by Declarative Services) 29 / 35 Simon Chemouil Aspecio
  48. 48. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Proxies and Services Aspecio can proxy services implementing multiple service interfaces just fine However Aspecio will ignore services registered as a class or an abstract class Service proxies registered by Aspecio share the same properties as the woven service Aspecio handles different service scopes intelligently (including prototype) It will not generate different proxies for a singleton service scoped as bundle for lazy-loading reasons (e.g, by Declarative Services) 29 / 35 Simon Chemouil Aspecio
  49. 49. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Proxies and Services Aspecio can proxy services implementing multiple service interfaces just fine However Aspecio will ignore services registered as a class or an abstract class Service proxies registered by Aspecio share the same properties as the woven service Aspecio handles different service scopes intelligently (including prototype) It will not generate different proxies for a singleton service scoped as bundle for lazy-loading reasons (e.g, by Declarative Services) 29 / 35 Simon Chemouil Aspecio
  50. 50. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Outline 1 Motivation DRY and modularity Dealing with cross-cutting concerns 2 Aspecio Overview Using Aspecio Interceptors, Advices and Proxies Getting started 3 Summary 30 / 35 Simon Chemouil Aspecio
  51. 51. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Getting started Aspecio is just one bundle depends only ony an OSGi R6+ framework slf4j is an optional dependency; otherwise JUL logging is used It has to be started early in the framework, before any bundle that uses it No way to “fake” service events to “unwire” components; Same situation for all frameworks making use of framework hooks, e.g subsystems 31 / 35 Simon Chemouil Aspecio
  52. 52. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Getting started Aspecio is just one bundle depends only ony an OSGi R6+ framework slf4j is an optional dependency; otherwise JUL logging is used It has to be started early in the framework, before any bundle that uses it No way to “fake” service events to “unwire” components; Same situation for all frameworks making use of framework hooks, e.g subsystems 31 / 35 Simon Chemouil Aspecio
  53. 53. Motivation Aspecio Summary Overview Using Aspecio Interceptors, Advices and Proxies Getting started Checking if Aspecio sees your aspects Aspecio provides two Gogo commands aspect:aspects aspects and interceptors available aspect:woven services currently woven, with which aspect 32 / 35 Simon Chemouil Aspecio
  54. 54. Motivation Aspecio Summary Aspecio Tech Sheet A bit of a component framework Plays with service dynamics and registers some on behalf of other bundles A lot of bytecode trickery Mimicking the signature of classes and methods it proxies Supporting all primitive types for call performance And a Java DSL to define advices Advices are close to automata that determine how the generated code will interact with user code 33 / 35 Simon Chemouil Aspecio
  55. 55. Motivation Aspecio Summary Aspecio Tech Sheet A bit of a component framework Plays with service dynamics and registers some on behalf of other bundles A lot of bytecode trickery Mimicking the signature of classes and methods it proxies Supporting all primitive types for call performance And a Java DSL to define advices Advices are close to automata that determine how the generated code will interact with user code 33 / 35 Simon Chemouil Aspecio
  56. 56. Motivation Aspecio Summary Aspecio Tech Sheet A bit of a component framework Plays with service dynamics and registers some on behalf of other bundles A lot of bytecode trickery Mimicking the signature of classes and methods it proxies Supporting all primitive types for call performance And a Java DSL to define advices Advices are close to automata that determine how the generated code will interact with user code 33 / 35 Simon Chemouil Aspecio
  57. 57. Motivation Aspecio Summary In-house Feedback Used on a large project (hundreds of bundles, thousands of services) Easy to get started Some aspects are extremely useful Metrics, Access Control, Exception Logging, ... Practical with integration testing (with and without Aspecio) Potential improvements: Better stacking of advices, asynchronous support, “this” handling 34 / 35 Simon Chemouil Aspecio
  58. 58. Motivation Aspecio Summary In-house Feedback Used on a large project (hundreds of bundles, thousands of services) Easy to get started Some aspects are extremely useful Metrics, Access Control, Exception Logging, ... Practical with integration testing (with and without Aspecio) Potential improvements: Better stacking of advices, asynchronous support, “this” handling 34 / 35 Simon Chemouil Aspecio
  59. 59. Motivation Aspecio Summary In-house Feedback Used on a large project (hundreds of bundles, thousands of services) Easy to get started Some aspects are extremely useful Metrics, Access Control, Exception Logging, ... Practical with integration testing (with and without Aspecio) Potential improvements: Better stacking of advices, asynchronous support, “this” handling 34 / 35 Simon Chemouil Aspecio
  60. 60. Motivation Aspecio Summary Aspecio Questions? http: // lambdacube. github. io/ aspecio/ Twitter: @simach simon.chemouil@lambdacube.fr 35 / 35 Simon Chemouil Aspecio
  61. 61. Evaluate the Sessions Sign in and vote at eclipsecon.org - 1 + 10

×