svn commit: r1339079 - in /ofbiz/trunk/framework/minilang: dtd/simple-methods-v2.xsd src/org/ofbiz/minilang/method/callops/CallService.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1339079 - in /ofbiz/trunk/framework/minilang: dtd/simple-methods-v2.xsd src/org/ofbiz/minilang/method/callops/CallService.java

adrianc
Author: adrianc
Date: Wed May 16 09:20:03 2012
New Revision: 1339079

URL: http://svn.apache.org/viewvc?rev=1339079&view=rev
Log:
Overhauled Mini-language <call-service> element, final pass.

Modified:
    ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd
    ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/callops/CallService.java

Modified: ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd?rev=1339079&r1=1339078&r2=1339079&view=diff
==============================================================================
--- ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd (original)
+++ ofbiz/trunk/framework/minilang/dtd/simple-methods-v2.xsd Wed May 16 09:20:03 2012
@@ -323,7 +323,7 @@ under the License.
             <xs:attribute type="xs:string" name="default-error-code">
                 <xs:annotation>
                     <xs:documentation>
-                        The default error return code. Defauts to "error".
+                        The default error return code. Defaults to "error".
                         &lt;br/&gt;&lt;br/&gt;
                         Optional. Attribute type: constant.
                     </xs:documentation>
@@ -332,7 +332,7 @@ under the License.
             <xs:attribute type="xs:string" name="default-success-code">
                 <xs:annotation>
                     <xs:documentation>
-                        The default success return code. Defauts to "success".
+                        The default success return code. Defaults to "success".
                         &lt;br/&gt;&lt;br/&gt;
                         Optional. Attribute type: constant.
                     </xs:documentation>
@@ -572,128 +572,129 @@ under the License.
     <xs:element name="call-service" substitutionGroup="CallOperations">
         <xs:annotation>
             <xs:documentation>
-                The call-service tag invokes a service through the Service Engine.
-                If the specified error code is returned from the service,
-                the event is aborted and the transaction in the current thread is rolled back.
-                Otherwise, the remaining operations are invoked.
-
-                The result-to-request and result-to-session elements will be IGNORED when
-                called in a service context. So, they are ONLY used when called in an event context.
+                Invokes a service through the Service Engine.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
             <xs:sequence>
-                <xs:element minOccurs="0" ref="error-prefix"/>
-                <xs:element minOccurs="0" ref="error-suffix"/>
-                <xs:element minOccurs="0" ref="success-prefix"/>
-                <xs:element minOccurs="0" ref="success-suffix"/>
-                <xs:element minOccurs="0" ref="message-prefix"/>
-                <xs:element minOccurs="0" ref="message-suffix"/>
-                <xs:element minOccurs="0" ref="default-message"/>
+                <xs:element minOccurs="0" ref="error-prefix" />
+                <xs:element minOccurs="0" ref="error-suffix" />
+                <xs:element minOccurs="0" ref="success-prefix" />
+                <xs:element minOccurs="0" ref="success-suffix" />
+                <xs:element minOccurs="0" ref="message-prefix" />
+                <xs:element minOccurs="0" ref="message-suffix" />
+                <xs:element minOccurs="0" ref="default-message" />
                 <xs:choice minOccurs="0" maxOccurs="unbounded">
-                    <xs:element ref="results-to-map"/>
-                    <xs:element ref="result-to-field"/>
-                    <xs:element ref="result-to-request"/>
-                    <xs:element ref="result-to-session"/>
-                    <xs:element ref="result-to-result"/>
+                    <xs:element ref="results-to-map" />
+                    <xs:element ref="result-to-field" />
+                    <xs:element ref="result-to-request" />
+                    <xs:element ref="result-to-session" />
+                    <xs:element ref="result-to-result" />
                 </xs:choice>
             </xs:sequence>
