001/**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krms.framework;
017
018import static junit.framework.Assert.assertEquals;
019import static junit.framework.Assert.assertTrue;
020import static junit.framework.Assert.fail;
021
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.lang.StringUtils;
032import org.junit.Before;
033import org.junit.Test;
034import org.kuali.rice.krms.api.engine.Term;
035import org.kuali.rice.krms.api.engine.TermResolutionEngine;
036import org.kuali.rice.krms.api.engine.TermResolutionException;
037import org.kuali.rice.krms.api.engine.TermResolver;
038import org.kuali.rice.krms.framework.engine.TermResolutionEngineImpl;
039import org.springframework.util.CollectionUtils;
040
041
042public class TermResolutionEngineTest {
043        
044        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TermResolutionEngineTest.class);
045
046        private TermResolutionEngine termResolutionEngine = null;
047        
048        @Before
049        public void setUp() {
050                termResolutionEngine = new TermResolutionEngineImpl();
051        }
052        
053        @Test
054        public void testNoResolution() {
055                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
056                
057                // GIVENS:
058                testHelper.addGivens("A");
059
060                testHelper.logScenarioDescription();
061                
062                testHelper.assertSuccess("A");
063        }
064        
065        @Test
066        public void testSimpleResolution() {
067                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
068                
069                // GIVENS:
070                testHelper.addGivens("A");
071                
072                // RESOLVERS:
073                testHelper.addResolver("B", /* <-- */ "A");
074                
075                testHelper.logScenarioDescription();
076                
077                testHelper.assertSuccess("B");
078        }
079        
080        @Test
081        public void testTwoStepResolution() {
082                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
083                
084                // GIVENS:
085                testHelper.addGivens("A");
086                
087                // RESOLVERS:
088                testHelper.addResolver("B", /* <-- */ "A");
089                testHelper.addResolver("C", /* <-- */ "B");
090                
091                testHelper.logScenarioDescription();
092                
093                testHelper.assertSuccess("C");
094        }
095
096        @Test
097        public void testForkingResolution() {
098                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
099
100                // GIVENS:
101                testHelper.addGivens("A", "Z");
102                
103                // RESOLVERS:
104                testHelper.addResolver("D", /* <-- */ "B","C");
105                testHelper.addResolver("C", /* <-- */ "Z");
106                testHelper.addResolver("B", /* <-- */ "A");
107                
108                testHelper.logScenarioDescription();
109                
110                testHelper.assertSuccess("D");
111        }
112        
113        @Test
114        public void testMultipleValidPaths() {
115                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
116                
117                // GIVENS:
118                testHelper.addGivens("A", "Z");
119                
120                // RESOLVERS:
121                testHelper.addResolver("D", /* <-- */ "B","C");
122                testHelper.addResolver("C", /* <-- */ "Z");
123                testHelper.addResolver("B", /* <-- */ "A");
124                testHelper.addResolver("D", /* <-- */ "A");
125                
126                testHelper.logScenarioDescription();
127                
128                testHelper.assertSuccess("D");
129        }
130        
131        @Test
132        public void testDiamond() {
133                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
134
135                // GIVENS:
136                testHelper.addGivens("A");
137                
138                // RESOLVERS:
139                testHelper.addResolver("D", /* <-- */ "B","C");
140                testHelper.addResolver("C", /* <-- */ "A");
141                testHelper.addResolver("B", /* <-- */ "A");
142                
143                testHelper.logScenarioDescription();
144                
145                testHelper.assertSuccess("D");
146                
147        }
148
149        @Test
150        public void testComplexPath() {
151                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
152
153                // GIVENS:
154                testHelper.addGivens("Q","R","S");
155                
156                // RESOLVERS:
157                testHelper.addResolver("A", /* <-- */ "B","F");
158                testHelper.addResolver("A", /* <-- */ "Z");
159                testHelper.addResolver("B", /* <-- */ "D");
160                testHelper.addResolver("B", /* <-- */ "C");
161                testHelper.addResolver("C", /* <-- */ "S");
162                testHelper.addResolver("D", /* <-- */ "E");
163                testHelper.addResolver("E", /* <-- */ "S");
164                testHelper.addResolver("F", /* <-- */ "G","Q");
165                testHelper.addResolver("G", /* <-- */ "R");
166                
167                testHelper.logScenarioDescription();
168                
169                testHelper.assertSuccess("A");
170                
171        }
172
173        @Test
174        public void testCycle() {
175                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
176
177                // GIVENS:
178                // none
179                
180                // RESOLVERS:
181                testHelper.addResolver("D", /* <-- */ "C");
182                testHelper.addResolver("C", /* <-- */ "D");
183                
184                testHelper.logScenarioDescription();
185                
186                testHelper.assertException("D");
187                
188        }
189        
190
191        @Test
192        public void testUnreachableTerm() {
193                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
194
195                // GIVENS:
196                // none
197                
198                // RESOLVERS:
199                testHelper.addResolver("D", /* <-- */ "C");
200                testHelper.addResolver("C", /* <-- */ "B");
201                
202                testHelper.logScenarioDescription();
203                
204                testHelper.assertException("D");
205                
206        }
207        
208        @Test
209        public void testRedHerringPath() {
210                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
211
212                // GIVENS:
213                testHelper.addGivens("Q");
214                
215                // RESOLVERS:
216                testHelper.addResolver("A", /* <-- */ "B");
217                // this is the shortest path, but C can't be resolved
218                testHelper.addResolver("B", /* <-- */ "C");
219                
220                testHelper.addResolver("B", /* <-- */ "D");
221                testHelper.addResolver("D", /* <-- */ "Q");
222                
223                testHelper.logScenarioDescription();
224                
225                testHelper.assertSuccess("A");
226        }
227        
228        @Test
229        public void testResolveParamaterizedTerm() {
230                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
231
232                // GIVENS:
233                testHelper.addGivens("Q");
234                
235                // RESOLVERS:
236                testHelper.addResolver(1, "A", new String[] {"param"}, /* <-- */ "B");
237                testHelper.addResolver("B", /* <-- */ "Q");
238                
239                testHelper.logScenarioDescription();
240                
241                Map<String,String> params = new HashMap();
242                params.put("param", "value");
243                testHelper.assertSuccess(testHelper.getTerm("A", params));
244
245                // termName A needs parameters, so this shouldn't work
246                testHelper.assertException(new Term("A"));
247
248        }
249        
250        @Test
251        public void testIntermediateParamaterizedTerm() {
252                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
253
254                // GIVENS:
255                testHelper.addGivens("Q");
256                
257                // RESOLVERS:
258                testHelper.addResolver(1, "A", new String[] {"foo"}, /* <-- */ "B");
259                testHelper.addResolver(1, "B", new String[] {"bar"},  /* <-- */ "Q");
260                
261                testHelper.logScenarioDescription();
262                
263                Map<String,String> params = new HashMap();
264                params.put("foo", "foovalue");
265
266                // Resolver for termName b requires parameters, so this shouldn't work
267                testHelper.assertException(testHelper.getTerm("A", params));
268        }
269        
270        private static class WhiteBoxTermResolutionEngineImpl extends TermResolutionEngineImpl {
271                
272                // expose this for testing purposes
273                @Override
274                public List<TermResolverKey> buildTermResolutionPlan(String termName) {
275                        return super.buildTermResolutionPlan(termName);
276                }
277        }
278        
279        
280        @Test
281        public void testShortestPath() {
282                
283                WhiteBoxTermResolutionEngineImpl whiteBoxTermResolutionService = new WhiteBoxTermResolutionEngineImpl();
284                TestScenarioHelper testHelper = new TestScenarioHelper(whiteBoxTermResolutionService);
285
286                // GIVENS:
287                testHelper.addGivens("Q");
288                
289                // RESOLVERS:
290                // this one costs 3 (instead of default cost of 1)
291                testHelper.addResolver(3, "A", /* <-- */ "Q");
292
293                // the steps for the alternate path each cost 1, but total length is 4
294                testHelper.addResolver("A", /* <-- */ "B");
295                testHelper.addResolver("B", /* <-- */ "C");
296                testHelper.addResolver("C", /* <-- */ "D");
297                testHelper.addResolver("D", /* <-- */ "Q");
298                
299                testHelper.logScenarioDescription();
300                
301                List<?> plan = whiteBoxTermResolutionService.buildTermResolutionPlan("A");
302                LOG.info("resolutionPlan: " + StringUtils.join(plan, ", ") + " <-- should be length 1!");
303                assertTrue("didn't choose the shortest resolution path (of length 1)", plan.size() == 1);
304        }
305        
306        /*
307         *  TODO: test exception variants:
308         *  - TermResolver throws TermResolutionException
309         *  - TermResolutionEngine is passed a null termName
310         */
311        
312        // TODO: what should the TermResolutionEngine do if a resolver throws a RuntimeException?
313        /*
314        @Test
315        public void testExplodingResolver() {
316                TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
317
318                // GIVENS:
319                testHelper.addGivens("Q");
320                
321                // RESOLVERS:
322                TermResolverMock exploder = new TermResolverMock(testHelper.getTerm("A"), testHelper.getResult("A"), testHelper.getTerm("Q"));
323                exploder.setIsExploder(true);
324                termResolutionEngine.addTermResolver(exploder);
325                
326                testHelper.logScenarioDescription();
327                
328                testHelper.assertException("A");
329        }
330        */
331        
332        
333        private static class TermResolverMock<T> implements TermResolver<T> {
334                private String output;
335                private Set<String> params;
336                private T result;
337                private Set<String> prereqs;
338                private int cost;
339                private boolean isExploder = false;
340
341                public TermResolverMock(String output, T result, int cost, String ... prereqs) {
342                        this(output, null, result, cost, prereqs);
343                }
344                
345                public TermResolverMock(String output, Set<String> params, T result, int cost, String ... prereqs) {
346                        this.output = output;
347                        this.params = Collections.unmodifiableSet(params);
348                        this.result = result;
349                        this.prereqs = new HashSet<String>(Arrays.asList(prereqs));
350                        this.cost = cost;
351                }
352                
353                @Override
354                public int getCost() {
355                        return cost;
356                }
357                
358                @Override
359                public String getOutput() {
360                        return output;
361                }
362                
363                @Override
364                public Set<String> getPrerequisites() {
365                        return prereqs;
366                }
367                
368                @Override
369                public Set<String> getParameterNames() {
370                        return params;
371                }
372                
373                public void setIsExploder(boolean isExploder) {
374                        this.isExploder = isExploder;
375                }
376                
377                @Override
378                public T resolve(Map<String, Object> resolvedPrereqs, Map<String, String> parameters) {
379                        
380                        if (isExploder) {
381                                throw new RuntimeException("I'm the exploder, coo coo catchoo");
382                        }
383                        
384                        // get all prereqs first
385                        for (String prereq : prereqs) {
386                                Object result = resolvedPrereqs.get(prereq);
387                                if (result == null) fail("got back null for prereq " + prereq);
388                        }
389
390                        LOG.info("resolving " + output);
391                        return result;
392                }
393                
394                @Override
395                public String toString() {
396                        String paramStr = "";
397                        if (!CollectionUtils.isEmpty(params)) {
398                                paramStr = "+(" + StringUtils.join(params, ",") + ")";
399                        }
400                        return getClass().getSimpleName()+"[ "+output+paramStr+ " <- " + StringUtils.join(prereqs.iterator(), ",") + " ]";
401                }
402
403        }
404        
405
406        private static class TestScenarioHelper {
407                
408                private final TermResolutionEngine ars;
409                
410                private List<String> givens = new LinkedList<String>();
411                private List<String> resolvers = new LinkedList<String>();
412
413                public TestScenarioHelper(TermResolutionEngine tre) {
414                        this.ars = tre;
415                }
416                
417                public void addGivens(String ... names) {
418                        for (String name : names) {
419                                ars.addTermValue(new Term(name, null), getResult(name));
420                                givens.add(name);
421                        }
422                }
423                
424                public String getResult(String name) {
425                        return getResult(new Term(name, null));
426                }
427                
428                public String getResult(Term term) {
429                        return term.getName()  +"-result";
430                }
431                
432                public Term getTerm(String name, Map<String,String> params) {
433                        return new Term(name, params);
434                }
435                
436                public void addResolver(String out, String ... prereqs) {
437                        addResolver(1, out, prereqs);
438                }
439
440                public void addResolver(int cost, String out, String ... prereqs) {
441                        addResolver(1, out, null, prereqs);
442                }
443                
444                public void addResolver(int cost, String out, String[] params, String ... prereqs) {
445                        String [] prereqTerms = new String [prereqs.length];
446                        
447                        for (int i=0; i<prereqs.length; i++) prereqTerms[i] = prereqs[i];
448                        
449                        Set<String> paramSet = Collections.emptySet();
450                        if (params != null) paramSet = new HashSet<String>(Arrays.asList(params));
451                        
452                        ars.addTermResolver(new TermResolverMock<Object>(out, paramSet, getResult(out), cost, prereqTerms));
453                        resolvers.add("(" + out + " <- " + StringUtils.join(prereqs, ",") + ")");
454                }
455                
456                public void logScenarioDescription() {
457                        StringBuilder sb = new StringBuilder();
458                        
459                        sb.append("givens: " + StringUtils.join(givens.iterator(), ", ") + "\n\n");
460                        sb.append("resolvers:\n----------------------\n");
461                        if (resolvers == null || resolvers.size() == 0) {
462                                sb.append("none");
463                        } else { 
464                                sb.append(StringUtils.join(resolvers.iterator(), "\n"));
465                        }
466                        sb.append("\n");
467
468                        LOG.info("Test Scenario:\n\n" + sb.toString());
469                }
470                
471                public void assertSuccess(String toResolve) {
472                        assertSuccess(new Term(toResolve, null));
473                }
474                
475                public void assertSuccess(Term toResolve) {
476                        LOG.info("Testing resolution of " + toResolve);
477                        try {
478                                assertEquals(getResult(toResolve), ars.resolveTerm(toResolve));
479                                LOG.info("Success!");
480                        } catch (TermResolutionException e) {
481                                fail("Should resolve the termName w/o exceptions");
482                        }
483                }
484
485                public void assertException(String toResolve) {
486                        assertException(new Term(toResolve, null));
487                }
488                        
489                public void assertException(Term toResolve) {
490                        LOG.info("Testing resolution of " + toResolve);
491                        try {
492                                ars.resolveTerm(toResolve);
493                                fail("Should throw TermResolutionException");
494                        } catch (TermResolutionException e) {
495                                LOG.info("Success! threw " + e);
496                                // Good, this is what we expect
497                        }
498                }
499        }
500        
501}