0
Industrial Strength
     Groovy

Tools for the Professional
   Groovy Developer

       Dr Paul King
         ASERT
    Br...
Topics
   Testing/Mocking: JUnit, TestNG, EasyB, Spock,
    Instinct, MockFor, Gmock, EasyMock
   Injection: Spring, Gui...
Groovy’s Appeal
   Innovators/Thought leaders
       Ideas, power, flexibility, novelty, thinking community
   Early ad...
Testing: JUnit

import org.junit.Test
import static org.junit.Assert.assertEquals

class ArithmeticTest {
    @Test
    vo...
Testing: TestNG
import ...
public class SimpleTest {
    @BeforeClass
    public void setUp() {
      // code invoked when...
Mocking: EasyMock
import org.easymock.EasyMock

mockControl = EasyMock.createStrictControl()
mockReverser = mockControl.cr...
Testing: Instinct
class a_default_storer {
    def storer
    @initially void create_new_storer() {
        storer = new S...
Mocking: MockFor
import groovy.mock.interceptor.MockFor

def mocker = new MockFor(Collaborator.class)    //   create the M...
Mocking: Gmock ...
   Method mocking: mockLoader.load(quot;fruitquot;).returns(quot;applequot;)
   Exception mocking: mo...
Mocking: Gmock

import org.gmock.GMockTestCase

class LoaderTest extends GMockTestCase {
    void testLoader(){
        de...
Testing: Spock ...
... Testing: Spock
@Speck
@RunWith(Sputnik)
class PublisherSubscriberSpeck {
  def quot;events are received by all subscri...
Testing: EasyB ...


given quot;an invalid zip codequot;, {
    invalidzipcode = quot;221o1quot;
}

and quot;given the zip...
Testing: EasyB ...
before quot;start seleniumquot;, {
 given quot;selenium is up and runningquot;, {
  // start selenium
 ...
Dependency Injection

   Hollywood Principle
       Don’t call us, we’ll call you

   “All problems in computer science...
Dependency Injection
   Pattern for loosely coupled & testable objects
class Client {                class Client {
  Cal...
Dependency Injection: Spring ...
   Several flavors
        let’s look at Annotation and BeanBuilder flavors

    import...
... Dependency Injection: Spring
import
org.springframework.context.support.GenericApplicationContext
import
org.springfra...
Dependency Injection: Guice
import com.google.inject.*

@ImplementedBy(CalculatorImpl)
interface Calculator {
    def add(...
Dependency Injection:
      Metaprogramming Style
class Calculator {
    def total = 0
    def add(a, b) { total++; a + b ...
Coverage: Cobertura ...
                          Ant
... Coverage: Cobertura ...


                                                 Maven




cobertura-instrument
java –cp ......
... Coverage: Cobertura
Remember 100% coverage rule ...
   Necessary but not sufficient condition




                                           ...
Remember 100% coverage rule ...
   Necessary but not sufficient condition




                                           ...
... Remember 100% coverage rule
   Necessary but not sufficient condition
Code style: CodeNarc ...




About 50 rules (1 broken?)
... Code style: CodeNarc ...
... Code style: CodeNarc
Code style: IntelliJ
Duplication: Simian ...
Simian fully supports the following languages:
   Java
   C#
   C++                          wi...
... Duplication: Simian ...
... Duplication: Simian ...
... Duplication: Simian
Similarity Analyser 2.2.23 -
   http://www.redhillconsulting.com.au/products/simian/index.html
Cop...
Documentation: GroovyDoc ...
<taskdef name=quot;groovydocquot;
    classname=quot;org.codehaus.groovy.ant.Groovydocquot;>
...
... Documentation: GroovyDoc
Builds: Groovy from Ant
   Need groovy jar on your Ant classpath

<taskdef name=quot;groovyquot;
         classname=quot;...
Builds: Ant from Groovy
   Built-in (need ant.jar on your Groovy classpath)

new AntBuilder().with {
    echo(file:'Temp....
Builds: Gant
   lightweight façade on Groovy's AntBuilder
   target def’ns, pre-defined ‘ant’, operations on predefined ...
Builds: GMaven
   Implementing Maven plugins has never been
    Groovier!
   Groovy Mojos
       A Simple Groovy Mojo
...
Builds: GMaven

 <plugin>
    <groupId>org.codehaus.mojo.groovy</groupId>
    <artifactId>groovy-maven-plugin</artifactId>...
Builds: GMaven

def domainDir =
   project.properties['weblogic.domain.easyimage.dir']

stopWebLogic()
copyFiles(domainDir...
Builds: Gradle ...
   A very flexible general purpose build tool like Ant
   Switchable, build-by-convention frameworks ...
... Builds: Gradle
Builds: Hudson
   Gant Plugin — This plugin allows Hudson to
    invoke Gant build script as the main build step
   Grad...
Modularisation: Grapes
// Google Collections example
import com.google.common.collect.HashBiMap
@Grab(group='com.google.co...
Modularisation: OSGi
This is Apache Sling in five bullets:
   REST based web framework
   Content-driven, using a JCR co...
Modularisation: Futures
   Future ... possibly maybe ... Grapes then OSGi
       Command line/Conf file grab support (th...
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Upcoming SlideShare
Loading in...5
×

Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King

7,933

Published on

Published in: Technology
0 Comments
18 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
7,933
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
248
Comments
0
Likes
18
Embeds 0
No embeds

No notes for slide

Transcript of "Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King"

  1. 1. Industrial Strength Groovy Tools for the Professional Groovy Developer Dr Paul King ASERT Brisbane, Australia
  2. 2. Topics  Testing/Mocking: JUnit, TestNG, EasyB, Spock, Instinct, MockFor, Gmock, EasyMock  Injection: Spring, Guice  Coverage: Cobertura  Code style: CodeNarc, IntelliJ  Duplication: Simian  Documentation: GroovyDoc  Builds: Ant, Gant, GMaven, Gradle, Hudson  Modularisation: Grapes, OSGi
  3. 3. Groovy’s Appeal  Innovators/Thought leaders  Ideas, power, flexibility, novelty, thinking community  Early adopters  Productivity benefits and collegiate community  Leverage JVM and potential for mainstream  Mainstream  Leverage existing Java skills, low learning curve  Leverage JVM and production infrastructure  Professional community  Tools, tools, tools
  4. 4. Testing: JUnit import org.junit.Test import static org.junit.Assert.assertEquals class ArithmeticTest { @Test void additionIsWorking() { assertEquals 4, 2+2 } @Test(expected=ArithmeticException) void divideByZero() { println 1/0 } }
  5. 5. Testing: TestNG import ... public class SimpleTest { @BeforeClass public void setUp() { // code invoked when test is created } @Test(groups = [ quot;fastquot; ]) public void aFastTest() { System.out.println(quot;Fast testquot;); } @Test(groups = [ quot;slowquot; ]) public void aSlowTest() { System.out.println(quot;Slow testquot;); } }
  6. 6. Mocking: EasyMock import org.easymock.EasyMock mockControl = EasyMock.createStrictControl() mockReverser = mockControl.createMock(Reverser.class) storer = new JavaStorer(mockReverser) testStorage() def testStorage() { expectReverse(123.456, -123.456) expectReverse('hello', 'olleh') mockControl.replay() checkReverse(123.456, -123.456) checkReverse('hello', 'olleh') mockControl.verify() } def expectReverse(input, output) { EasyMock.expect(mockReverser.reverse(input)).andReturn(output) } def checkReverse(value, reverseValue) { storer.put(value) assert value == storer.get() assert reverseValue == storer.getReverse() }
  7. 7. Testing: Instinct class a_default_storer { def storer @initially void create_new_storer() { storer = new Storer() } private check_persist_and_reverse(value, expectedReverse) { storer.put(value) def persisted = storer.get() assert persisted == value def reversed = storer.reverse assert reversed == expectedReverse } @spec def should_reverse_numbers() { check_persist_and_reverse 123.456, -123.456 } @spec def should_reverse_strings() { check_persist_and_reverse 'hello', 'olleh' } @spec def should_reverse_lists() { check_persist_and_reverse([1, 3, 5], [5, 3, 1]) } } check_specs_for a_default_storer
  8. 8. Mocking: MockFor import groovy.mock.interceptor.MockFor def mocker = new MockFor(Collaborator.class) // create the Mock support mocker.demand.one(1..2) { 1 } // demand the 'one' method one // or two times, returning 1 mocker.demand.two() { 2 } // demand the 'two' method // exactly once, returning 2 mocker.use { // start using the Mock def caller = new Caller() // caller will call Collaborator assertEquals 1, caller.collaborateOne() // will call Collaborator.one assertEquals 1, caller.collaborateOne() // will call Collaborator.one assertEquals 2, caller.collaborateTwo() // will call Collaborator.two } // implicit verify here import groovy.mock.interceptor.MockFor def mocker = new MockFor(Collaborator.class) mocker.demand.one(1..2) { 1 } mocker.demand.two() { 2 } mocker.use { def caller = new Caller() assertEquals 1, caller.collaborateOne() assertEquals 1, caller.collaborateOne() assertEquals 2, caller.collaborateTwo() }
  9. 9. Mocking: Gmock ...  Method mocking: mockLoader.load(quot;fruitquot;).returns(quot;applequot;)  Exception mocking: mockLoader.load(quot;unknownquot;).raises(new RuntimeException())  Stub mocking: mockLoader.load(quot;fruitquot;).returns(quot;applequot;).stub()  Static method mocking: mockMath.static.random().returns(0.5)  Property mocking: mockLoader.name.returns(quot;loaderquot;)  Constructor mocking: def mockFile = mock(File, constructor('/a/path/file.txt'))  Partial mocking: mock(controller).params.returns([id: 3])  Times expectation: mockLoader.load(quot;fruitquot;).returns(quot;applequot;).atLeastOnce()  Custom matcher: mockLoader.load(match{ it.startsWith(quot;fruquot;) })  Strict ordering: ordered { ... }  Optional support for Hamcrest matcher: mockLoader.put(quot;testquot;, is(not(lessThan(5))))  GMockController if you can't extend GMockTestCase in your test
  10. 10. Mocking: Gmock import org.gmock.GMockTestCase class LoaderTest extends GMockTestCase { void testLoader(){ def mockLoader = mock() mockLoader.load('key').returns('value') play { assertEquals quot;valuequot;, mockLoader.load('key') } } }
  11. 11. Testing: Spock ...
  12. 12. ... Testing: Spock @Speck @RunWith(Sputnik) class PublisherSubscriberSpeck { def quot;events are received by all subscribersquot;() { def pub = new Publisher() def sub1 = Mock(Subscriber) def sub2 = Mock(Subscriber) pub.subscribers << sub1 << sub2 when: pub.send(quot;eventquot;) then: 1 * sub1.receive(quot;eventquot;) 1 * sub2.receive(quot;eventquot;) } }
  13. 13. Testing: EasyB ... given quot;an invalid zip codequot;, { invalidzipcode = quot;221o1quot; } and quot;given the zipcodevalidator is initializedquot;, { zipvalidate = new ZipCodeValidator() } when quot;validate is invoked with the invalid zip codequot;, { value = zipvalidate.validate(invalidzipcode) } then quot;the validator instance should return falsequot;, { value.shouldBe false }
  14. 14. Testing: EasyB ... before quot;start seleniumquot;, { given quot;selenium is up and runningquot;, { // start selenium } } scenario quot;a valid person has been enteredquot;, { when quot;filling out the person form with a first and last namequot;, { selenium.open(quot;http://acme.racing.net/greport/personracereport.htmlquot;) selenium.type(quot;fnamequot;, quot;Britneyquot;) selenium.type(quot;lnamequot;, quot;Smithquot;) } and quot;the submit link has been clickedquot;, { selenium.click(quot;submitquot;) } then quot;the report should have a list of races for that personquot;, { selenium.waitForPageToLoad(quot;5000quot;) values = [quot;Mclean 1/2 Marathonquot;, quot;Reston 5Kquot;, quot;Herndon 10Kquot;, quot;Leesburg 10Kquot;] for(i in 0..<values.size()){ selenium.getText(quot;//table//tr[${(i+3)}]/tdquot;).shouldBeEqualTo values[i] } } } after quot;stop seleniumquot; , { then quot;selenium should be shutdownquot;, { // stop selenium } }
  15. 15. Dependency Injection  Hollywood Principle  Don’t call us, we’ll call you  “All problems in computer science can be solved by another level of indirection”  quot;...except for the problem of too many layers of indirection“  For attributions, see http://en.wikipedia.org/wiki/Inversion_of_control
  16. 16. Dependency Injection  Pattern for loosely coupled & testable objects class Client { class Client { Calculator calc = Calculator calc new CalculatorImpl() def executeCalc(a, b) { def executeCalc(a, b) { calc.add(a, b) calc.add(a, b) } } } }  Need to select setter,  Service locator/factory constructor, field style  Tightly coupled?  Can add complexity  Hard to test?  Manage configuration  Easy to understand?  Direct or framework  Refactoring/navigation?  Consistency/lifecycle
  17. 17. Dependency Injection: Spring ...  Several flavors  let’s look at Annotation and BeanBuilder flavors import org.springframework.stereotype.Component @Component class AdderImpl { def add(x, y) { x + y } } import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @Component class CalcImpl3 { @Autowired private AdderImpl adder def doAdd(x, y) { adder.add(x, y) } }
  18. 18. ... Dependency Injection: Spring import org.springframework.context.support.GenericApplicationContext import org.springframework.context.annotation.ClassPathBeanDefinitio nScanner def ctx = new GenericApplicationContext() new ClassPathBeanDefinitionScanner(ctx).scan('') ctx.refresh() def calc = ctx.getBean('calcImpl3') println calc.doAdd(3, 4) // => 7 def bb = new grails.spring.BeanBuilder() bb.beans { adder(AdderImpl) calcBean(CalcImpl2) { delegate.adder = adder } } def ctx = bb.createApplicationContext() def calc = ctx.getBean('calcBean') println calc.doAdd(3, 4) // => 7
  19. 19. Dependency Injection: Guice import com.google.inject.* @ImplementedBy(CalculatorImpl) interface Calculator { def add(a, b) } @Singleton class CalculatorImpl implements Calculator { private total = 0 def add(a, b) { total++; a + b } def getTotalCalculations() { 'Total Calculations: ' + total } String toString() { 'Calc: ' + hashCode()} } class Client { @Inject Calculator calc // ... } def injector = Guice.createInjector() // ...
  20. 20. Dependency Injection: Metaprogramming Style class Calculator { def total = 0 def add(a, b) { total++; a + b } } def INSTANCE = new Calculator() Calculator.metaClass.constructor = { -> INSTANCE } def c1 = new Calculator() def c2 = new Calculator() assert c1.add(1, 2) == 3 assert c2.add(3, 4) == 7 assert c1.is(c2) assert [c1, c2].total == [2, 2]
  21. 21. Coverage: Cobertura ... Ant
  22. 22. ... Coverage: Cobertura ... Maven cobertura-instrument java –cp ... Command-line cobertura-report cobertura-check cobertura-merge Grails grails install-plugin code-coverage --global
  23. 23. ... Coverage: Cobertura
  24. 24. Remember 100% coverage rule ...  Necessary but not sufficient condition // 100%
  25. 25. Remember 100% coverage rule ...  Necessary but not sufficient condition // 100% // Fail
  26. 26. ... Remember 100% coverage rule  Necessary but not sufficient condition
  27. 27. Code style: CodeNarc ... About 50 rules (1 broken?)
  28. 28. ... Code style: CodeNarc ...
  29. 29. ... Code style: CodeNarc
  30. 30. Code style: IntelliJ
  31. 31. Duplication: Simian ... Simian fully supports the following languages:  Java  C#  C++ with partial support for:  C • JSP  Objective-C • ASP  JavaScript (ECMAScript) • HTML  COBOL, ABAP • XML  Ruby  Lisp  SQL  Visual Basic  Groovy
  32. 32. ... Duplication: Simian ...
  33. 33. ... Duplication: Simian ...
  34. 34. ... Duplication: Simian Similarity Analyser 2.2.23 - http://www.redhillconsulting.com.au/products/simian/index.html Copyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved. Simian is not free unless used solely for non-commercial or evaluation purposes. {failOnDuplication=true, ignoreCharacterCase=true, ignoreCurlyBraces=true, ignoreIdentifierCase=true, ignoreModifiers=true, ignoreStringCase=true, threshold=6} Found 6 duplicate lines in the following files: Between lines 201 and 207 in /Users/haruki_zaemon/Projects/redhill/simian/build/dist/src/java/awt/image/W ritableRaster.java ... Found 66375 duplicate lines in 5949 blocks in 1260 files Processed a total of 390309 significant (1196065 raw) lines in 4242 files Processing time: 9.490sec
  35. 35. Documentation: GroovyDoc ... <taskdef name=quot;groovydocquot; classname=quot;org.codehaus.groovy.ant.Groovydocquot;> <classpath> <path path=quot;${mainClassesDirectory}quot; /> <path refid=quot;compilePathquot; /> </classpath> </taskdef> <groovydoc destdir=quot;${docsDirectory}/gapiquot; sourcepath=quot;${mainSourceDirectory}quot; packagenames=quot;**.*quot; use=quot;truequot; windowtitle=quot;${title} quot; doctitle=quot;${title}quot; header=quot;${title}quot; footer=quot;${docFooter}quot; overview=quot;src/main/overview.htmlquot; private=quot;falsequot;> <link packages=quot;java.,org.xml.,javax.,org.xml.quot; href=quot;http://java.sun.com/j2se/1.5.0/docs/apiquot; /> <link packages=quot;org.apache.ant.,org.apache.tools.ant.quot; href=quot;http://www.dpml.net/api/ant/1.7.0quot; /> <link packages=quot;org.junit.,junit.framework.quot; href=quot;http://junit.sourceforge.net/junit3.8.1/javadoc/quot; /> </groovydoc>
  36. 36. ... Documentation: GroovyDoc
  37. 37. Builds: Groovy from Ant  Need groovy jar on your Ant classpath <taskdef name=quot;groovyquot; classname=quot;org.codehaus.groovy.ant.Groovyquot; classpathref=quot;my.classpathquot;/> <target name=quot;printXmlFileNamesFromJarquot;> <zipfileset id=quot;foundquot; src=quot;foobar.jarquot; includes=quot;**/*.xmlquot;/> <groovy> project.references.found.each { println it.name } </groovy> </target>
  38. 38. Builds: Ant from Groovy  Built-in (need ant.jar on your Groovy classpath) new AntBuilder().with { echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println(quot;Helloquot;); } } ''') javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done
  39. 39. Builds: Gant  lightweight façade on Groovy's AntBuilder  target def’ns, pre-defined ‘ant’, operations on predefined objects includeTargets << gant.targets.Clean cleanPattern << [ '**/*~' , '**/*.bak' ] cleanDirectory << 'build' target ( stuff : 'A target to do some stuff.' ) { println ( 'Stuff' ) depends ( clean ) echo ( message : 'A default message from Ant.' ) otherStuff ( ) } target ( otherStuff : 'A target to do some other stuff' ) { println ( 'OtherStuff' ) echo ( message : 'Another message from Ant.' ) clean ( ) }
  40. 40. Builds: GMaven  Implementing Maven plugins has never been Groovier!  Groovy Mojos  A Simple Groovy Mojo  Building Plugins  Project Definition  Mojo Parameters  Putting More Groove into your Mojo  Using ant, Using fail()  gmaven-archetype-mojo Archetype  gmaven-plugin Packaging
  41. 41. Builds: GMaven <plugin> <groupId>org.codehaus.mojo.groovy</groupId> <artifactId>groovy-maven-plugin</artifactId> <executions> <execution> <id>restart-weblogic</id> <phase>pre-integration-test</phase> <goals> <goal>execute</goal> </goals> <configuration> <source> ${pom.basedir}/src/main/script/restartWeblogic.groovy </source> </configuration> </execution> ...
  42. 42. Builds: GMaven def domainDir = project.properties['weblogic.domain.easyimage.dir'] stopWebLogic() copyFiles(domainDir) startWebLogic(domainDir) waitForWebLogicStartup() def stopWebLogic() { weblogicServerDir = project.properties['weblogic.server.dir'] adminUrl = project.properties['easyimage.weblogic.admin.t3'] userName = 'weblogic' password = 'weblogic' ant.exec(executable: 'cmd', failonerror: 'false') { arg(line: quot;/C ${wlsDir}/bin/setWLSEnv.cmd && java ...quot; ... } ...
  43. 43. Builds: Gradle ...  A very flexible general purpose build tool like Ant  Switchable, build-by-convention frameworks a la Maven. But we never lock you in!  Very powerful support for multi-project builds  Very powerful dependency management (based on Apache Ivy)  Full support for your existing Maven or Ivy repository infrastructure  Support for transitive dependency management without the need for remote repositories or pom.xml and ivy.xml files  Ant tasks as first class citizens  Groovy build scripts  A rich domain model for describing your build
  44. 44. ... Builds: Gradle
  45. 45. Builds: Hudson  Gant Plugin — This plugin allows Hudson to invoke Gant build script as the main build step  Gradle Plugin — This plugin allows Hudson to invoke Gradle build script as the main build step  Grails Plugin — This plugin allows Hudson to invoke Grails tasks as build steps  Hudson CLI and GroovyShell Usage pattern? Source: http://weblogs.java.net/blog/kohsuke/archive/2009/05/hudson_cli_and.html
  46. 46. Modularisation: Grapes // Google Collections example import com.google.common.collect.HashBiMap @Grab(group='com.google.collections', module='google-collections', version='1.0-rc1') def getFruit() { [ grape:'purple', lemon:'yellow', orange:'orange' ] as HashBiMap } assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon'
  47. 47. Modularisation: OSGi This is Apache Sling in five bullets:  REST based web framework  Content-driven, using a JCR content repository  Powered by OSGi  Scripting inside, multiple languages  Apache Open Source project See also: Grails JCR plugin See also: http://hamletdarcy.blogspot.com
  48. 48. Modularisation: Futures  Future ... possibly maybe ... Grapes then OSGi  Command line/Conf file grab support (thanks Danno)  Grab within a script without class/method  Grapes may have some light-weight hooks  groovyMethods  groovyStaticMethods  groovyExpandoMethods  groovyAstTransforms  groovyBuilderMetadata  Auto imports  Runner registration
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×