-            <xs:attributeGroup ref="attlist.call-service"/>
-        </xs:complexType>
-    </xs:element>
-    <xs:attributeGroup name="attlist.call-service">
-        <xs:attribute type="xs:string" name="service-name" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Name of the service to call.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="in-map-name">
-            <xs:annotation>
-                <xs:documentation>
-                    Optional name of a map in the method environment to use as the input map.
-                    If you're not going to pass any parameters to the service than you can just
-                    leave off the in-map name, although typically in a service tag you will see
-                    a service-name and the in-map-name passed in.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute name="include-user-login">
-            <xs:annotation>
-                <xs:documentation>
-                    Include-user-login by default will include the user login
-                    so if there is a user login for the current simple-method
-                    it will pass that in to the service. If you don't want it to
-                    pass that in you can just set this to false.
-                    Defaults to "true".
-                </xs:documentation>
-            </xs:annotation>
-            <xs:simpleType>
+            <xs:attribute type="xs:string" name="service-name" use="required">
                 <xs:annotation>
+                    <xs:documentation>
+                        Name of the service to call.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: constant, expression.
+                    </xs:documentation>
                 </xs:annotation>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="true"/>
-                    <xs:enumeration value="false"/>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute name="break-on-error">
-            <xs:annotation>
-                <xs:documentation>
-                    If there's an error in the service by default
-                    it will stop the current simple-method and return an
-                    error message that came from the service it called. If
-                    you don't want it to when there's an error you can just
-                    set that to false.
-                    Defaults to "true".
-                </xs:documentation>
-            </xs:annotation>
-            <xs:simpleType>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="true"/>
-                    <xs:enumeration value="false"/>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="error-code">
-            <xs:annotation>
-                <xs:documentation>
-                    Defaults to "error".
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="success-code">
-            <xs:annotation>
-                <xs:documentation>
-                    Defaults to "success".
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute name="require-new-transaction">
-            <xs:annotation>
-                <xs:documentation>
-                    Defines if the simple-method requires a new transaction or not.
-                    Defaults to "false".
-                </xs:documentation>
-            </xs:annotation>
-            <xs:simpleType>
-                <xs:restriction base="xs:token">
-                    <xs:enumeration value="true"/>
-                    <xs:enumeration value="false"/>
-                </xs:restriction>
-            </xs:simpleType>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="transaction-timeout">
-            <xs:annotation>
-                <xs:documentation>
-                    Defines the timeout for the transaction, in seconds.
-                    Defaults to the value set in the service definition which in turn defaults to the setting
-                    in the TransactionFactory being used (typically 60 seconds).
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="in-map-name">
+                <xs:annotation>
+                    <xs:documentation>
+                        A map in the method environment to use as the input map.
+                        If you're not going to pass any parameters to the service than you can just
+                        omit the in-map attribute, although typically in a service element you will see
+                        the in-map-name attribute included.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="include-user-login">
+                <xs:annotation>
+                    <xs:documentation>
+                        Include the current UserLogin GenericValue in the called service IN attributes.
+                        Defaults to "true".
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:annotation>
+                    </xs:annotation>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="true" />
+                        <xs:enumeration value="false" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute name="break-on-error">
+                <xs:annotation>
+                    <xs:documentation>
+                        Halt script execution if the called service returns an error.
+                        Defaults to "true".
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="true" />
+                        <xs:enumeration value="false" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="error-code">
+                <xs:annotation>
+                    <xs:documentation>
+                        The error code returned by the called service. The error code is copied to the script result.
+                        Defaults to the enclosing simple-method &quot;default-error-code&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="success-code">
+                <xs:annotation>
+                    <xs:documentation>
+                        The success code returned by the called service. The success code is copied to the script result.
+                        Defaults to the enclosing simple-method &quot;default-success-code&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="require-new-transaction">
+                <xs:annotation>
+                    <xs:documentation>
+                        Require a new transaction for the called service.
+                        Defaults to "false".
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:token">
+                        <xs:enumeration value="true" />
+                        <xs:enumeration value="false" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="transaction-timeout">
+                <xs:annotation>
+                    <xs:documentation>
+                        The timeout for the new transaction, in seconds.
+                        Defaults to the value set in the service definition.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: constant.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+        </xs:complexType>
+    </xs:element>
     <xs:element name="error-prefix">
         <xs:annotation>
             <xs:documentation>
@@ -768,132 +769,134 @@ under the License.
     <xs:element name="results-to-map">
         <xs:annotation>
             <xs:documentation>
