001: package org.drools.brms.server.builder;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.io.InputStream;
020: import java.util.Map;
021:
022: import junit.framework.TestCase;
023:
024: import org.acme.insurance.Driver;
025: import org.acme.insurance.Policy;
026: import org.drools.RuleBase;
027: import org.drools.RuleBaseFactory;
028: import org.drools.WorkingMemory;
029: import org.drools.brms.client.common.AssetFormats;
030: import org.drools.brms.client.modeldriven.SuggestionCompletionEngine;
031: import org.drools.brms.client.modeldriven.brl.ActionFieldValue;
032: import org.drools.brms.client.modeldriven.brl.ActionSetField;
033: import org.drools.brms.client.modeldriven.brl.DSLSentence;
034: import org.drools.brms.client.modeldriven.brl.FactPattern;
035: import org.drools.brms.client.modeldriven.brl.RuleModel;
036: import org.drools.brms.server.selector.AssetSelector;
037: import org.drools.brms.server.selector.SelectorManager;
038: import org.drools.brms.server.util.BRXMLPersistence;
039: import org.drools.brms.server.util.TestEnvironmentSessionHelper;
040: import org.drools.repository.AssetItem;
041: import org.drools.repository.PackageItem;
042: import org.drools.repository.RulesRepository;
043: import org.drools.rule.Package;
044: import org.drools.ruleflow.core.RuleFlowProcess;
045:
046: /**
047: * This will unit test package assembly into a binary.
048: * @author Michael Neale
049: */
050: public class ContentPackageAssemblerTest extends TestCase {
051:
052: /**
053: * Test package configuration errors,
054: * including header, functions, DSL files.
055: */
056: public void testPackageConfigWithErrors() throws Exception {
057: //test the config, no rule assets yet
058: RulesRepository repo = getRepo();
059: PackageItem pkg = repo.createPackage(
060: "testBuilderPackageConfig", "x");
061: pkg.updateHeader("import java.util.List");
062: AssetItem func = pkg.addAsset("func1", "a function");
063: func.updateFormat(AssetFormats.FUNCTION);
064: func
065: .updateContent("function void doSomething() { \n System.err.println(List.class.toString()); }");
066: func.checkin("yeah");
067:
068: func = pkg.addAsset("func2", "q");
069: func.updateFormat(AssetFormats.FUNCTION);
070: func
071: .updateContent("function void foo() { \nSystem.err.println(42); \n}");
072: func.checkin("");
073:
074: AssetItem ass = pkg.addAsset("dsl", "m");
075: ass.updateFormat(AssetFormats.DSL);
076: ass.updateContent("[when]Foo bar=String()");
077: ass.checkin("");
078: repo.save();
079:
080: //now lets light it up
081: ContentPackageAssembler assembler = new ContentPackageAssembler(
082: pkg);
083: assertFalse(assembler.hasErrors());
084: Package bin = assembler.getBinaryPackage();
085: assertNotNull(bin);
086: assertEquals("testBuilderPackageConfig", bin.getName());
087: assertEquals(2, bin.getFunctions().size());
088:
089: assertTrue(bin.isValid());
090: assertEquals(1, assembler.builder.getDSLMappingFiles().size());
091:
092: pkg.updateHeader("koo koo ca choo");
093: assembler = new ContentPackageAssembler(pkg);
094: assertTrue(assembler.hasErrors());
095: assertTrue(assembler.isPackageConfigurationInError());
096:
097: pkg.updateHeader("import java.util.Date");
098: assembler = new ContentPackageAssembler(pkg);
099: assertTrue(assembler.hasErrors());
100: assertTrue(assembler.getErrors().get(0).itemInError instanceof AssetItem);
101:
102: assertEquals("func1", assembler.getErrors().get(0).itemInError
103: .getName());
104: try {
105: assembler.getBinaryPackage();
106: fail("should not work as is in error.");
107: } catch (IllegalStateException e) {
108: assertNotNull(e.getMessage());
109: }
110:
111: //fix it up
112: pkg.updateHeader("import java.util.List");
113: assembler = new ContentPackageAssembler(pkg);
114: assertFalse(assembler.hasErrors());
115:
116: //now break a DSL and check the error
117: ass.updateContent("rubbish");
118: ass.checkin("");
119: assembler = new ContentPackageAssembler(pkg);
120: assertTrue(assembler.hasErrors());
121: assertTrue(assembler.getErrors().get(0).itemInError.getName()
122: .equals(ass.getName()));
123: assertNotEmpty(assembler.getErrors().get(0).errorReport);
124: assertFalse(assembler.isPackageConfigurationInError());
125:
126: //now fix it up
127: ass.updateContent("[when]foo=String()");
128: ass.checkin("");
129: assembler = new ContentPackageAssembler(pkg);
130: assertFalse(assembler.hasErrors());
131:
132: //break a func, and check for error
133: func.updateContent("goo");
134: func.checkin("");
135: assembler = new ContentPackageAssembler(pkg);
136: assertTrue(assembler.hasErrors());
137: assertFalse(assembler.isPackageConfigurationInError());
138: assertTrue(assembler.getErrors().get(0).itemInError.getName()
139: .equals(func.getName()));
140: assertNotEmpty(assembler.getErrors().get(0).errorReport);
141: }
142:
143: public void testPackageWithRuleflow() throws Exception {
144: RulesRepository repo = getRepo();
145:
146: PackageItem pkg = repo.createPackage("testPackageWithRuleFlow",
147: "");
148: AssetItem model = pkg.addAsset("model", "qed");
149: model.updateFormat(AssetFormats.MODEL);
150:
151: model.updateBinaryContentAttachment(this .getClass()
152: .getResourceAsStream("/billasurf.jar"));
153: model.checkin("");
154:
155: pkg
156: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
157:
158: AssetItem rule1 = pkg.addAsset("rule_1", "");
159: rule1.updateFormat(AssetFormats.DRL);
160: rule1
161: .updateContent("rule 'rule1' \n when Board() \n then customer.setAge(42); \n end");
162: rule1.checkin("");
163:
164: AssetItem ruleFlow = pkg.addAsset("ruleFlow", "");
165: ruleFlow.updateFormat(AssetFormats.RULE_FLOW_RF);
166:
167: ruleFlow.updateBinaryContentAttachment(this .getClass()
168: .getResourceAsStream("/ruleflow.rf"));
169: ruleFlow.checkin("");
170:
171: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
172: assertFalse(asm.hasErrors());
173: Map flows = asm.getBinaryPackage().getRuleFlows();
174: assertNotNull(flows);
175:
176: assertEquals(1, flows.size());
177: Object flow = flows.values().iterator().next();
178: assertNotNull(flow);
179: assertTrue(flow instanceof RuleFlowProcess);
180:
181: }
182:
183: public void testSimplePackageBuildNoErrors() throws Exception {
184: RulesRepository repo = getRepo();
185:
186: PackageItem pkg = repo.createPackage(
187: "testSimplePackageBuildNoErrors", "");
188: AssetItem model = pkg.addAsset("model", "qed");
189: model.updateFormat(AssetFormats.MODEL);
190:
191: model.updateBinaryContentAttachment(this .getClass()
192: .getResourceAsStream("/billasurf.jar"));
193: model.checkin("");
194:
195: pkg
196: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
197:
198: AssetItem rule1 = pkg.addAsset("rule_1", "");
199: rule1.updateFormat(AssetFormats.DRL);
200: rule1
201: .updateContent("rule 'rule1' \n when Board() \n then customer.setAge(42); \n end");
202: rule1.checkin("");
203:
204: AssetItem rule2 = pkg.addAsset("rule2", "");
205: rule2.updateFormat(AssetFormats.DRL);
206: rule2
207: .updateContent("agenda-group 'q' \n when \n Board() \n then \n System.err.println(42);");
208: rule2.checkin("");
209:
210: AssetItem rule3 = pkg.addAsset("A file", "");
211: rule3.updateFormat(AssetFormats.DRL);
212: rule3
213: .updateContent("package testSimplePackageBuildNoErrors\n rule 'rule3' \n when \n then \n customer.setAge(43); \n end \n"
214: + "rule 'rule4' \n when \n then \n System.err.println(44); \n end");
215: rule3.checkin("");
216:
217: repo.save();
218:
219: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
220: assertFalse(asm.hasErrors());
221: assertNotNull(asm.getBinaryPackage());
222: Package bin = asm.getBinaryPackage();
223: assertEquals(pkg.getName(), bin.getName());
224: assertTrue(bin.isValid());
225:
226: assertEquals(4, bin.getRules().length);
227:
228: //now create a snapshot
229: repo.createPackageSnapshot(pkg.getName(), "SNAP_1");
230:
231: //and screw up the the non snapshot one
232: pkg.updateHeader("koo koo ca choo");
233: asm = new ContentPackageAssembler(pkg);
234: assertTrue(asm.hasErrors());
235:
236: //check the snapshot is kosher
237: pkg = repo.loadPackageSnapshot(pkg.getName(), "SNAP_1");
238: asm = new ContentPackageAssembler(pkg);
239: assertFalse(asm.hasErrors());
240:
241: }
242:
243: public void testIgnoreArchivedItems() throws Exception {
244: RulesRepository repo = getRepo();
245:
246: PackageItem pkg = repo.createPackage("testIgnoreArchivedItems",
247: "");
248: AssetItem model = pkg.addAsset("model", "qed");
249: model.updateFormat(AssetFormats.MODEL);
250:
251: model.updateBinaryContentAttachment(this .getClass()
252: .getResourceAsStream("/billasurf.jar"));
253: model.checkin("");
254:
255: pkg
256: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
257:
258: AssetItem rule1 = pkg.addAsset("rule_1", "");
259: rule1.updateFormat(AssetFormats.DRL);
260: rule1
261: .updateContent("rule 'rule1' \n when Board() \n then customer.setAge(42); \n end");
262: rule1.checkin("");
263:
264: AssetItem rule2 = pkg.addAsset("rule2", "");
265: rule2.updateFormat(AssetFormats.DRL);
266: rule2
267: .updateContent("agenda-group 'q' \n when \n Boardx() \n then \n System.err.println(42);");
268: rule2.checkin("");
269:
270: repo.save();
271:
272: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
273: assertTrue(asm.hasErrors());
274:
275: rule2.archiveItem(true);
276: rule2.checkin("");
277:
278: assertTrue(rule2.isArchived());
279: asm = new ContentPackageAssembler(pkg);
280: assertFalse(asm.hasErrors());
281:
282: }
283:
284: /**
285: * This this case we will test errors that occur in rule assets,
286: * not in functions or package header.
287: */
288: public void testErrorsInRuleAsset() throws Exception {
289:
290: RulesRepository repo = getRepo();
291:
292: //first, setup the package correctly:
293: PackageItem pkg = repo.createPackage("testErrorsInRuleAsset",
294: "");
295: AssetItem model = pkg.addAsset("model", "qed");
296: model.updateFormat(AssetFormats.MODEL);
297: model.updateBinaryContentAttachment(this .getClass()
298: .getResourceAsStream("/billasurf.jar"));
299: model.checkin("");
300: pkg
301: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
302: repo.save();
303:
304: AssetItem goodRule = pkg.addAsset("goodRule", "");
305: goodRule.updateFormat(AssetFormats.DRL);
306: goodRule
307: .updateContent("rule 'yeah' \n when \n Board() \n then \n System.out.println(42); end");
308: goodRule.checkin("");
309:
310: AssetItem badRule = pkg.addAsset("badRule", "xxx");
311: badRule.updateFormat(AssetFormats.DRL);
312: badRule.updateContent("if something then another");
313: badRule.checkin("");
314:
315: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
316: assertTrue(asm.hasErrors());
317: assertFalse(asm.isPackageConfigurationInError());
318:
319: for (ContentAssemblyError err : asm.getErrors()) {
320: assertTrue(err.itemInError.getName().equals(
321: badRule.getName()));
322: assertNotEmpty(err.errorReport);
323: }
324:
325: }
326:
327: /**
328: * This time, we mix up stuff a bit
329: *
330: */
331: public void testRuleAndDSLAndFunction() throws Exception {
332: RulesRepository repo = getRepo();
333:
334: //first, setup the package correctly:
335: PackageItem pkg = repo.createPackage(
336: "testRuleAndDSLAndFunction", "");
337: AssetItem model = pkg.addAsset("model", "qed");
338: model.updateFormat(AssetFormats.MODEL);
339: model.updateBinaryContentAttachment(this .getClass()
340: .getResourceAsStream("/billasurf.jar"));
341: model.checkin("");
342: pkg
343: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
344: repo.save();
345:
346: AssetItem func = pkg.addAsset("func", "");
347: func.updateFormat(AssetFormats.FUNCTION);
348: func
349: .updateContent("function void foo() { System.out.println(42); }");
350: func.checkin("");
351:
352: AssetItem dsl = pkg.addAsset("myDSL", "");
353: dsl.updateFormat(AssetFormats.DSL);
354: dsl.updateContent("[then]call a func=foo();");
355: dsl.checkin("");
356:
357: AssetItem dsl2 = pkg.addAsset("myDSL2", "");
358: dsl2.updateFormat(AssetFormats.DSL);
359: dsl2.updateContent("[when]There is a board=Board()");
360: dsl2.checkin("");
361:
362: AssetItem rule = pkg.addAsset("myRule", "");
363: rule.updateFormat(AssetFormats.DSL_TEMPLATE_RULE);
364: rule
365: .updateContent("when \n There is a board \n then \n call a func");
366: rule.checkin("");
367:
368: AssetItem rule2 = pkg.addAsset("myRule2", "");
369: rule2.updateFormat(AssetFormats.DSL_TEMPLATE_RULE);
370: rule2
371: .updateContent("package testRuleAndDSLAndFunction \n rule 'myRule2222' \n when \n There is a board \n then \n call a func \nend");
372: rule2.checkin("");
373:
374: AssetItem rule3 = pkg.addAsset("myRule3", "");
375: rule3.updateFormat(AssetFormats.DRL);
376: rule3
377: .updateContent("package testRuleAndDSLAndFunction\n rule 'rule3' \n when \n Board() \n then \n System.err.println(42); end");
378: rule3.checkin("");
379:
380: repo.save();
381:
382: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
383: assertFalse(asm.hasErrors());
384: Package bin = asm.getBinaryPackage();
385: assertNotNull(bin);
386: assertEquals(3, bin.getRules().length);
387: assertEquals(1, bin.getFunctions().size());
388:
389: }
390:
391: public void testShowSource() throws Exception {
392: RulesRepository repo = getRepo();
393:
394: //first, setup the package correctly:
395: PackageItem pkg = repo.createPackage("testShowSource", "");
396:
397: pkg
398: .updateHeader("import com.billasurf.Board\n global com.billasurf.Person customer");
399: repo.save();
400:
401: AssetItem func = pkg.addAsset("func", "");
402: func.updateFormat(AssetFormats.FUNCTION);
403: func
404: .updateContent("function void foo() { System.out.println(42); }");
405: func.checkin("");
406:
407: AssetItem dsl = pkg.addAsset("myDSL", "");
408: dsl.updateFormat(AssetFormats.DSL);
409: dsl
410: .updateContent("[then]call a func=foo();\n[when]foo=FooBarBaz()");
411: dsl.checkin("");
412:
413: AssetItem rule = pkg.addAsset("rule1", "");
414: rule.updateFormat(AssetFormats.DRL);
415: rule.updateContent("rule 'foo' when Goo() then end");
416: rule.checkin("");
417:
418: AssetItem rule2 = pkg.addAsset("rule2", "");
419: rule2.updateFormat(AssetFormats.DSL_TEMPLATE_RULE);
420: rule2.updateContent("when \n foo \n then \n call a func");
421: rule2.checkin("");
422:
423: ContentPackageAssembler asm = new ContentPackageAssembler(pkg,
424: false, null);
425: String drl = asm.getDRL();
426:
427: assertNotNull(drl);
428:
429: assertContains(
430: "import com.billasurf.Board\n global com.billasurf.Person customer",
431: drl);
432: assertContains("package testShowSource", drl);
433: assertContains(
434: "function void foo() { System.out.println(42); }", drl);
435: assertContains("foo();", drl);
436: assertContains("FooBarBaz()", drl);
437: assertContains("rule 'foo' when Goo() then end", drl);
438:
439: }
440:
441: public void testXLSDecisionTable() throws Exception {
442:
443: RulesRepository repo = getRepo();
444:
445: //first, setup the package correctly:
446: PackageItem pkg = repo
447: .createPackage("testXLSDecisionTable", "");
448:
449: pkg
450: .updateHeader("import org.acme.insurance.Policy\n import org.acme.insurance.Driver");
451: repo.save();
452:
453: InputStream xls = this .getClass().getResourceAsStream(
454: "/SampleDecisionTable.xls");
455: assertNotNull(xls);
456:
457: AssetItem asset = pkg.addAsset("MyDT", "");
458: asset.updateFormat(AssetFormats.DECISION_SPREADSHEET_XLS);
459: asset.updateBinaryContentAttachment(xls);
460: asset.checkin("");
461:
462: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
463: if (asm.hasErrors()) {
464: System.err.println(asm.getErrors().get(0).errorReport);
465: System.err.println(asm.getDRL());
466: }
467: assertFalse(asm.hasErrors());
468:
469: String drl = asm.getDRL();
470:
471: assertContains("policy: Policy", drl);
472:
473: Package bin = asm.getBinaryPackage();
474:
475: RuleBase rb = RuleBaseFactory.newRuleBase();
476: rb.addPackage(bin);
477:
478: WorkingMemory wm = rb.newStatefulSession();
479:
480: //now create some test data
481: Driver driver = new Driver();
482: Policy policy = new Policy();
483:
484: wm.insert(driver);
485: wm.insert(policy);
486:
487: wm.fireAllRules();
488:
489: assertEquals(120, policy.getBasePrice());
490:
491: asset.updateBinaryContentAttachment(this .getClass()
492: .getResourceAsStream(
493: "/SampleDecisionTable_WithError.xls"));
494: asset.checkin("");
495: asm = new ContentPackageAssembler(pkg);
496: assertTrue(asm.hasErrors());
497: assertEquals(asset.getName(),
498: asm.getErrors().get(0).itemInError.getName());
499: asm = new ContentPackageAssembler(pkg, false);
500: assertFalse(asm.hasErrors());
501: drl = asm.getDRL();
502: assertNotNull(drl);
503: assertContains("Driverx", drl);
504:
505: }
506:
507: public void testBRXMLWithDSLMixedIn() throws Exception {
508: RulesRepository repo = getRepo();
509:
510: //create our package
511: PackageItem pkg = repo.createPackage("testBRLWithDSLMixedIn",
512: "");
513: pkg.updateHeader("import org.drools.Person");
514: AssetItem rule1 = pkg.addAsset("rule2", "");
515: rule1.updateFormat(AssetFormats.BUSINESS_RULE);
516:
517: AssetItem dsl = pkg.addAsset("MyDSL", "");
518: dsl.updateFormat(AssetFormats.DSL);
519: dsl
520: .updateContent("[when]This is a sentence=Person()\n[then]say {hello}=System.err.println({hello});");
521: dsl.checkin("");
522:
523: RuleModel model = new RuleModel();
524: model.name = "rule2";
525: FactPattern pattern = new FactPattern("Person");
526: pattern.boundName = "p";
527: ActionSetField action = new ActionSetField("p");
528: ActionFieldValue value = new ActionFieldValue("age", "42",
529: SuggestionCompletionEngine.TYPE_NUMERIC);
530: action.addFieldValue(value);
531:
532: model.addLhsItem(pattern);
533: model.addRhsItem(action);
534:
535: DSLSentence dslCondition = new DSLSentence();
536: dslCondition.sentence = "This is a sentence";
537:
538: model.addLhsItem(dslCondition);
539:
540: DSLSentence dslAction = new DSLSentence();
541: dslAction.sentence = "say {42}";
542:
543: model.addRhsItem(dslAction);
544:
545: rule1.updateContent(BRXMLPersistence.getInstance().marshal(
546: model));
547: rule1.checkin("");
548: repo.save();
549:
550: //now add a rule with no DSL
551: model = new RuleModel();
552: model.name = "ruleNODSL";
553: pattern = new FactPattern("Person");
554: pattern.boundName = "p";
555: action = new ActionSetField("p");
556: value = new ActionFieldValue("age", "42",
557: SuggestionCompletionEngine.TYPE_NUMERIC);
558: action.addFieldValue(value);
559:
560: model.addLhsItem(pattern);
561: model.addRhsItem(action);
562:
563: AssetItem ruleNODSL = pkg.addAsset("ruleNoDSL", "");
564: ruleNODSL.updateFormat(AssetFormats.BUSINESS_RULE);
565:
566: ruleNODSL.updateContent(BRXMLPersistence.getInstance().marshal(
567: model));
568: ruleNODSL.checkin("");
569:
570: pkg = repo.loadPackage("testBRLWithDSLMixedIn");
571: ContentPackageAssembler asm = new ContentPackageAssembler(pkg);
572: assertFalse(asm.hasErrors());
573: Package bpkg = asm.getBinaryPackage();
574: assertEquals(2, bpkg.getRules().length);
575:
576: }
577:
578: public void testCustomSelector() throws Exception {
579: RulesRepository repo = getRepo();
580:
581: //create our package
582: PackageItem pkg = repo.createPackage("testCustomSelector", "");
583: pkg.updateHeader("import org.drools.Person");
584: AssetItem rule1 = pkg.addAsset("rule1", "");
585: rule1.updateFormat(AssetFormats.DRL);
586:
587: rule1
588: .updateContent("when \n Person() \n then \n System.out.println(\"yeah\");\n");
589: rule1.checkin("");
590:
591: AssetItem rule2 = pkg.addAsset("rule2", "");
592: rule2.updateFormat(AssetFormats.DRL);
593:
594: rule2
595: .updateContent("when \n Person() \n then \n System.out.println(\"yeah\");\n");
596: rule2.checkin("");
597:
598: SelectorManager sm = SelectorManager.getInstance();
599: sm.selectors.put("testSelect", new AssetSelector() {
600: public boolean isAssetAllowed(AssetItem asset) {
601: return asset.getName().equals("rule2");
602: }
603: });
604:
605: ContentPackageAssembler asm = new ContentPackageAssembler(pkg,
606: "testSelect");
607:
608: Package pk = asm.getBinaryPackage();
609: assertEquals(1, pk.getRules().length);
610: assertEquals("rule2", pk.getRules()[0].getName());
611:
612: asm = new ContentPackageAssembler(pkg, null);
613: pk = asm.getBinaryPackage();
614: assertEquals(2, pk.getRules().length);
615:
616: asm = new ContentPackageAssembler(pkg, "nothing valid");
617: assertTrue(asm.hasErrors());
618: assertEquals(1, asm.getErrors().size());
619: assertEquals(pkg, asm.getErrors().get(0).itemInError);
620:
621: asm = new ContentPackageAssembler(pkg, "");
622: pk = asm.getBinaryPackage();
623: assertEquals(2, pk.getRules().length);
624:
625: }
626:
627: private void assertContains(String sub, String text) {
628: if (text.indexOf(sub) == -1) {
629: fail("the text: '" + sub + "' was not found.");
630: }
631:
632: }
633:
634: private void assertNotEmpty(String s) {
635: if (s == null)
636: fail("should not be null");
637: if (s.trim().equals(""))
638: fail("should not be empty string");
639: }
640:
641: private RulesRepository getRepo() throws Exception {
642: return new RulesRepository(TestEnvironmentSessionHelper
643: .getSession());
644: }
645:
646: }
|