gr8conf 2009: practical groovy dsl by guillaume laforge

85

Upload: gr8conf

Post on 19-Jan-2015

2.071 views

Category:

Technology


2 download

DESCRIPTION

Guillaume Laforge, Groovy Project Manager, presents how to implement your own Domain-Specific Languages in Groovy

TRANSCRIPT

Page 1: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge
Page 2: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Practical Domain-Specific Languages with Groovy

All the techniques to create your own DSLs

Guillaume Laforge

Head of Groovy Development

Page 3: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• Groovy Project Manager

• JSR-241 Spec Lead

• Head of Groovy Developmentat SpringSource

• Initiator of the Grails framework

• Co-author of Groovy in Action

• Speaker: JavaOne, QCon, JavaZone, Sun TechDays, Devoxx, The Spring Experience, JAX, Dynamic Language World, IJTC, and more...

Guillaume Laforge

3

Page 4: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

A few words about Groovy

• Groovy is a dynamic language for the JVM

–with a Meta Object Protocol

–compiles directly to bytecode, seamless Java interop

• Open Source ASL 2 project hosted at Codehaus

• Relaxed grammar derived from Java 5

–+ borrowed good ideas from Ruby, Python, Smalltalk

• Fast... for a dynlang on the JVM

• Closures, properties, optional typing, BigDecimal by default, nice wrapper APIs, and more...

4

Page 5: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• The context and the usual issues we face

• Some real-life examples of Domain-Specific Languages

• Groovy’s DSL capabilities

• Integrating a DSL in your application

• Considerations to remember when designing your own DSL

Agenda

5

Page 6: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

The context

Page 7: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Subject Matter Experts,Business analysts...

Page 8: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

HAI

CAN HAS STDIO?

I HAS A VAR

IM IN YR LOOP

UP VAR!!1

VISIBLE VAR

IZ VAR BIGGER THAN 10?

KTHXBYE

IM OUTTA YR LOOP

KTHXBYE

Developer producing LOLCODE

Page 9: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Lots of languages...

Page 10: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

And in the end......nobody understands each other

Page 11: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

11

Expressing requirements...

Page 12: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

DSL: a potential solution?

•Use a more expressive language than a general purpose one

•Share a common metaphore of understanding between developers and subject matter experts

•Have domain experts help with the design of the business logic of an application

•Avoid cluttering business code with too much boilerplate technical code

•Cleanly separate business logic from application code

•Let business rules have their own lifecycle

12

Page 13: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (1)

13

Page 14: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (1)

13

Page 15: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (1)

20%

13

Page 16: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (2)

14

Page 17: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (2)

14

Page 18: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Towards more readability (2)

80%

14

Page 19: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• The context and the usual issues we face

• Some real-life examples of Domain-Specific Languages

• Groovy’s DSL capabilities

• Integrating a DSL in your application

• Considerations to remember when designing your own DSL

Agenda

15

Page 20: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• In our everyday life, we’re surrounded by DSLs

–Technical dialects

–Notations

–Business languages

16

A collection of DSLs

Page 21: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

17

Technical dialects

Page 22: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

SQL

Page 23: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

19

^[\w-\.]+@([\w-]){2,4}$

Page 24: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

20

Notations

Page 25: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

1. e4 e52. Nf3 Nc63. Bb5 a6

Page 26: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

L2 U F-1 B L2 F B -1 U L2

Page 27: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Visual!

Page 28: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

24

Business languages

Page 29: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Real-life Groovy examples

• Anti-malaria drug resistance simulation

• Human Resources employee skills representation

• Insurance policies risk calculation engine

• Loan acceptance rules engine for a financial platform

• Mathematica-like lingua for nuclear safety simulations

• Market data feeds evolution scenarios

• and more...

25

Page 30: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• The context and the usual issues we face

• Some real-life examples of Domain-Specific Languages

• Groovy’s DSL capabilities

• Integrating a DSL in your application

• Considerations to remember when designing your own DSL