-                results-to-map will take all of the results of the service,
-                the outgoing maps from the service and put them in a map of the given map-name.
+                Copies the called service result Map to the specified field.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
-            <xs:attributeGroup ref="attlist.results-to-map"/>
+            <xs:attribute type="xs:string" name="map-name" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the target field.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.results-to-map">
-        <xs:attribute type="xs:string" name="map-name" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Name of a map where results will be put in.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="result-to-field">
         <xs:annotation>
             <xs:documentation>
-                Specify the name of the field in the result and then the name of the field in the context you want to put it in,
-                and optionally the  name in the map.
-
-                There's a field-map there. If you don't specify a field-name then the result-name will be used for the field-name,
-                that's the name of the variable that will be created in the current context for the value of that result.
+                Copies the called service OUT attribute to the specified field.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
-            <xs:attributeGroup ref="attlist.result-to-field"/>
+            <xs:attribute type="xs:string" name="result-name" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the called service OUT attribute.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="field">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of target field. Defaults to the &quot;result-name&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.result-to-field">
-        <xs:attribute type="xs:string" name="result-name" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Name of the result. May be used for the field-name. If you don't specify a field-name,
-                    this will be name of the variable that will be created in the current context for the value of that result.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="field">
-            <xs:annotation><xs:documentation>Optional name of target field. If empty will default to result-name.</xs:documentation></xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="result-to-request">
         <xs:annotation>
             <xs:documentation>
-                result-to-request is event specific.
-                It takes the result with the given name and puts it in a request attribute with the given name here.
-                Again the request-name is optional.
-                If you leave it off then it will put it in an attribute with the name of the result-name.
+                Copies the called service OUT attribute to the specified request attribute.
+                Valid only when the script is run in an event.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
-            <xs:attributeGroup ref="attlist.result-to-request"/>
+            <xs:attribute type="xs:string" name="result-name" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the called service OUT attribute.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="request-name">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the target request attribute. Defaults to the &quot;result-name&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.result-to-request">
-        <xs:attribute type="xs:string" name="result-name" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Name of the result. May be used for the request attribute name. If you don't specify a request-name ,
-                    that's the name of the request attribute that will be created for the value of that result.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="request-name">
-            <xs:annotation>
-                <xs:documentation>
-                    Optionnal request name.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="result-to-session">
         <xs:annotation>
             <xs:documentation>
-                Specify the name of the session attribute that you want it to put the value in.
-                If you don't specify one it will use the result-name.
+                Copies the called service OUT attribute to the specified session attribute.
+                Valid only when the script is run in an event.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
-            <xs:attributeGroup ref="attlist.result-to-session"/>
+            <xs:attribute type="xs:string" name="result-name" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the called service OUT attribute.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute type="xs:string" name="session-name">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of the target session attribute. Defaults to the &quot;result-name&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: expression.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
         </xs:complexType>
     </xs:element>
-    <xs:attributeGroup name="attlist.result-to-session">
-        <xs:attribute type="xs:string" name="result-name" use="required">
-            <xs:annotation>
-                <xs:documentation>
-                    Name of the result. May be used for the session attribute name. If you don't specify a session-name,
-                    that's the name of the session attribute that will be created for the value of that result.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-        <xs:attribute type="xs:string" name="session-name">
-            <xs:annotation>
-                <xs:documentation>
-                    Optional session name.
-                </xs:documentation>
-            </xs:annotation>
-        </xs:attribute>
-    </xs:attributeGroup>
     <xs:element name="result-to-result">
         <xs:annotation>
             <xs:documentation>
                 Copies service OUT attributes from a called service to the calling service's OUT attributes.
-                Only valid when the script is run in a service.
+                This element can also be used to copy the called service OUT attributes to the return result of
+                a simple-method called as a function.
                 &lt;br/&gt;&lt;br/&gt;
                 Note that the attribute names are somewhat confusing:
-                result-name is the name of the OUT attribute of the called service, and
-                service-result-name is the name of the OUT attribute of the calling service.
-                In other words, copy the OUT attribute FROM result-name TO service-result-name.
+                &quot;result-name&quot; is the name of the OUT attribute of the called service, and
+                &quot;service-result-name&quot; is the name of the OUT attribute of the calling service.
+                In other words, copy the OUT attribute FROM &quot;result-name&quot; TO &quot;service-result-name&quot;.
             </xs:documentation>
         </xs:annotation>
         <xs:complexType>
             <xs:attribute type="xs:string" name="result-name" use="required">
                 <xs:annotation>
                     <xs:documentation>
