JBoss Drools

3,059 views
2,997 views

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
3,059
On SlideShare
0
From Embeds
0
Number of Embeds
1,504
Actions
Shares
0
Downloads
41
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

JBoss Drools

  1. 1. JBoss Drools by Victor Polischuk
  2. 2. Business Problem Model and Requirements
  3. 3. Party PartyDefid partyId startTime stopTime Partici publicCode PartyRole pantpartyIdroleId RolestartTime idstopTime publicCode
  4. 4. Order ProductDefid productIdtime Order startTimeinParty stopTimeinRole publicCodeoutPartyoutRole OrderIteminPartyId orderIdinRoleId quantityoutPartyId productId ProductoutRoleId product id
  5. 5. 1)Start-stop intervals must not overlap.2)Entity exists only within start-stop interval.3)Order must have valid and existent in party.4)Order must have valid and existent out party.5)Order must have valid and existent in role.6)Order must have valid and existent out role.7)Order items must have valid and existent product.8)Order in and out parties must not be the same.9)Order roles must be: (buyer, seller), (buyer, repairer), (repairer, seller), (seller, buyer).
  6. 6. Frontal Assault Conditions
  7. 7. public void placeOrder(Order order) {}
  8. 8. public void placeOrder(Order order) { PartyDef ip = dao.findPartyBy(...);}
  9. 9. public void placeOrder(Order order) { PartyDef ip = dao.findPartyBy(...); if (ip == null) { throw new Rule3Exception(...); }}
  10. 10. public void placeOrder(Order order) { PartyDef ip = dao.findPartyBy(...); if (ip == null) { throw new Rule3Exception(...); } PartyDef op = dao.findPartyBy(...); if (op == null) { throw new Rule4Exception(...); }}
  11. 11. public void placeOrder(Order order) { PartyDef ip = dao.findPartyBy(...); if (ip == null) { throw new Rule3Exception(...); } PartyDef op = dao.findPartyBy(...); if (op == null) { throw new Rule4Exception(...); } PartyRole ir = dao.findRoleBy(...); if (ir == null) { throw new Rule5Exception(...); }}
  12. 12. public void placeOrder(Order order) { PartyDef ip = dao.findPartyBy(...); assertRule3(ip); PartyDef op = dao.findPartyBy(...); assertRule4(op); PartyRole ir = dao.findRoleBy(...); assertRule5(ir);}
  13. 13. public void placeOrder(Order order) { PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getValidatedByRule5(...);}
  14. 14. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getValidatedByRule5(...); //???}
  15. 15. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); if (ip != null) { PartyRole ir = getValidatedByRule5(...); }}
  16. 16. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); if (ip != null) { PartyRole ir = getValidatedByRule5(...); } if (op != null) { PartyRole or = getValidatedByRule6(...); }}
  17. 17. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); if (ip != null) { PartyRole ir = getValidatedByRule5(...); } if (op != null) { PartyRole or = getValidatedByRule6(...); } if (ip != null && op != null && ip == op) { problems.add(...); // Rule #8 violation }}
  18. 18. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getSafeValidatedByRule5(...); PartyRole or = getSafeValidatedByRule6(...); validateRule8(...);}
  19. 19. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getSafeValidatedByRule5(...); PartyRole or = getSafeValidatedByRule6(...); validateRule8(...); if (ir != null && or != null && (...)) { problems.add(...); // Rule #9 violation }}
  20. 20. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getSafeValidatedByRule5(...); PartyRole or = getSafeValidatedByRule6(...); validateRule8(...); validateRule9(...); if (problems.isEmpty()) { order.inPartyId = ip.partyId; ... }}
  21. 21. public void placeOrder(Order order) { List<Problem> problems = …; PartyDef ip = getValidatedByRule3(...); PartyDef op = getValidatedByRule4(...); PartyRole ir = getSafeValidatedByRule5(...); PartyRole or = getSafeValidatedByRule6(...); validateRule8(...); validateRule9(...); fillOrderIdentifications(...); // TODO: Rule #7}
  22. 22. Clean the Mess Independent Rules
  23. 23. Rule RuleConfigContext RuleRunner Data
  24. 24. class RuleRunner...public <T> List<Problem> run(<T> data) { List<Problem> problems = …; Context context = …; RuleConfig config = …; while (config.hasNext(context)) { Rule rule = config.next(context); try { rule.execute(context, data); } catch (…) { problems.add(...); } }}
  25. 25. class RuleConfig...private final List<Rule> rules = …;public Rule next(Context context) { Rule rule = null; int i = context.getLatestRuleIndex(); while (i < rules.size() && rule == null) { If (rules.get(i).accepts(context)) { rule = rules.get(i); } i++; } context.setLatestRuleIndex(i); return rules.get(i);}
  26. 26. interface Rule<T> { boolean accepts(Context context); void execute(Context context, T data);}
  27. 27. class RuleN3 implements Rule<Order>public boolean accepts(Context ctx) { return true;}public void execute(Context ctx,Order data) { PartyDef ip = dao.findPartyBy(...); if (ip == null) { throw new Rule3Exception(...); } ctx.inPartyDef = ip; data.inPartyId = ip.partyId;}
  28. 28. class RuleN8 implements Rule<Order>public boolean accepts(Context ctx) { return ctx.inPartyDef != null && ctx.outPartyDef != null;}public void execute(Context ctx,Order data) { PartyDef ip = dao.findPartyBy(...); if (ctx.inPartyDef == ctx.outPartyDef) { throw new Rule8Exception(...); }}
  29. 29. One More Step Expert System
  30. 30. Rule RuleConfigContext RuleRunner Data
  31. 31. KnowledgeBas Rule eWorkingMemor RuleEngine y Facts
  32. 32. Simple Rule Engine Decision Block 3 1 Rule Decision Block 4 Rule Decision Block 5 Rule Decision Block 6 Rule Result
  33. 33. Expert SystemDecision Block 3 1 RuleDecision Block 4 RuleDecision Block 5 RuleDecision Block 6 Rule Result
  34. 34. Process Order Fix ErrorRouting ?Fixed End Processing ?
  35. 35. Rule 1 Fact 1Rule 2 Fact 2Rule 3 Fact 3 * * Repeats = ?Rule 4 Fact 4 ... ...Rule N Fact M
  36. 36. Rule 1 Fact 1Rule 2 Fact 2Rule 3 Fact 3 * * Repeats = ?Rule 4 Fact 4 ... ...Rule N Fact M Inefficient
  37. 37. To the Rescue Rete
  38. 38. Rete = Net Charles L. Forgy 19791974 1982 © Wikipedia.org
  39. 39. Root Node StartAll Facts Pass Through It
  40. 40. 1-input Nodes Alpha NetworkIntraElement Conditions
  41. 41. 2-input NodesBeta Network Two Element Comparison
  42. 42. Terminal Nodes Stop Rules Have a Full Match
  43. 43. Drools Finally
  44. 44. Drools Rule Patternrule "Rule example" // Rule unique name... // Rule attributes: grouping, priority, etc.when ... // Left hand side. Condition.then ... // Right hand side. Consequence.end
  45. 45. THE <consequenceIF <condition> N >
  46. 46. rule "Rule #3 violation"when $o : Order($id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)==null )then log.info("Rule #3 violated for the order: " + $id); insert(new Problem($id, 3));end
  47. 47. rule "Rule #3 fulfillment"when $o : Order(inPartyId==null, $id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)!=null )then log.info("Rule #3 fulfilled for the order: " + $id); modify($o) { setInPartyId( dao.findPartyBy($t, $pty).getPartyId() ) };end
  48. 48. class RuleN3 implements Rule<Order>public boolean accepts(Context ctx) { return true;}public void execute(Context ctx,Order data) { PartyDef ip = dao.findPartyBy(...); if (ip == null) { throw new Rule3Exception(...); } ctx.inPartyDef = ip; data.inPartyId = ip.partyId;}
  49. 49. If Java Is Simpler Why Use Drools?
  50. 50. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  51. 51. class RuleN7 implements Rule<Order>public boolean accepts(Context ctx) { return true;}public void execute(Context ctx,Order data) { for (OrderItem item : data.items) { ProductDef pd = dao.findProductBy(...); if (pd == null) { throw new Rule7Exception(...); //??? } }}
  52. 52. Java Solution #1 Complex Rule
  53. 53. class RuleN7_1 implements Rule<Order>public boolean accepts(Context ctx) { return true;}public void execute(Context ctx,Order data) { for (OrderItem item : data.items) { ProductDef pd = dao.findProductBy(...); if (pd == null) { ctx.addRule7Error(...); //OK } }}
  54. 54. Java Solution #2Complex Runner and Configuration
  55. 55. class RuleN7_2 implements Rule<OrderItem>public boolean accepts(Context ctx) { return true;}public void execute(Context ctx,OrderItem data) { ProductDef pd = dao.findProductBy(...); if (pd == null) { throw new Rule7Exception(...); //OK }}
  56. 56. Drools Again?
  57. 57. rule "Rule #3 violation"when $o : Order($id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)==null )then log.info("Rule #3 violated for the order: " + $id); insert(new Problem($id, 3));end
  58. 58. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  59. 59. Yet Another Validation Tool Or Not?
  60. 60. package com.victor.droolsdialect "mvel"import com.victor.drools.*; // optionalglobal GeneralDao dao;global org.slf4j.Logger log;declare Problem orderId : long @key ruleId : int @keyenddeclare OIProblem extends Problem product : String @keyend
  61. 61. Drools Class Declarationdeclare ExampleClass // Class name[extends ParentExampleClass] // Inheritance @stringMetadata("I am String") // Metadata @dateMetadata(01-Jan-1970) propertyName1 : ValidType, // Properties propertyName2 : AnotherValidType
  62. 62. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  63. 63. Drools Function Declarationfunction String hello(String name) { return "Hello "+name+"!";}
  64. 64. rule "Function usage in LHS"when eval( hello("Victor")!="Hello Victor!" )then int faults = ++alexeyFaults.count; log.fatal("Alexey, fix hello(..) function"); log.anger("Youd better be off"); log.info("Youve broken it " +faults+ " time(s)");end
  65. 65. rule "Function usage in RHS"when eval(true)then log.happiness(hello("Victor")); log.worship(hello("Master"));end
  66. 66. Drools Query Declarationquery "Orders with inParty" $o : Order(inParty != null)endquery "Orders with inRole equals to" (String role) $o : Order(inRole == role)end
  67. 67. Drools Templatestemplate headerfield1... // Template fieldstemplate “TemplateName”rule "ExampleRule_@{row.rowNumber}"... // Usage of declared fieldsendend template
  68. 68. Promotional Age Number of Policy type Discount % discount rules Bracket prior applying for claimsRewards for safe 18,24 0 COMPREHENSIVE 1 drivers 18,24 0 FIRE_THEFT 2 25,30 1 COMPREHENSIVE 5 25,30 2 COMPREHENSIVE 1 25,30 0 COMPREHENSIVE 20 © jboss.org
  69. 69. Pièce de Résistance DSL
  70. 70. rule "Rule #3 violation"when $o : Order($id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)==null )then log.info("Rule #3 violated for the order: " + $id); insert(new Problem($id, 3));end
  71. 71. rule "Rule #3 violation"when Every Order Without violations of rule 3 Which violates inParty constraintsthen Add the violation of the rule 3end
  72. 72. [when] Every Order = $o : Order($id:id, $t:time,$inPty:inParty)[when] Without violations of rule {ruleNumber} =not(Problem(orderId==$id, ruleId=={ruleNumber}))[when] Which violates inParty constraints =eval( dao.findPartyBy($t, $inPty) == null )[then] Add the violation of the rule {ruleNumber} =log.info("Rule #" + {ruleNumber} + " violated fororder: " + $id); insert(new Problem($id,{ruleNumber}));
  73. 73. rule "Rule #4 violation"when Every Order Without violations of rule 4 Which violates outParty constraintsthen Add the violation of the rule 4end
  74. 74. [when] Every Order = $o : Order($id:id, $t:time,$inPty:inParty)[when] Without violations of rule {ruleNumber} =not(Problem(orderId==$id, ruleId=={ruleNumber}))[when] Which violates inParty constraints =eval( dao.findPartyBy($t, $inPty) == null )[then] Add the violation of the rule {ruleNumber} =log.info("Rule #" + {ruleNumber} + " violated fororder: " + $id); insert(new Problem($id,{ruleNumber}));
  75. 75. [when] Every Order = $o : Order($id:id, $t:time,$inPty:inParty,$outPty:outParty)[when] Without violations of rule {ruleNumber} =not(Problem(orderId==$id, ruleId=={ruleNumber}))[when] Which violates inParty constraints =eval( dao.findPartyBy($t, $inPty) == null )[when] Which violates outParty constraints =eval( dao.findPartyBy($t, $outPty) == null )[then] Add the violation of the rule {ruleNumber} =log.info("Rule #" + {ruleNumber} + " violated fororder: " + $id); insert(new Problem($id,{ruleNumber}));
  76. 76. Seven?Lets have fun
  77. 77. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  78. 78. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  79. 79. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  80. 80. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  81. 81. rule "Rule #7 violation"when $o : Order(items!=null, $id:id, $t:time) $item : OrderItem($prd:product) from $o.items not( OIProblem(orderId==$id, ruleId==7, product==$prd) ) eval(dao.findProductBy($t, $prd)==null)then log.info("Rule #7 violated for order: " + $id + " and product: " + $prd); insert(new OIProblem($id, 7, $prd));end
  82. 82. rule "Rule #7 violation"when Every Order - which has items And every OrderItem Without violations of order item rule 7 Which violates product constraintsthen Add the violation of the rule 7 for the order itemend
  83. 83. [when] Every Order =[when] Without violations of rule {N} =[when] Which violates inParty constraints =[when] Which violates outParty constraints =[then] Add the violation of the rule {N} =
  84. 84. [when] Every Order =[when] - which has items = items != null[when] And every OrderItem =[when] Without violations of rule {N} =[when] Which violates inParty constraints =[when] Which violates outParty constraints =[when] Without violations of order item rule {N} =[when] Which violates product constraints =[then] Add the violation of the rule {N} for orderitem =[then] Add the violation of the rule {N} =
  85. 85. I Do Believe Am I Alone?
  86. 86. Meanwhile in the Real World... Requirements Change
  87. 87. 1)Start-stop intervals must not overlap.2)Entity exists only within start-stop interval.3)Order must have valid and existent in party.4)Order must have valid and existent out party.5)Order must have valid and existent in role.6)Order must have valid and existent out role.7)Order items must have valid and existent product.8)Order in and out parties CAN be the same. ONLY IF they have roles (seller, repairer).9)Order roles must be: (buyer, seller), (buyer, repairer), (repairer, seller), (seller, buyer). OR (seller, repairer) if they are the same.
  88. 88. rule "Rule #8 violation"when $o : Order( inPartyId != null, outPartyId != null, outPartyId == inPartyId, $id : id) not( Problem(orderId == $id, ruleId in (3, 4, 8)) )then log.info("Rule #8 violated for order: " + $id); insert(new Problem($id, 8));end
  89. 89. rule "Rule #8 violation"when $o : Order(!(inRole == "S" && outRole == "R"), inPartyId != null, outPartyId != null, outPartyId == inPartyId, $id : id) not( Problem(orderId == $id, ruleId in (3, 4, 8)) )then log.info("Rule #8 violated for order: " + $id); insert(new Problem($id, 8));end
  90. 90. rule "Rule #8 violation"when Every Order - with inPartyId - with outPartyId - where inPartyId equals outPartyId - and inRole not "S" and outRole not "R" Without violations of rules [3,4,8]then Add the violation of the rule 8end
  91. 91. rule "Rule #8 exceptional case"// salience 100when $o : Order(inRole == "S", outRole == "R", inPartyId != null, outPartyId != null, outPartyId == inPartyId, $id : id)then log.info("Rule #8 special case for order: " +$id); insertLogical(new SkipRule($id, 8));end
  92. 92. rule "Rule #8 violation"when $o : Order( inPartyId != null, outPartyId != null, outPartyId == inPartyId, $id : id) not( SkipRule(orderId == $id, ruleId == 8) ) not(Problem(orderId == $id, ruleId in (3, 4, 8)) )then log.info("Rule #8 violated for order: " + $id); insert(new Problem($id, 8));end
  93. 93. rule "Cleanup skipped rules"when $o : Order($id : id) SkipRule(orderId == $id, $rId : ruleId) $p : Problem(orderId == $id, ruleId == $rId)then log.info("Retract #" + $rId + " for order: " + $id); retract($p);end
  94. 94. Houston, weve got a problem Performance & Resources
  95. 95. Rete Adapters/EvalEntry Point JoinObject Type NotAlpha Terminal
  96. 96. Rete Entry Point Entry Point NodeScope 1 EntryPointNode#1Scope 2 EntryPointNode#2Scope 3 EntryPointNode#3
  97. 97. Entry Point NodeObject Type Object Type NodeOrder ObjectTypeNode#1PartyDef ObjectTypeNode#2Problem ObjectTypeNode#3OIProblem ObjectTypeNode#4
  98. 98. Object Type Node (Order) Fact SetOrder#112Order#113Order#114Order#115...
  99. 99. Object Type Node (Order) Literal Alpha NodeinParty == null AlphaNode#1inParty != null AlphaNode#2outParty == null AlphaNode#3outParty != null AlphaNode#4
  100. 100. Alpha Node Fact SetOrder#1Order#5Order#6...
  101. 101. Join Node Left RightOrder#1, SkipRule#5 Problem#1Order#5, SkipRule#15 Problem#5Order#6, SkipRule#11 Problem#6... ... Problem#11 Problem#12
  102. 102. Not Node Left RightOrder#1, OrderItem#5 Problem#1Order#1, OrderItem#1 Problem#5Order#1, OrderItem#3 Problem#6... ... Problem#11 Problem#12
  103. 103. Eval No Cache No Memory
  104. 104. rule "Rule #3 violation"when $o : Order($id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)==null )then log.info("Rule #3 violated for the order: " + $id); insert(new Problem($id, 3));end
  105. 105. rule "Rule #3 fulfillment"when $o : Order(inPartyId==null, $id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) eval( dao.findPartyBy($t, $pty)!=null )then log.info("Rule #3 fulfilled for the order: " + $id); modify($o) { setInPartyId( dao.findPartyBy($t, $pty).getPartyId() ) };end
  106. 106. Hard to Test Inefficient High Cost of Refactoring Impossible Exception HandlingCan Change State Between Invocations Usually Less Readable
  107. 107. Replace EVAL Prefetch Data
  108. 108. rule "Rule #3 violation"when $o : Order($id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) not( PartyDef(publicCode == $pty, startTime <= $t, stopTime > $t) )then log.info("Rule #3 violated for the order: " + $id); insert(new Problem($id, 3));end
  109. 109. rule "Rule #3 fulfillment"when $o : Order(inPartyId==null, $id:id, $t:time, $pty:inParty) not( Problem(orderId==$id, ruleId==3) ) $p : PartyDef(publicCode == $pty, startTime <= $t, stopTime > $t)then log.info("Rule #3 fulfilled for the order: " + $id); modify($o) { inPartyId = $p.getPartyId(); };end
  110. 110. rule "Prefetch inParty PartyDef"salience 100when Order(inParty!=null, $t:time, $pCode:inParty) not( PartyDef(publicCode == $pCode, startTime <= $t, stopTime > $t) )then PartyDef pd = dao.findPartyBy($t, $pCode); if (pd != null) { log.info("insert party: " + pd.getPartyId()); insert(pd); }end
  111. 111. rule "Prefetch inParty PartyDef"salience 100when Order(inParty!=null, $t:time, $pCode:inParty) not( PartyDef(publicCode == $pCode, startTime <= $t, stopTime > $t) )then PartyDef pd = dao.findPartyBy($t, $pCode); log.info("insert party (if exists): " + pd); insert(pd); // null wont be insertedend
  112. 112. Hard to Test Inefficient High Cost of Refactoring Impossible Exception HandlingCan Change State Between Invocations Usually Less Readable Several More Rules to Support
  113. 113. ??? http://docs.jboss.org/ http://akinator.com/

×