Agenda

26

Page 31: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

A flexible & malleable syntax

• No need to write full-blown classes, use scripts

• Optional typing (def)

–in scripts, you can even omit the def keyword

• Native syntax constructs

• Parentheses & semi-colons are optional

• Named arguments

• BigDecimal by default for decimal numbers

• Closures for custom control structures

• Operator overloading

27

Page 32: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Scripts vs classes

• Hide all the boilerplate technical code

–an end-user doesn’t need to know about classes

–public class Rule { public static void main(String[] args) { System.out.println(“Hello”); }}

–println “Hello”

28

Page 33: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Optional typing

• No need to bother with types or even generics

–unless you want to!

• Imagine an interest rate lookup table method returning some generified type:

–Rate<LoanType, Duration, BigDecimal>[] lookupTable() { ... }def table = lookupTable()

• No need to repeat the horrible generics type info!

29

Page 34: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Native syntax constructs

• Lists

–[Monday, Tuesday, Wednesday]

• Maps

–[CA: ‘California’, TX: ‘Texas’]

• Ranges

–def bizDays = Monday..Friday

–def allowedAge = 18..65

–You can create your own custom ranges

30

Page 35: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Optional parens & semis

• Make statements and expressions look more like natural languages

–move(left);

–move left

31

Page 36: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Named arguments

• In Groovy you can mix named and unnamed arguments for method parameters

–named params are actually put in a map parameter

–plus optional parens & semis

• take 1.pill, of: Chloroquinine, after: 6.hours

• Corresponds to a method signature like:

–def take(Map m, MedicineQuantity mq)

32

Page 37: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

BigDecimal by default

• Main reason why financial institutions often decide to use Groovy for their business rules!

–Although these days rounding issues are overrated!

• Java vs Groovy for a simple interpolation equation

• BigDecimal uMinusv = c.subtract(a); BigDecimal vMinusl = b.subtract(c); BigDecimal uMinusl = a.subtract(b); return e.multiply(uMinusv) .add(d.multiply(vMinusl)) .divide(uMinusl, 10, BigDecimal.ROUND_HALF_UP);

• (d * (b - c) + e * (c - a)) / (a - b)

33

Page 38: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• When closures are last, they can be put “out” of the parentheses surrounding parameters

•unless (account.balance > 100.euros, { account.debit 100.euros })

•unless (account.balance > 100.euros) { account.debit 100.euros}

• Signature def unless(boolean b, Closure c)

34

Custom control structuresThanks to closures

Page 39: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Operator overloading

•Currency amounts–15.euros + 10.dollars

•Distance handling–10.kilometers - 10.meters

•Workflow, concurrency

–taskA | taskB & taskC

•Credit an account–account << 10.dollarsaccount += 10.dollarsaccount.credit 10.dollars

a + b a.plus(b)

a - b a.minus(b)

a * b a.multiply(b)

a / b a.divide(b)

a % b a.modulo(b)

a ** b a.power(b)

a | b a.or(b)

a & b a.and(b)

a ^ b a.xor(b)

a[b] a.getAt(b)

a << b a.leftShift(b)

a >> b a.rightShift(b)

+a a.positive()

-a a.negative()

~a a.bitwiseNegate()

35

Page 40: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Groovy’s dynamic heart:

The MOP!MetaObject Protocol

Page 41: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Groovy’s MOP

• All the accesses to methods, properties, constructors, operators, etc. can be intercepted thanks to the MOP

• While Java’s behavior is hard-wired at compile-time in the class

• Groovy’s runtime behavior is adaptable at runtime through the metaclass.

• Different hooks for changing the runtime behavior

–GroovyObject, custom MetaClass implementation, categories, ExpandoMetaClass

37

Page 42: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

GroovyObject

• All instances of classes created in Groovy implement the GroovyObject interface:

–getProperty(String name)

–setProperty(String name, Object value)

–invokeMethod(String name, Object[] params)

–getMetaClass()