-                        Name of the field in the result of this service call that the value comes FROM.
+                        Name of the called service OUT attribute.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Required. Attribute type: expression.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>
             <xs:attribute type="xs:string" name="service-result-name">
                 <xs:annotation>
                     <xs:documentation>
-                        Name of the field in the result of this simple-method called as a service where the value goes TO.
+                        Name of the calling service OUT attribute (or function return result).
+                        Defaults to the &quot;result-name&quot; attribute value.
+                        &lt;br/&gt;&lt;br/&gt;
+                        Optional. Attribute type: expression.
                     </xs:documentation>
                 </xs:annotation>
             </xs:attribute>

Modified: ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/callops/CallService.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/callops/CallService.java?rev=1339079&r1=1339078&r2=1339079&view=diff
==============================================================================
--- ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/callops/CallService.java (original)
+++ ofbiz/trunk/framework/minilang/src/org/ofbiz/minilang/method/callops/CallService.java Wed May 16 09:20:03 2012
@@ -34,8 +34,10 @@ import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.base.util.UtilXml;
 import org.ofbiz.base.util.collections.FlexibleMapAccessor;
 import org.ofbiz.base.util.collections.FlexibleServletAccessor;
+import org.ofbiz.base.util.string.FlexibleStringExpander;
 import org.ofbiz.entity.GenericValue;
 import org.ofbiz.minilang.MiniLangException;
+import org.ofbiz.minilang.MiniLangValidate;
 import org.ofbiz.minilang.SimpleMethod;
 import org.ofbiz.minilang.method.MethodContext;
 import org.ofbiz.minilang.method.MethodOperation;
@@ -75,6 +77,12 @@ public final class CallService extends M
 
     public CallService(Element element, SimpleMethod simpleMethod) throws MiniLangException {
         super(element, simpleMethod);
+        if (MiniLangValidate.validationOn()) {
+            MiniLangValidate.attributeNames(simpleMethod, element, "serviceName", "in-map-name", "include-user-login", "break-on-error", "error-code", "require-new-transaction", "transaction-timeout", "success-code");
+            MiniLangValidate.constantAttributes(simpleMethod, element, "include-user-login", "break-on-error", "error-code", "require-new-transaction", "transaction-timeout", "success-code");
+            MiniLangValidate.expressionAttributes(simpleMethod, element, "service-name", "in-map-name");
+            MiniLangValidate.childElements(simpleMethod, element, "error-prefix", "error-suffix", "success-prefix", "success-suffix", "message-prefix", "message-suffix", "default-message", "results-to-map", "result-to-field", "result-to-request", "result-to-session", "result-to-result");
+        }
         serviceName = element.getAttribute("service-name");
         inMapFma = FlexibleMapAccessor.getInstance(element.getAttribute("in-map-name"));
         includeUserLogin = !"false".equals(element.getAttribute("include-user-login"));
@@ -87,7 +95,7 @@ public final class CallService extends M
             try {
                 timeout = Integer.parseInt(timeoutStr);
             } catch (NumberFormatException e) {
-                Debug.logWarning(e, "Setting timeout to 0 (default)", module);
+                MiniLangValidate.handleError("Exception thrown while parsing transaction-timeout attribute: " + e.getMessage(), simpleMethod, element);
                 timeout = 0;
             }
         }
