001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.krms.framework;
017    
018    import static junit.framework.Assert.assertEquals;
019    import static junit.framework.Assert.assertTrue;
020    import static junit.framework.Assert.fail;
021    
022    import java.util.Arrays;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.LinkedList;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import org.apache.commons.lang.StringUtils;
032    import org.junit.Before;
033    import org.junit.Test;
034    import org.kuali.rice.krms.api.engine.Term;
035    import org.kuali.rice.krms.api.engine.TermResolutionEngine;
036    import org.kuali.rice.krms.api.engine.TermResolutionException;
037    import org.kuali.rice.krms.api.engine.TermResolver;
038    import org.kuali.rice.krms.framework.engine.TermResolutionEngineImpl;
039    import org.springframework.util.CollectionUtils;
040    
041    
042    public 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    }