–setMetaClass(MetaClass mc)

• A GO can have “pretended” methods and properties

38

Page 43: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

MetaClass

• The core of Groovy’s MOP system

–invokeConstructor()

–invokeMethod() and invokeStaticMethod()

–invokeMissingMethod()

–getProperty() and setProperty()

–getAttribute() and setAttribute()

–respondsTo() and hasProperty()

• MetaClasses can change the behavior of existing third-party classes — even from the JDK

39

Page 44: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

ExpandoMetaClass

• A DSL for MetaClasses!

• MoneyAmount.metaClass.constructor = { ... }Number.metaClass.getDollars = { ... }Distance.metaClass.toMeters = { ... }Distance.metaClass.static.create = { ... }

• To avoid repetition of Type.metaClass, you can pass a closure to metaClass { ... }

• The delegate variable in closure represents the

current instance, and it the default parameter

40

Page 45: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

The Builder pattern

Page 46: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

The Groovy MarkupBuilder

•def mkp = new MarkupBuilder()mkp.html { head { title “Groovy in Action” } body { div(width: ‘100’) { p(class: ‘para) { span “Best book ever!” } } }}

42

Page 47: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

A builder for HR

• softskills { ideas { capture 2 formulate 3 } ...}knowhow { languages { java 4 groovy 5 } ...}

43

Page 48: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

A builder for HR

• softskills { ideas { capture 2 formulate 3 } ...}knowhow { languages { java 4 groovy 5 } ...}

43

Page 49: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Builders

• Builders are...

–a mechanism for creating any tree-structered graph

–the realization of the GoF builder pattern at the syntax level in Groovy

–simply a clever use of chained method invocation, closures, parentheses omission, and use of the GroovyObject methods

• Existing builders

–XML, Object graph, Swing, Ant, JMX, and more...

44

Page 50: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

The clever trick

• GroovyObject#invokeMethod() is used to catch all non-existing method calls in the context of the builder

• The nesting of closures visually shows the level of nesting / depth in the tree

