This is an automated email from the ASF dual-hosted git repository.

nmalin pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 25c75e2  Implemented: Clojure Service engine (OFBIZ-12302) (#317)
25c75e2 is described below

commit 25c75e2839d2ad020951edaeae17371a48348906
Author: Eugen Stan <ieu...@apache.org>
AuthorDate: Tue Aug 10 17:09:39 2021 +0300

    Implemented: Clojure Service engine (OFBIZ-12302) (#317)
    
    * Added Clojure service and service test and example service
    * Added clojure engine
---
 build.gradle                                       |   4 +
 framework/common/servicedef/services.xml           |   6 +-
 framework/common/servicedef/services_test.xml      |   7 ++
 .../org/apache/ofbiz/common/clojure_test.clj       |  51 ++++++++
 framework/service/config/serviceengine.xml         |   1 +
 .../service/engine/StandardClojureEngine.java      | 140 +++++++++++++++++++++
 .../ofbiz/service/test/ServiceEngineTests.java     |   6 +
 7 files changed, 214 insertions(+), 1 deletion(-)

diff --git a/build.gradle b/build.gradle
index 6e5bfc9..4cd51b8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -151,6 +151,9 @@ allprojects {
             // com.springsource.com.sun.syndication
             url "https://repo.spring.io/plugins-release";
         }
+        maven {
+            url "https://clojars.org/repo";
+        }
     }
 }
 