@@ -154,6 +162,9 @@ public final class CallService extends M
 
     @Override
     public boolean exec(MethodContext methodContext) throws MiniLangException {
+        if (methodContext.isTraceOn()) {
+            outputTraceMessage(methodContext, "Begin call-service.");
+        }
         String serviceName = methodContext.expandString(this.serviceName);
         String errorCode = this.errorCode;
         if (errorCode.isEmpty()) {
@@ -172,7 +183,7 @@ public final class CallService extends M
             methodContext.removeEnv(simpleMethod.getEventErrorMessageName());
             methodContext.removeEnv(simpleMethod.getEventEventMessageName());
             methodContext.removeEnv(simpleMethod.getEventResponseCodeName());
-        } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+        } else {
             methodContext.removeEnv(simpleMethod.getServiceErrorMessageName());
             methodContext.removeEnv(simpleMethod.getServiceSuccessMessageName());
             methodContext.removeEnv(simpleMethod.getServiceResponseMessageName());
@@ -197,45 +208,72 @@ public final class CallService extends M
             if (this.transactionTimeout >= 0) {
                 timeout = this.transactionTimeout;
             }
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Invoking service \"" + serviceName + "\", require-new-transaction = " + requireNewTransaction + ", transaction-timeout = " + timeout + ", IN attributes:", inMap.toString());
+            }
             result = methodContext.getDispatcher().runSync(serviceName, inMap, timeout, requireNewTransaction);
         } catch (GenericServiceException e) {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Service engine threw an exception: " + e.getMessage());
+            }
             String errMsg = "ERROR: Could not complete the " + simpleMethod.getShortDescription() + " process [problem invoking the [" + serviceName + "] service with the map named [" + inMapFma + "] containing [" + inMap + "]: " + e.getMessage() + "]";
             Debug.logError(e, errMsg, module);
             if (breakOnError) {
                 if (methodContext.getMethodType() == MethodContext.EVENT) {
                     methodContext.putEnv(simpleMethod.getEventErrorMessageName(), errMsg);
                     methodContext.putEnv(simpleMethod.getEventResponseCodeName(), errorCode);
-                } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+                } else {
                     methodContext.putEnv(simpleMethod.getServiceErrorMessageName(), errMsg);
                     methodContext.putEnv(simpleMethod.getServiceResponseMessageName(), errorCode);
                 }
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "break-on-error set to \"true\", halting script execution. End call-service.");
+                }
                 return false;
             } else {
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "End call-service.");
+                }
                 return true;
             }
         }
         if (resultsToMapList != null) {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Processing " + resultsToMapList.size() + " <results-to-map> elements.");
+            }
             for (String mapName : resultsToMapList) {
                 methodContext.putEnv(mapName, UtilMisc.makeMapWritable(result));
             }
         }
         if (resultToFieldList != null) {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Processing " + resultToFieldList.size() + " <result-to-field> elements.");
+            }
             for (ResultToField rtfDef : resultToFieldList) {
                 rtfDef.exec(methodContext, result);
             }
         }
         if (resultToResultList != null) {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Processing " + resultToResultList.size() + " <result-to-result> elements.");
+            }
             for (ResultToResult rtrDef : resultToResultList) {
                 rtrDef.exec(methodContext, result);
             }
         }
         if (methodContext.getMethodType() == MethodContext.EVENT) {
             if (resultToRequestList != null) {
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "Processing " + resultToRequestList.size() + " <result-to-request> elements.");
+                }
                 for (ResultToRequest rtrDef : resultToRequestList) {
                     rtrDef.exec(methodContext, result);
                 }
             }
             if (resultToSessionList != null) {
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "Processing " + resultToSessionList.size() + " <result-to-session> elements.");
+                }
                 for (ResultToSession rtsDef : resultToSessionList) {
                     rtsDef.exec(methodContext, result);
                 }
@@ -268,7 +306,7 @@ public final class CallService extends M
                     }
                     methodContext.putEnv(simpleMethod.getEventErrorMessageListName(), errorMessageList);
                 }
-            } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+            } else {
                 ServiceUtil.addErrors(UtilMisc.<String, String> getListFromMap(methodContext.getEnvMap(), this.simpleMethod.getServiceErrorMessageListName()), UtilMisc.<String, String, Object> getMapFromMap(methodContext.getEnvMap(), this.simpleMethod.getServiceErrorMessageMapName()), result);
                 Debug.logError(new Exception(errorMessage), module);
             }
@@ -277,7 +315,7 @@ public final class CallService extends M
         if (UtilValidate.isNotEmpty(successMessage)) {
             if (methodContext.getMethodType() == MethodContext.EVENT) {
                 methodContext.putEnv(simpleMethod.getEventEventMessageName(), successMessage);
-            } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+            } else {
                 methodContext.putEnv(simpleMethod.getServiceSuccessMessageName(), successMessage);
             }
         }