• builder.m1(attr1:1, attr2:2, { builder.m2(...,

{...}) } becomes equivalent to

builder.m1(attr1:1, attr2:2) { m2(...) {...} }

thanks to parens omission

45

Page 51: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Adding properties to numbers

• Three possible approaches

–create a Category

•a category is a kind of decorator for default MCs

–create a custom MetaClass

•a full-blown MC class to implement and to set on the

POGO instance

–use ExpandoMetaClass

•friendlier DSL approach but with a catch

46

Page 52: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

With a Category

• class DistanceCategory { static Distance getMeters(Integer self) { new Distance(self, Unit.METERS) }}

use(DistanceCategory) { 100.meters}

• Interesting scope: thread-bound & lexical

• But doesn’t work across the hierarchy of classes

–ie. subclasses won’t benefit from the new property

47

Page 53: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

With an ExpandoMetaClass

• Number.metaClass.getMeters = {-> new Distance(delegate, Unit.METERS) }

100.meters

• Works for the class hierarchy for POJOs, and a flag exists to make it work for POGOs too

• But the catch is it’s really a global change, so beware EMC enhancements collisions

48

Page 54: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Compile-time metaprogramming

• Groovy 1.6 introduced AST Transformations

• Compile-time == No runtime performance penalty!

Transformation

49

Page 55: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

AST Transformations

• Two kinds of transformations

–Global transformations

•applicable to all compilation units

–Local transformations

•applicable to marked program elements

•using specific marker annotations

50

Page 56: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Example #1: @Singleton

• Let’s revisit this evil (anti-)pattern! public class Evil { public static final Evil instance = new Evil (); private Evil () {} Evil getInstance() { return instance; } }

• In Groovy! @Singleton class Evil {}

• Also a “lazy” version! @Singleton(lazy = true) class Evil {}

51

Page 57: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• You can delegate to fields of your classes

–class Employee { def doTheWork() { “done” }}class Manager { @Delegate Employee slave = new Employee()}def god = new Manager()assert god.doTheWork() == “done”

• Damn manager who will get all the praise...

52

Example #2: @DelegateNot just for managers!

Page 58: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Global transformations

• Implement ASTTransformation

• Annotate the transfo specifying a compilation phase

• @GroovyASTTransformation(phase=CompilePhase.CONVERSION)public class MyTransformation implements ASTTransformation { public void visit(ASTNode[] nodes, SourceUnit unit) { ... }}

• For discovery, create the file META-INF/services/org.codehaus.groovy.transform.ASTTransformation

• Add the fully qualified name of the class in that file

53

Page 59: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Local transformations

• Same approach as Globale transformations

• But you don’t need the META-INF file

• Instead create an annotation to specify on which element the transformation should apply

• @Retention(RetentionPolicy.SOURCE)@Target([ElementType.METHOD])@GroovyASTTransformationClass( ["fqn.MyTransformation"])public @interface WithLogging {...}

54

Page 60: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Example: the Spock framework

• Changing the semantics of the original code

• But keeping a valid Groovy syntax

• @Speckclass HelloSpock { def "can you figure out what I'm up to?"() { expect: name.size() == size

where: name << ["Kirk", "Spock", "Scotty"] size << [4, 5, 6] }}

55

Page 61: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• The context and the usual issues we face

• Some real-life examples of Domain-Specific Languages

• Groovy’s DSL capabilities

• Integrating a DSL in your application

• Considerations to remember when designing your own DSL

Agenda

56

Page 62: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• Java 6’s javax.script.* APIs (aka JSR-223)

• Spring’s language namespace

• Groovy’s own mechanisms

• But a key idea is to externalize those DSL programs

–DSL programs can have their own lifecycle

–no need to redeploy an application because of a rule change

–business people won’t see the technical code

57

Various integration mechanisms

Page 63: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Java 6’s javax.script.* API

• Groovy 1.6 provides its own implementation of the javax.script.* API

• ScriptEngineManager mgr = new ScriptEngineManager();ScriptEngine engine = mgr.getEngineByName(“Groovy”);

String result = (String)engine.eval(“2+3”);

58

Page 64: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Spring’s lang namespace

• POGOs (Plain Old Groovy Objects) can be pre-compiled as any POJO and used interchangeably with POJOs in a Spring application

• But Groovy scripts & classes can be loaded at runtime through the <lang:groovy/> namespace and tag

• Reloadable on change

• Customizable through a custom MetaClass

• <lang:groovy id="events" script-source="classpath:dsl/eventsChart.groovy" customizer-ref="eventsMetaClass" />

59

Page 65: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Groovy’s own mechanisms

• Eval

–for evaluating simple expressions

• GroovyShell

–for more complex scripts and DSLs

• GroovyClassLoader

–the most powerful mechanism

60

Page 66: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Eval

• Simple mechanism to evaluate math-like formulas

• Eval.me ( ‘3*4’)Eval.x (1, ‘3*x + 4’)Eval.xy (1, 2, ‘x + y’)Eval.xyz(1, 2, 3, ‘x * y - z’)

61

Page 67: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• A Binding provides a context of execution

–can implement lazy evaluation if needed

• A base script class can be specified

• def binding = new Binding()binding.mass = 22.3binding.velocity = 10.6def shell = new GroovyShell(binding)shell.evaluate(“mass * velocity ** 2 / 2”)

62

GroovyShell

Page 68: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

GroovyClassLoader

• Most powerful mechanism

–could also visit or change the AST

–scripts & classes can be loaded from elsewhere

–more control on compilation

• GroovyClassLoader gcl = new GroovyClassLoader();Class clazz = gcl.parseClass( new File(“f.groovy”));GroovyObject instance = (GroovyObject)clazz.newInstance();instance.setMetaClass(customMC);

63

Page 69: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Externalize business rules

• Although Groovy DSLs can be embedded in normal Groovy classes, you should externalize them

• Store them elsewhere

–in a database, an XML file, etc.

• Benefits

–Business rules are not entangled in technical application code

–Business rules can have their own lifecycle, without requiring application redeployments

64

Page 70: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• The context and the usual issues we face

• Some real-life examples of Domain-Specific Languages

• Groovy’s DSL capabilities

• Integrating a DSL in your application

• Considerations to remember when designing your own DSL

Agenda

65

Page 71: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Start small, with key concepts

Beware overengineering!

Page 72: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Grow your language progressively

Page 73: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Get your hands dirty

Play with the end-users

Page 74: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Let your DSL fly, it’s not yours, it’s theirs!

Page 75: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Tight feedback loop

Iterative process

Page 76: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Stay humble.

You can’t get it right the first time.

Don’t design alone at your deskInvolve the end users from the start

Page 77: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Playing it safein a sandbox

Page 78: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Various levels of sandboxing

• Groovy supports the usual Java Security Managers

• Use metaprogramming tricks to prevent calling / instantiating certain classes

• Create a special GroovyClassLoader AST code visitor to filter only the nodes of the AST you want to keep

–ArithmeticShell in Groovy’s samples

73

Page 79: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Test, test, test!

• Don’t just test for nominal cases

–Explicitly test for errors!

• Ensure end-users get meaningful error messages

74

Page 80: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• Summary

• Questions & Answers

Agenda

75

Page 81: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Summary

• Groovy’s a great fit for Domain-Specific Languages

–Malleable & flexible syntax

–Full object-orientation

• Metaprogramming capabilities

–Runtime metaprogramming

–Compile-time metaprogramming

• Groovy’s very often used for mission-critical DSLs

76

Page 82: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

I kan haz my cheezburgr naw?Or do ya reely haz keshtionz?

?

Page 83: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

Appendix

78

Page 84: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• http://www.flickr.com/photos/wheatfields/420088151/sizes/l/

• http://www.flickr.com/photos/therefromhere/518053737/sizes/l/

• http://www.flickr.com/photos/romainguy/230416692/sizes/l/

• http://www.flickr.com/photos/addictive_picasso/2874279971/sizes/l/

• http://www.flickr.com/photos/huangjiahui/3127634297/sizes/l/

• http://www.flickr.com/photos/25831000@N08/3064515804/sizes/o/

• http://www.flickr.com/photos/lanier67/3147696168/sizes/l/

• http://www.flickr.com/photos/ktb/4916063/sizes/o/

• http://www.flickr.com/photos/nathonline/918128338/sizes/l/

• http://www.flickr.com/photos/kevinsteele/39300193/sizes/l/

• http://commons.wikimedia.org/wiki/File:Brueghel-tower-of-babel.jpg

• http://commons.wikimedia.org/wiki/File:Platypus.jpg

• http://www.flickr.com/photos/joaomoura/2317171808/sizes/l/

• http://www.flickr.com/photos/wiccked/132687067/

• http://www.flickr.com/photos/xcbiker/386876546/sizes/l/

• http://www.flickr.com/photos/pietel/152403711/sizes/o/

79

Page 85: GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

• http://www.flickr.com/photos/forezt/192554677/sizes/o/

• http://keremkosaner.files.wordpress.com/2008/04/

softwaredevelopment.gif

• http://www.jouy.inra.fr

• http://www.flickr.com/photos/ejpphoto/408101818/sizes/o/

• http://www.flickr.com/photos/solaro/2127576608/sizes/l/

• http://www.flickr.com/photos/biggreymare/2846899405/sizes/l/

• http://www.flickr.com/photos/timsamoff/252370986/sizes/l/

• http://www.flickr.com/photos/29738009@N08/2975466425/sizes/l/

• http://www.flickr.com/photos/howie_berlin/180121635/sizes/o/

• http://www.flickr.com/photos/yogi/1281980605/sizes/l/

• http://www.flickr.com/photos/dorseygraphics/1336468896/sizes/l/

80