@@ -216,6 +219,7 @@ dependencies {
     implementation 'org.apache.xmlgraphics:fop:2.3' // NOTE: since 2.4 
dependencies are messed up. See 
https://github.com/moqui/moqui-fop/blob/master/build.gradle
     implementation 'org.apache.xmlrpc:xmlrpc-client:3.1.3'
     implementation 'org.apache.xmlrpc:xmlrpc-server:3.1.3'
+    implementation 'org.clojure:clojure:1.10.2'
     implementation 'org.codehaus.groovy:groovy-all:2.5.11' // Compile issue 
with commons-cli and Groovy 3. Remember to change the version number in javadoc 
block.
     implementation 'org.freemarker:freemarker:2.3.31' // Remember to change 
the version number in FreeMarkerWorker class when upgrading. See OFBIZ-10019 if 
>= 2.4
     implementation 'org.owasp.esapi:esapi:2.2.2.0'
diff --git a/framework/common/servicedef/services.xml 
b/framework/common/servicedef/services.xml
index dd84f48..b654afb 100644
--- a/framework/common/servicedef/services.xml
+++ b/framework/common/servicedef/services.xml
@@ -43,7 +43,11 @@ under the License.
 
     <service name="echoService" engine="java" validate="false"
             location="org.apache.ofbiz.common.CommonServices" 
invoke="echoService">
-        <description>Echos back all passed parameters</description>
+        <description>Echoes back all passed parameters</description>
+    </service>
+    <service name="echoClojureService" engine="clojure" validate="false"
+             location="org.apache.ofbiz.common.clojure-test" 
invoke="echo-service">
+        <description>Echoes back all passed parameters</description>
     </service>
     <service name="returnErrorService" engine="java" validate="false"
             location="org.apache.ofbiz.common.CommonServices" 
invoke="returnErrorService">
diff --git a/framework/common/servicedef/services_test.xml 
b/framework/common/servicedef/services_test.xml
index 8e9eae3..9a68160 100644
--- a/framework/common/servicedef/services_test.xml
+++ b/framework/common/servicedef/services_test.xml
@@ -47,6 +47,13 @@ under the License.
     <service name="testError" engine="java" export="true" validate="false" 
require-new-transaction="true" max-retry="1"
             location="org.apache.ofbiz.common.CommonServices" 
invoke="returnErrorService">
     </service>
+    <service name="testClojureSvc" engine="clojure" export="true" 
validate="false" require-new-transaction="true"
+             location="org.apache.ofbiz.common.clojure-test" 
invoke="test-clojure-svc">
+        <description>Test service</description>
+        <attribute name="defaultValue" type="Double" mode="IN" 
default-value="999.9999"/>
+        <attribute name="message" type="String" mode="IN" optional="true"/>
+        <attribute name="resp" type="String" mode="OUT"/>
+    </service>
     <!-- Because of the danger of Java deserialization when using RMI, the RMI 
component has been disabled in the default configuration of OFBiz.
          If you need RMI you just need to uncomment those places - See 
OFBIZ-6942 for details -->
     <!-- see serviceengine.xml to configure the rmi location alias -->
diff --git 
a/framework/common/src/main/resources/org/apache/ofbiz/common/clojure_test.clj 
b/framework/common/src/main/resources/org/apache/ofbiz/common/clojure_test.clj
new file mode 100644
index 0000000..12d0a4e
--- /dev/null
+++ 
b/framework/common/src/main/resources/org/apache/ofbiz/common/clojure_test.clj
@@ -0,0 +1,51 @@
+;; Licensed to the Apache Software Foundation (ASF) under one
+;; or more contributor license agreements.  See the NOTICE file
+;; distributed with this work for additional information
+;; regarding copyright ownership.  The ASF licenses this file
+;; to you under the Apache License, Version 2.0 (the
+;; "License"); you may not use this file except in compliance
+;; with the License.  You may obtain a copy of the License at
+;;
+;; http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing,
+;; software distributed under the License is distributed on an
+;; "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+;; KIND, either express or implied.  See the License for the
+;; specific language governing permissions and limitations
+;; under the License.
+(ns org.apache.ofbiz.common.clojure-test
+  (:import [org.apache.ofbiz.base.util Debug]
+           [org.apache.ofbiz.service ServiceUtil]
+           [org.apache.ofbiz.service ModelService]))
+
+(def ^String module (str (ns-name 'org.apache.ofbiz.common.clojure-test)))
+
+(defn log-context
+  "Logs context if not empty."
+  [^java.util.Map ctx]
+  (when-not (.isEmpty ctx)
+    (doseq [keyval ctx]
+      (Debug/logInfo (str "---- SVC-CONTEXT: "  (key keyval)  " => " (val 
keyval)) module))))
+
+
+(defn test-clojure-svc
+  "Clojure test service."
+  [dctx ctx]
+  (let [response (ServiceUtil/returnSuccess)
+        has-message? (contains? ctx "messages")]
+    (log-context ctx)
+    (if has-message?
+      (.put response "resp" "no message found")
+      (do
+        (Debug/logInfo (str "-----SERVICE TEST----- : " (.get ctx "message")) 
module)
+        (.put response "resp" "service done")))
+    (Debug/logInfo (str "----- SVC: " (.getName dctx) " -----") module)
+    response))
+
+(defn echo-service
+  "Echo back all the parameters"
+  [dctx ctx]
+  (doto (new java.util.LinkedHashMap)
+        (.putAll ctx)
+        (.put ModelService/RESPONSE_MESSAGE ModelService/RESPOND_SUCCESS)))
diff --git a/framework/service/config/serviceengine.xml 
b/framework/service/config/serviceengine.xml
index e1dc683..a172952 100644
--- a/framework/service/config/serviceengine.xml
+++ b/framework/service/config/serviceengine.xml
@@ -45,6 +45,7 @@ under the License.
         <engine name="group" 
class="org.apache.ofbiz.service.group.ServiceGroupEngine"/>
         <engine name="interface" 
class="org.apache.ofbiz.service.engine.InterfaceEngine"/>
         <engine name="java" 
class="org.apache.ofbiz.service.engine.StandardJavaEngine"/>
+        <engine name="clojure" 
class="org.apache.ofbiz.service.engine.StandardClojureEngine"/>
         <engine name="simple" 
class="org.apache.ofbiz.minilang.SimpleServiceEngine"/>
         <engine name="script" 
class="org.apache.ofbiz.service.engine.ScriptEngine"/>
         <!-- Engines that can be replaced by the generic script engine -->
diff --git 
a/framework/service/src/main/java/org/apache/ofbiz/service/engine/StandardClojureEngine.java
 
b/framework/service/src/main/java/org/apache/ofbiz/service/engine/StandardClojureEngine.java
new file mode 100644
index 0000000..4918129
--- /dev/null
+++ 
b/framework/service/src/main/java/org/apache/ofbiz/service/engine/StandardClojureEngine.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ 
*******************************************************************************/
+package org.apache.ofbiz.service.engine;
+
+import clojure.java.api.Clojure;
+import clojure.lang.IFn;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.UtilGenerics;
+import org.apache.ofbiz.service.DispatchContext;
+import org.apache.ofbiz.service.GenericServiceException;
+import org.apache.ofbiz.service.ModelService;
+import org.apache.ofbiz.service.ServiceDispatcher;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+/**
+ * Clojure service engine. Enables OFBiz services written in Clojure.
+ */
+public class StandardClojureEngine extends GenericAsyncEngine {
+
+    private static final String MODULE = StandardClojureEngine.class.getName();
+
+    public StandardClojureEngine(ServiceDispatcher dispatcher) {
+        super(dispatcher);
+        Debug.logInfo("Created Clojure engine.", MODULE);
+    }
+
+    /**
+     * Load Clojure ns and call service function.
+     * <p>
+     * See 
https://clojure.github.io/clojure/javadoc/clojure/java/api/Clojure.html
+     *
+     * @param ns      Clojure namespace to load
+     * @param fn      Clojure function to call
+     * @param dctx    OFBiz dispatch context
+     * @param context OFBiz context - input parameters
+     * @return
+     * @throws Exception
+     */
+    public static Object callClojure(String ns, String fn, DispatchContext 
dctx, Map<String, Object> context) throws Exception {
+        Debug.logInfo("Call %s/%s ", MODULE, ns, fn);
+        IFn require = Clojure.var("clojure.core", "require");
+        require.invoke(Clojure.read(ns));
+        return Clojure.var(ns, fn).invoke(dctx, context);
+    }
+
+    @Override
+    public void runSyncIgnore(String localName, ModelService modelService,
+                              Map<String, Object> context) throws 
GenericServiceException {
+        runSync(localName, modelService, context);
+    }
+
+    @Override
+    public Map<String, Object> runSync(String localName, ModelService 
modelService,
+                                       Map<String, Object> context) throws 
GenericServiceException {
+
+        Object result = serviceInvoker(localName, modelService, context);
+
+        if (result == null || !(result instanceof Map<?, ?>)) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] did not return a 
Map object");
+        }
+        return UtilGenerics.cast(result);
+    }
+
+    private Object serviceInvoker(String localName, ModelService modelService,
+                                  Map<String, Object> context) throws 
GenericServiceException {
+        DispatchContext dctx = getDispatcher().getLocalContext(localName);
+        if (modelService == null) {
+            Debug.logError("ERROR: Null Model Service.", MODULE);
+        }
+        if (dctx == null) {
+            Debug.logError("ERROR: Null DispatchContext.", MODULE);
+        }
+        if (context == null) {
+            Debug.logError("ERROR: Null Service Context.", MODULE);
+        }
+        Object result = null;
+
+        // check the namespace and function names
+        if (modelService.getLocation() == null || modelService.getInvoke() == 
null) {
+            throw new GenericServiceException("Service [" + 
modelService.getName()
+                    + "] is missing location and/or invoke values which are 
required for execution.");
+        }
+
+        try {
+            String ns = this.getLocation(modelService);
+            String fn = modelService.getInvoke();
+            result = callClojure(ns, fn, dctx, context);
+        } catch (ClassNotFoundException cnfe) {
+            throw new GenericServiceException(
+                    "Cannot find service [" + modelService.getName() + "] 
location class", cnfe);
+        } catch (NoSuchMethodException nsme) {
+            throw new GenericServiceException("Service [" + 
modelService.getName()
+                    + "] specified Java method (invoke attribute) does not 
exist",
+                    nsme);
+        } catch (SecurityException se) {
+            throw new GenericServiceException("Service [" + 
modelService.getName() + "] Access denied",
+                    se);
+        } catch (IllegalAccessException iae) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] Method not 
accessible", iae);
+        } catch (IllegalArgumentException iarge) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] Invalid 
parameter match", iarge);
+        } catch (InvocationTargetException ite) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] target threw an 
unexpected exception",
+                    ite.getTargetException());
+        } catch (NullPointerException npe) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] ran into an 
unexpected null object", npe);
+        } catch (ExceptionInInitializerError eie) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] Initialization 
failed", eie);
+        } catch (Throwable th) {
+            throw new GenericServiceException(
+                    "Service [" + modelService.getName() + "] Error or unknown 
exception", th);
+        }
+
+        return result;
+    }
+}
diff --git 
a/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTests.java
 
b/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTests.java
index 5499469..6cc7386 100644
--- 
a/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTests.java
+++ 
b/framework/service/src/main/java/org/apache/ofbiz/service/test/ServiceEngineTests.java
@@ -46,4 +46,10 @@ public class ServiceEngineTests extends OFBizTestCase {
         Map<String, Object> result = getDispatcher().runSync("testScv", 
UtilMisc.toMap("message", "Unit Test"));
         assertEquals("Service result success", ModelService.RESPOND_SUCCESS, 
result.get(ModelService.RESPONSE_MESSAGE));
     }
+
+    public void testBasicClojureInvocation() throws Exception {
+        Map<String, Object> result = getDispatcher().runSync("testClojureSvc", 
UtilMisc.toMap("message", "Unit Test"));
+        assertEquals("Service result success", ModelService.RESPOND_SUCCESS, 
result.get(ModelService.RESPONSE_MESSAGE));
+    }
+
 }

Reply via email to