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)); + } + }