diff --git a/src/main/java/io/opensaber/views/EvaluatorFactory.java b/src/main/java/io/opensaber/views/EvaluatorFactory.java new file mode 100644 index 0000000..e8c12e1 --- /dev/null +++ b/src/main/java/io/opensaber/views/EvaluatorFactory.java @@ -0,0 +1,42 @@ +package io.opensaber.views; + +import java.util.List; + +public class EvaluatorFactory { + + /** + * returns the instance of IEvaluator implementations (like:FunctionEvaluator, ProviderEvaluator + * + * @param actualValues + * @param functiondef + * @return + */ + public static IEvaluator getInstance(FunctionDefinition functiondef, List actualValues) { + IEvaluator evaluator = null; + FieldFunction function = null; + + if (functiondef.getResult() != null) { + function = getFieldFunction(functiondef.getResult(), actualValues); + evaluator = new FunctionEvaluator(function); + + } else if (functiondef.getProvider() != null) { + function = getFieldFunction(functiondef.getProvider(), actualValues); + evaluator = new ProviderEvaluator(function); + } + + return evaluator; + } + /** + * Creates FieldFunction and sets argValues + * @param expression + * @param actualValues + * @return + */ + private static FieldFunction getFieldFunction(String expression, List actualValues) { + FieldFunction function = new FieldFunction(expression); + function.setArgValues(actualValues); + return function; + + } + +} diff --git a/src/main/java/io/opensaber/views/FunctionEvaluator.java b/src/main/java/io/opensaber/views/FunctionEvaluator.java index c2bcb83..e555673 100644 --- a/src/main/java/io/opensaber/views/FunctionEvaluator.java +++ b/src/main/java/io/opensaber/views/FunctionEvaluator.java @@ -7,7 +7,7 @@ import org.apache.commons.jexl2.MapContext; -public class FunctionEvaluator { +public class FunctionEvaluator implements IEvaluator{ private static final JexlEngine jexl = new JexlEngine(); private JexlContext jexlContext = new MapContext(); @@ -34,6 +34,7 @@ private void prepare() { setContextArgs(); } + @Override public Object evaluate() { prepare(); Object result = jexlExpression.evaluate(jexlContext); diff --git a/src/main/java/io/opensaber/views/IEvaluator.java b/src/main/java/io/opensaber/views/IEvaluator.java new file mode 100644 index 0000000..c5ff8ba --- /dev/null +++ b/src/main/java/io/opensaber/views/IEvaluator.java @@ -0,0 +1,10 @@ +package io.opensaber.views; + +public interface IEvaluator { + /** + * evaluates to provide result + * From a given expression, a provider class, a reference template of a sview template + * @return + */ + public T evaluate(); +} diff --git a/src/main/java/io/opensaber/views/ProviderEvaluator.java b/src/main/java/io/opensaber/views/ProviderEvaluator.java new file mode 100644 index 0000000..fdc98f7 --- /dev/null +++ b/src/main/java/io/opensaber/views/ProviderEvaluator.java @@ -0,0 +1,47 @@ +package io.opensaber.views; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProviderEvaluator implements IEvaluator { + + private static Logger logger = LoggerFactory.getLogger(ProviderEvaluator.class); + + private FieldFunction function; + + public ProviderEvaluator(FieldFunction function) { + this.function = function; + } + + @Override + public Object evaluate() { + IViewFunctionProvider viewFuntionProvider = getInstance(function.getExpression()); + Object result = viewFuntionProvider.doAction(function.getArgValues()); + return result; + } + + /** + * invokes instance for given providerName + * @param providerName full qualified name of class + * @return + */ + public IViewFunctionProvider getInstance(String providerName) { + + IViewFunctionProvider viewFunctionProvider = null; + try { + if (providerName == null || providerName.isEmpty()) { + throw new IllegalArgumentException( + "view function provider class {} cannot be instantiate with empty value"); + } + Class advisorClass = Class.forName(providerName); + viewFunctionProvider = (IViewFunctionProvider) advisorClass.newInstance(); + logger.info("Invoked view function provider class with classname: " + providerName); + + } catch (ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException e) { + logger.error("view function provider class {} cannot be instantiate with exception:", providerName, e); + } + return viewFunctionProvider; + } + +} diff --git a/src/main/java/io/opensaber/views/SampleViewFunctionProvider.java b/src/main/java/io/opensaber/views/SampleViewFunctionProvider.java deleted file mode 100644 index a2b7c50..0000000 --- a/src/main/java/io/opensaber/views/SampleViewFunctionProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.opensaber.views; - -import java.util.List; - -public class SampleViewFunctionProvider implements IViewFunctionProvider { - - @Override - public String doAction(List values) { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/src/main/java/io/opensaber/views/Transformer.java b/src/main/java/io/opensaber/views/Transformer.java index 206ebb3..1826d0a 100644 --- a/src/main/java/io/opensaber/views/Transformer.java +++ b/src/main/java/io/opensaber/views/Transformer.java @@ -32,21 +32,22 @@ public JsonNode transform(ViewTemplate viewTemplate, ObjectNode node) { if (functionStr != null) { String fdName = field.getFunctioName(); - String expression = viewTemplate.getExpression(fdName); - - FieldFunction function = new FieldFunction(expression); + FunctionDefinition funcDef = viewTemplate.getFunctionDefinition(fdName); + List actualValues = new ArrayList<>(); for (String oneArg : field.getArgNames()) { // Cut off the $ actualValues.add(ValueType.getValue(nodeAttrs.get(oneArg.substring(1)))); } - function.setArgValues(actualValues); - - FunctionEvaluator evaluator = new FunctionEvaluator(function); - + + IEvaluator evaluator = EvaluatorFactory.getInstance(funcDef, actualValues); if (field.getDisplay()) { - result.put(field.getTitle(), evaluator.evaluate().toString()); - + Object evaluatedValue = evaluator.evaluate(); + if(evaluatedValue instanceof String){ + result.put(field.getTitle(), evaluatedValue.toString()); + } else { + result.set(field.getTitle(), JsonNodeFactory.instance.pojoNode(evaluatedValue)); + } } } else if (field.getDisplay()) { result.set(field.getTitle(), nodeAttrs.get(field.getName())); @@ -56,5 +57,6 @@ public JsonNode transform(ViewTemplate viewTemplate, ObjectNode node) { logger.debug("Node transformation result: " + result); return JsonNodeFactory.instance.objectNode().set(subjectType, result); } + } diff --git a/src/main/java/io/opensaber/views/ViewTemplate.java b/src/main/java/io/opensaber/views/ViewTemplate.java index 9563e50..5a31847 100644 --- a/src/main/java/io/opensaber/views/ViewTemplate.java +++ b/src/main/java/io/opensaber/views/ViewTemplate.java @@ -38,25 +38,24 @@ public List getFields() { public void setFields(List fields) { this.fields = fields; } + /** - * return the result for a given function name - * Example: "arg1 + \" : \" + arg2" - * - * @param name function name (like concat) - * @return result + * return a FunctionDefinition for a given function name + + * @param name function name (like concat) + * @return */ - public String getExpression(String name) { - String expression = ""; + public FunctionDefinition getFunctionDefinition(String name) { + FunctionDefinition functionDef = null; for (FunctionDefinition fd : this.getFunctionDefinitions()) { if (fd.getName().compareTo(name) == 0) { - expression = fd.getResult(); - + functionDef = fd; } } - if (expression.isEmpty()) { + if (functionDef == null) { logger.error("No function definition specified for function - " + name); throw new IllegalArgumentException("No function definition specified for function - " + name); } - return expression; + return functionDef; } } diff --git a/src/main/java/org/example/provider/SampleViewFunctionProvider.java b/src/main/java/org/example/provider/SampleViewFunctionProvider.java new file mode 100644 index 0000000..dc58620 --- /dev/null +++ b/src/main/java/org/example/provider/SampleViewFunctionProvider.java @@ -0,0 +1,31 @@ +package org.example.provider; + +import io.opensaber.views.IViewFunctionProvider; +import java.util.List; +/** + * This class is a sample implementation class of IViewFunctionProvider + * + */ +public class SampleViewFunctionProvider implements IViewFunctionProvider { + + @Override + public String doAction(List values) { + // doing a simple concat for the values + return concat(values); + } + + /** + * simple concat for the values as string and comma(',') as seperator + * + * @param args + * @return + */ + public String concat(List args) { + String res = ""; + for (Object arg : args) { + res = res.toString().isEmpty() ? arg.toString() : (res + " : " + arg.toString()); + } + return res; + } + +} diff --git a/src/test/java/io/opensaber/views/TransformerTest.java b/src/test/java/io/opensaber/views/TransformerTest.java index f627d5e..94ecd4b 100644 --- a/src/test/java/io/opensaber/views/TransformerTest.java +++ b/src/test/java/io/opensaber/views/TransformerTest.java @@ -18,27 +18,17 @@ public class TransformerTest { private Transformer transformer = new Transformer(); @Test - public void testTransformForPersonVT() throws JsonProcessingException, IOException{ - String personJson = "{\"Person\": " + - " {\"nationalIdentifier\":\"nid823\"," + - " \"firstName\":\"Ram\"," + - " \"lastName\":\"Moorthy\"," + - " \"gender\":\"MALE\"," + - " \"dob\":\"1990-12-10\"}}"; - ObjectNode personNode = (ObjectNode) new ObjectMapper().readTree(personJson); - String viewTemplateJson = readFileContent("personVT1.json"); - ViewTemplate viewTemplate = new ObjectMapper().readValue(viewTemplateJson, ViewTemplate.class); + public void testTransformForPersonFunction() throws JsonProcessingException, IOException{ - JsonNode actualnode = transformer.transform(viewTemplate, personNode); + ObjectNode personNode = getPerson(); + ViewTemplate viewTemplate = getViewTemplatePerson("person_vt.json"); - System.out.println("actualnode = "+actualnode); - JsonNode expectedNode = new ObjectMapper().readTree("{\"Person\":{\"NAME\":\"Ram\",\"lastName\":\"Moorthy\",\"Name in passport\":\"Moorthy, Ram\"}}"); - System.out.println("expectedNode = "+expectedNode); + JsonNode actualnode = transformer.transform(viewTemplate, personNode); + JsonNode expectedNode = new ObjectMapper().readTree("{\"Person\":{\"NAME\":\"Ram\",\"lastName\":\"Moorthy\",\"Name in passport\":\"Moorthy, Ram\",\"Name as in DL\":\"Ram : Moorthy\"}}"); assertEquals(expectedNode, actualnode); } - @Test public void testTransformForMathVT() throws JsonProcessingException, IOException{ @@ -47,14 +37,29 @@ public void testTransformForMathVT() throws JsonProcessingException, IOException " \"b\": 2 }}"; ObjectNode node = (ObjectNode) new ObjectMapper().readTree(mathProblem); - String viewTemplateJson = readFileContent("mathVT1.json"); - ViewTemplate viewTemplate = new ObjectMapper().readValue(viewTemplateJson, ViewTemplate.class); - + ViewTemplate viewTemplate = getViewTemplatePerson("mathVT1.json"); JsonNode actualnode = transformer.transform(viewTemplate, node); - JsonNode expectedNode = new ObjectMapper().readTree("{\"Math\":{\"addend_A\":5,\"addend_B\":2,\"SUM\":\"7\"}}"); + JsonNode expectedNode = new ObjectMapper().readTree("{\"Math\":{\"addend_A\":5,\"addend_B\":2,\"SUM\":7}}"); assertEquals(expectedNode.toString(), actualnode.toString()); } + + + private ViewTemplate getViewTemplatePerson(String personJsonFileName) throws JsonProcessingException, IOException{ + + String viewTemplateJson = readFileContent(personJsonFileName); + return new ObjectMapper().readValue(viewTemplateJson, ViewTemplate.class); + } + + private ObjectNode getPerson() throws JsonProcessingException, IOException{ + String personJson = "{\"Person\": " + + " {\"nationalIdentifier\":\"nid823\"," + + " \"firstName\":\"Ram\"," + + " \"lastName\":\"Moorthy\"," + + " \"gender\":\"MALE\"," + + " \"dob\":\"1990-12-10\"}}"; + return (ObjectNode) new ObjectMapper().readTree(personJson); + } private static String readFileContent(String fileName) { InputStream in; diff --git a/src/test/java/io/opensaber/views/ViewTemplateTest.java b/src/test/java/io/opensaber/views/ViewTemplateTest.java index 7bba99b..581f2e4 100644 --- a/src/test/java/io/opensaber/views/ViewTemplateTest.java +++ b/src/test/java/io/opensaber/views/ViewTemplateTest.java @@ -26,17 +26,17 @@ public void init() { @Test public void testGetExpression() { - String result = vt.getExpression("name"); + FunctionDefinition fd = vt.getFunctionDefinition("name"); - assertEquals(vt.getFunctionDefinitions().get(0).getResult(), result); - assertNotEquals("unexpected", result); + assertEquals(vt.getFunctionDefinitions().get(0).getResult(), fd.getResult()); + assertNotEquals("unexpected", fd.getResult()); } @Test(expected = IllegalArgumentException.class) public void testGetExpressionException() { - vt.getExpression("invalid_name"); + vt.getFunctionDefinition("invalid_name"); } diff --git a/src/test/resources/personVT1.json b/src/test/resources/person_ref.json similarity index 84% rename from src/test/resources/personVT1.json rename to src/test/resources/person_ref.json index cdf99d8..08318e5 100644 --- a/src/test/resources/personVT1.json +++ b/src/test/resources/person_ref.json @@ -18,7 +18,7 @@ }, { "title": "Name in passport", - "function": "#/functionDefinitions/concat($lastName, $firstName)", + "function": "#/functionDefinitions/customDefinedConcat($lastName, $firstName)", "$comment": "This is a virtual field not defined in the schema" }, { @@ -37,12 +37,12 @@ }, { "name" : "userDefinedConcat", - "provider": "user.opensaber.registry.utils.MySplConcatenator", + "provider": "io.opensaber.provider.SampleViewFunctionProvider", "$comment" : "Complex operations that cannot be expressed easily in an in-line function definition can be implemented as a class. " }, { "name" : "customDefinedConcat", - "$ref": "AnotherViewTemplate.json#functionDefinitions/customDefinedConcat", + "result": "arg1 + \", \" + arg2", "$comment": "The functions defined in another template can be reused" } ] diff --git a/src/test/resources/person_vt.json b/src/test/resources/person_vt.json new file mode 100644 index 0000000..4da4033 --- /dev/null +++ b/src/test/resources/person_vt.json @@ -0,0 +1,55 @@ +{ + "id": "personDefaultView1", + "subject": "Person", + "fields": [ + { + "name": "firstName", + "title": "NAME" + }, + { + "name": "lastName", + "display": true + }, + { + "name": "nationalIdentifier", + "title": "AADHAAR number", + "display": false, + "$comment": "This field is not displayable, but needed for internal referencing" + }, + { + "title": "Name in passport", + "function": "#/functionDefinitions/concat($lastName, $firstName)", + "$comment": "This is a virtual field not defined in the schema" + }, + { + "title": "Name as in DL", + "function": "#/functionDefinitions/userDefinedConcat($firstName, $lastName)", + "$comment": "This is a virtual field not defined in the schema" + }, + + { + "name": "vehicles", + "$comment": "Nested child entity which may be of interest", + "fetchType": "eager", + "viewTemplateName": "vehicleNameView.json", + "display": false + } + ], + "functionDefinitions": [ + { + "name" : "concat", + "result": "arg1 + \", \" + arg2", + "$comment": "arg1 and arg2 will be populated with parameter values at runtime" + }, + { + "name" : "userDefinedConcat", + "provider": "org.example.provider.SampleViewFunctionProvider", + "$comment" : "Complex operations that cannot be expressed easily in an in-line function definition can be implemented as a class. " + }, + { + "name" : "customDefinedConcat", + "$ref": "person_ref.json#functionDefinitions/customDefinedConcat", + "$comment": "The functions defined in another template can be reused" + } + ] +} \ No newline at end of file