@@ -285,28 +323,38 @@ public final class CallService extends M
         if (UtilValidate.isEmpty(errorMessage) && UtilValidate.isEmpty(errorMessageList) && UtilValidate.isEmpty(successMessage) && UtilValidate.isNotEmpty(defaultMessageStr)) {
             if (methodContext.getMethodType() == MethodContext.EVENT) {
                 methodContext.putEnv(simpleMethod.getEventEventMessageName(), defaultMessageStr);
-            } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+            } else {
                 methodContext.putEnv(simpleMethod.getServiceSuccessMessageName(), defaultMessageStr);
             }
         }
-        // handle the result
         String responseCode = result.containsKey(ModelService.RESPONSE_MESSAGE) ? (String) result.get(ModelService.RESPONSE_MESSAGE) : successCode;
         if (errorCode.equals(responseCode)) {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Service returned an error.");
+            }
             if (breakOnError) {
                 if (methodContext.getMethodType() == MethodContext.EVENT) {
                     methodContext.putEnv(simpleMethod.getEventResponseCodeName(), responseCode);
-                } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+                } else {
                     methodContext.putEnv(simpleMethod.getServiceResponseMessageName(), responseCode);
                 }
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "break-on-error set to \"true\", halting script execution. End call-service.");
+                }
                 return false;
             } else {
-                // avoid responseCode here since we are ignoring the error
+                if (methodContext.isTraceOn()) {
+                    outputTraceMessage(methodContext, "End call-service.");
+                }
                 return true;
             }
         } else {
+            if (methodContext.isTraceOn()) {
+                outputTraceMessage(methodContext, "Service ran successfully. End call-service.");
+            }
             if (methodContext.getMethodType() == MethodContext.EVENT) {
                 methodContext.putEnv(simpleMethod.getEventResponseCodeName(), responseCode);
-            } else if (methodContext.getMethodType() == MethodContext.SERVICE) {
+            } else {
                 methodContext.putEnv(simpleMethod.getServiceResponseMessageName(), responseCode);
             }
             return true;
@@ -315,8 +363,7 @@ public final class CallService extends M
 
     @Override
     public String expandedString(MethodContext methodContext) {
-        // TODO: something more than a stub/dummy
-        return this.rawString();
+        return FlexibleStringExpander.expandString(toString(), methodContext.getEnvMap());
     }
 
     public String getServiceName() {
@@ -325,8 +372,18 @@ public final class CallService extends M
 
     @Override
     public String rawString() {
-        // TODO: something more than the empty tag
-        return "<call-service/>";
+        return toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("<call-service ");
+        sb.append("service-name=\"").append(this.serviceName).append("\" ");
+        if (!this.inMapFma.isEmpty()) {
+            sb.append("in-map-name=\"").append(this.inMapFma).append("\" ");
+        }
+        sb.append("/>");
+        return sb.toString();
     }
 
     public static final class CallServiceFactory implements Factory<CallService> {
@@ -341,19 +398,20 @@ public final class CallService extends M
 
     private final class ResultToField {
         private final FlexibleMapAccessor<Object> fieldFma;
-        private final String resultName;
+        private final FlexibleMapAccessor<Object> resultFma;
 
         private ResultToField(Element element) {
-            resultName = element.getAttribute("result-name");
+            resultFma = FlexibleMapAccessor.getInstance(element.getAttribute("result-name"));
             String fieldAttribute = element.getAttribute("field");
             if (fieldAttribute.isEmpty()) {
-                fieldAttribute = resultName;
+                fieldFma = resultFma;
+            } else {
+                fieldFma = FlexibleMapAccessor.getInstance(fieldAttribute);
             }
-            fieldFma = FlexibleMapAccessor.getInstance(fieldAttribute);
         }
 
         private void exec(MethodContext methodContext, Map<String, Object> resultMap) {
-            fieldFma.put(methodContext.getEnvMap(), resultMap.get(resultName));
+            fieldFma.put(methodContext.getEnvMap(), resultFma.get(resultMap));
         }
     }