This is a small guide for everybody involved in converting the Mini Language into Groovy.
Content
Anchor |
---|
groovyDsl | groovyDsl | Groovy DSL (dynamic scripting library) Section |
---|
Column |
---|
|
How to get Groovy support in your IDE |
Column |
---|
The following paragraph is for Eclipse users.
It is possible to get Groovy support in Eclipse by converting the loaded project to a Groovy Project. The project itself will work as before.
To do this just follow these few steps:
- Right-click on the project that has to be converted
- Click on "Configure"
- Click on "Convert to Groovy Project"
Eclipse will automatically load the file OfbizDslDescriptorForEclipse.dsld
, in which the known fields and methods used in Groovy Scripts are defined.
Section |
---|
Column |
---|
title | Why is this important? |
---|
|
This tutorial is directly linked to the efforts of converting all scripts in Mini Language to newer Groovy Scripts. All of this is done, because Groovy is much more readable and easier to review, more up to date and many other reasons, which can be found here: https://markmail.org/message/n2ybsyet5r4xcqsz To contribute, or just be up to date with the current process, you can look at the existing JIRA issue Jira |
---|
server | ASF JIRA |
---|
serverId | 5aa69414-a9e9-3523-82ec-879b028fb15b |
---|
key | OFBIZ-9350 |
---|
|
|
Content
Groovy DSL (dynamic scripting library)property name: 'parameters', type : 'java.util.Map'
These are the parameters given to the Groovy Script, when it is called as a service. It is equivalent to Map<String, Object> context
in the Java-Service-Definition.
property name: 'context', type: 'java.util.Map'
More parameters, which are, for example, given through a screen or another Groovy Script. This is important when the script is called through an action
segment of a screen.
property name: 'delegator', type: 'org.apache.ofbiz.entity.Delegator'
Normal instance of the Delegator
, which is used for special database access.
property name: 'dispatcher', type: 'org.apache.ofbiz.service.LocalDispatcher'
Normal instance of the LocalDispatcher
, which is used to call services and other service-like operations.
property name: 'security', type: 'org.apache.ofbiz.security.Security'
Normal instance of the Security
-Interface with which permission checks are done.
Section |
---|
Column |
---|
| Known Methods | How to get Groovy support in your IDE |
Column |
---|
The following paragraph is for Eclipse users.It is possible to get Groovy support in Eclipse by converting the loaded project to a Groovy Project. The project itself will work as before. To do this just follow these few steps: - Right-click on the project that has to be converted
- Click on "Configure"
- Click on "Convert to Groovy Project"
Eclipse will automatically load the file OfbizDslDescriptorForEclipse.dsld , in which the known fields and methods used in Groovy Scripts are defined.
|
|
Section |
---|
Column |
---|
property name: 'parameters
| Column |
---|
Helping method to call services instead of dispatcher.runSync(serviceName, inputMap) . Also possible: run service: serviceName, with: inputMap method name: 'makeValue', type: 'java.util.Map', params: [entityName: 'String']
Helping method to make a GenericValue instead of delegator.makeValue(entityName) . Creates an empty GenericValue of the specific entity. method name: 'findOne', type : 'java.util.Map' , params: [entityName: 'String', inputMap: 'java.util.Map']Helping method to find one GenericValue in the database. Used instead of delegator.findOne(entityName, inputMap)
These are the parameters given to the Groovy Script, when it is called as a service. It is equivalent to Map<String, Object> context in the Java-Service-Definition.
property name: 'context method name: 'findList', type: 'java.util. ListMap' , params: [entityName: 'String', inputMap: 'java.util.Map']Helping method to find many GenericValue in the database. Used instead of delegator.findList(entityName, inputMap, null, null, null, false) method name: 'selectMore parameters, which are, for example, given through a screen or another Groovy Script. This is important when the script is called through an action segment of a screen. property name: 'delegator', type: 'org.apache.ofbiz.entity. util.EntityQuery', params: [entity: 'java.util.Set']Delegator'
Normal instance of the Delegator , which is used for special database access. property name: 'dispatcher', type: 'org.apache.ofbiz.service.LocalDispatcher'
Normal instance of the LocalDispatcher , which is used to call services and other service-like operations. property name: 'security
Helping method used instead of EntityQuery.use(delegator).select(...) method name: 'select', type: 'org.apache.ofbiz. entitysecurity. util.EntityQuery', params: [entity: 'String...']As above.Security'
Normal instance of the Security -Interface with which permission checks are done.
|
|
Section |
---|
Column |
---|
fromorg.apache.ofbiz.entity.EntityQueryentitylangObject Helping method used to call services instead of EntityQuerydispatcher. userunSync( delegator).from(...)serviceName, inputMap) . Also possible: run service: serviceName, with: inputMap method name: ' successmakeValue', type: ' defjava.util.Map', params: [ messageentityName: 'String']
Helping method used to make a GenericValue instead of ServiceUtildelegator. returnSuccessmakeValue( message)entityName) . Creates an empty GenericValue of the specific entity. failuremessageHelping method used instead of ServiceUtil.returnFailure(messageto find one GenericValue in the database. Used instead of delegator.findOne(entityName, inputMap) errordefmessageHelping method used to find many GenericValue in the database. Used instead of ServiceUtildelegator. returnError(messagefindList(entityName, inputMap, null, null, null, false) method name: ' logInfoselect', type: ' void', params: [message: 'Stringorg.apache.ofbiz.entity.util.EntityQuery', params: [entity: 'java.util.Set']
Helping method used instead of Debug.logInfo(message, fileName EntityQuery.use(delegator).select(...) method name: ' logWarningselect', type: ' voidorg.apache.ofbiz.entity.util.EntityQuery', params: [ messageentity: 'String...']
Helping method used instead of Debug.logWarning(message, fileName) As above. method name: ' logErrorfrom', type: ' void'org.apache.ofbiz.entity.util.EntityQuery', params: [ messageentity: ' Stringjava.lang.Object']
Helping method used instead of Debug.logError(message, fileName EntityQuery.use(delegator).from(...) method name: ' logVerbosesuccess', type: ' voiddef', params: [message: 'String']
Helping method used instead of DebugServiceUtil. logVerbosereturnSuccess(message , fileName) The actual definition of the methods can be found in /framework/service/src/main/java/org/apache/ofbiz/service/engine/GroovyBaseScript.groovy , the variables dctx , dispatcher and delegator are set in the file GroovyEngine.java which can be found in the same location. |
|
Section |
---|
Anchor |
---|
services | services | Services From MiniLang to Groovy
To see additional examples and finished conversions, which may help with occurring questions, click: Jira |
---|
server | ASF JIRA |
---|
serverId | 5aa69414-a9e9-3523-82ec-879b028fb15b |
---|
key | OFBIZ-9350 |
---|
|
There is a chance that a similar case has already been converted.)
method name: 'failure', type: 'java.util.Map', params: [message: 'String']
Helping method used instead of ServiceUtil.returnFailure(message) method name: 'error', type: 'def', params: [message: 'String']
Helping method used instead of ServiceUtil.returnError(message) method name: 'logInfo', type: 'void', params: [message: 'String']
Helping method used instead of Debug.logInfo(message, fileName) method name: 'logWarning', type: 'void', params: [message: 'String']
Helping method used instead of Debug.logWarning(message, fileName) method name: 'logError', type: 'void', params: [message: 'String']
Helping method used instead of Debug.logError(message, fileName) method name: 'logVerbose', type: 'void', params: [message: 'String']
Helping method used instead of Debug.logVerbose(message, fileName)
The actual definition of the methods can be found in /framework/service/src/main/java/org/apache/ofbiz/service/engine/GroovyBaseScript.groovy , the variables dctx , dispatcher and delegator are set in the file GroovyEngine.java which can be found in the same location. |
|
Services From MiniLang to Groovy
To see additional examples and finished conversions, which may help with occurring questions, click:
Jira |
---|
server | ASF JIRA |
---|
serverId | 5aa69414-a9e9-3523-82ec-879b028fb15b |
---|
key | OFBIZ-9350 |
---|
|
There is a chance that a similar case has already been converted.
IMPORTANT: When a simple-method ends, it will automatically at least return a success-map.
All the Groovy Services have to return success
at least, too.
Code Block |
---|
|
return success() |
Anchor |
---|
| gettingStarted |
---|
| gettingStarted |
---|
|
Getting startedMiniLang files consist of services, which, in most cases, implement services.
The get converted to Groovy like the following:
Code Block |
---|
<!-- This is MiniLang -->
<simple-method method-name="createProductCategory" short-description="Create an ProductCategory">
<!-- Code -->
</simple-method>
// This is the converted Groovy equivalent
/**
* Create an ProductCategory
*/
def createProductCategory() {
// Code
} |
It will be useful for future developers, and everybody who has to check something in the code, to put at least the short-description
as the new Groovydoc. This will hopefully more or less explain, what the method should or shouldn't do.
If the short-description
isn't helpful enough, feel free complete it.
The structure of if
and else
in MiniLang is a little different than the one from Groovy or Java and can be a bit confusing when first seen, so here is an example:
Code Block |
---|
<if-empty field="parameters.productCategoryId">
<sequenced-id sequence-name="ProductCategory" field="newEntity.productCategoryId"/>
<else>
<set field="newEntity.productCategoryId" from-field="parameters.productCategoryId"/>
<check-id field="newEntity.productCategoryId"/>
<check-errors/>
</else>
</if-empty> |
Notice, that the else
always starts before the if
-tag is closed, but sometimes isn't indented as one would expect it.
When navigating through bigger if
-phrases, the navigation itself will be much easier through just clicking in the opening or closing if
-tag; Eclipse will automatically mark the matching opening or closing if
-tag for you.
There are two possibilities to initialize a field/variable in Groovy.
To define a field/variable with its correct typing:
Code Block |
---|
String fieldName = "value" |
To just "define" a field/variable. The IDE you are working with may not recognize the typing, but OFBiz can work with it:
Code Block |
---|
def fieldName = "value" |
Anchor |
---|
| checkingFields |
---|
| checkingFields |
---|
|
Checking Fields Code Block |
---|
<if-empty field="fieldName"></if-empty>
// checks if fieldName is existent and/or empty
if (!fieldName) {} |
Code Block |
---|
<if-empty field="fieldName.property"></if-empty>
// fieldName has to be existent, property doesn't need to
// if known, that property does exist, the ? can be left out
if (!fieldName?.property) {}
// CAUTION: every query like this in Groovy evaluates to a Boolean type
// everything that is empty or false will turn into false:
// null, [], [:], "", false -> false
// if you want to check if the field really is empty
if (UtilValidate.isEmpty(fieldName)) {} |
Code Block |
---|
<if>
<condition>
<or>
<if-empty field="field1"/>
<if-empty field="field2"/>
</or>
</condition>
<then>
<!-- code in if -->
</then>
<else>
<!-- code in else -->
</else>
</if>
if (!field1 || !field2) {
// code in if
} else {
// code in else
} |
Code Block |
---|
<if-compare-field field="product.primaryProductCategoryId" to-field="parameters.productCategoryId" operator="equals">
<!-- code -->
</if-compare-field>
// this will even work, if product is not existent or null
if (UtilValidate.areEqual(product?.primaryProductCategoryId, parameters.productCategoryId)) {
// code
} |
Code Block |
---|
<if-instance-of field="parameters.categories" class="java.util.List"></if-instance-of>
if (parameters.categories instanceof java.util.List) {} |
Anchor |
---|
| settingFields |
---|
| settingFields |
---|
|
Setting Fields Code Block |
---|
<set field="fieldName" value="value"/>
// if fieldName is not initialized
String fieldName = "value"
// if fieldName is initialized
fieldName = "value" |
Code Block |
---|
<set field="otherFieldName.property" value="value"/>
<set field="otherFieldName.otherProperty" value="true" type="Boolean"/>
<set field="otherFieldName.otherProperty" from-field="parameters.property/>
// if otherFieldName is not yet initialized, you have to do it first
// MiniLang does that automatically
Map otherFieldName = [:] // empty Map
// now put the values in
otherFieldName = [
property: "value",
otherProperty: true
]
// or the less efficient way
otherFieldName.property = "value"
otherFieldName.otherProperty = true
// it is possible to put different values in later:
otherFieldName.property = parameters.property |
Code Block |
---|
<set field="thisFieldName" value="${groovy: []}" type="List"/>
// this is easier in Groovy
List thisFieldName = [] |
Code Block |
---|
<property-to-field resource="CommonUiLabels" property="CommonGenericPermissionError" field="failMessage"/>
<!-- there are different cases of this, which are not distinguished in MiniLang -->
<property-to-field resource="general.properties" property="currency.uom.id.default" field="parameters.rateCurrencyUomId"/>
// The property-to-field minilang method treated retrieved property as input to FlexibleStringExpander#expandString and passed it the
// current environment map. This behaviour can be replicated by passing the variables Map to UtilProperties#getMessage.
String failMessage = UtilProperties.getMessage("CommonUiLabels", "CommonGenericPermissionError", binding.variables, parameters.locale)
// in Groovy there can is a difference for the second case
parameters.rateCurrencyUomId = UtilProperties.getPropertyValue('general.properties', 'currency.uom.id.default') |
Code Block |
---|
<clear-field field="product.primaryProductCategoryId"/>
product.primaryProductCategoryId = null |
Anchor |
---|
| startingServices |
---|
| startingServices |
---|
|
Starting Services Code Block |
---|
<set field="relatedCategoryContext.parentProductCategoryId" from-field="defaultTopCategoryId"/>
<call-service service-name="getRelatedCategories" in-map-name="relatedCategoryContext">
<result-to-field result-name="categories" field="resCategories"/>
</call-service>
def relatedCategoryContext = [parentProductCategoryId: defaultTopCategoryId]
def serviceResult = run service: "getRelatedCategoryies", with: relatedCategoryContext
def resCategories = serviceResult.categories
// if it is not too confusing to read you can leave out the extra variable
run service: "getRelatedCategoryies", with: [parentProductCategoryId: defaultTopCategoryId] |
Code Block |
---|
<set-service-fields service-name="productCategoryGenericPermission" map="parameters" to-map="productCategoryGenericPermissionMap"/>
<call-service service-name="productCategoryGenericPermission" in-map-name="productCategoryGenericPermissionMap">
<results-to-map map-name="genericResult"/>
</call-service>
// instead of setting the service fields from parameters, it is possible to run the service with the parameters map
Map genericResult = run service: "productCategoryGenericPermission", with: parameters |
Anchor |
---|
| preparingServiceResults |
---|
| preparingServiceResults |
---|
|
Preparing Service Results Code Block |
---|
<field-to-result field="fieldBudgetId" result-name="budgetId"/>
// MiniLang knows this implizitly
def result = success()
result.budgetId = fieldBudgetId
return result |
Anchor |
---|
| databaseCommunication |
---|
| databaseCommunication |
---|
|
Database Communication Code Block |
---|
<make-value entity-name="FinAccountTrans" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<set-pk-fields map="parameters" value-field="newEntity"/>
// this is the easy way
GenericValue newEntity = makeValue("FinAccountTrans", parameters)
// this is also possible
GenericValue newEntity = makeValue("FinAccountTrans")
newEntity.setPKFields(parameters)
newEntity.setNonPKFields(parameters) |
Code Block |
---|
<entity-and entity-name="BudgetStatus" list="budgetStatuses">
<field-map field-name="budgetId" from-field="parameters.budgetId"/>
<order-by field-name="-statusDate"/>
</entity-and>
// this can also be done in one line, but it can easily become unreadable
def budgetStatuses = from("BudgetStatus")
.where("budgetId", paramters.budgetId)
.orderBy("-statusDate")
.queryList() |
Code Block |
---|
<entity-one entity-name="StatusValidChange" value-field="statusValidChange">
<field-map field-name="statusId" from-field="budgetStatus.statusId"/>
<field-map field-name="statusIdTo" from-field="parameters.statusId"/>
</entity-one>
<!-- entity-one can be called without child elements, too -->
<entity-one entity-name="Product" value-field="product" auto-field-map="true"/>
// MiniLang has false set for useCache as the default value
statusValidChange = findOne("StatusValidChange", [statusId: budgetStatus.statusId, statusIdTo: parameters.statusId], false)
// this is also possible
statusValidChange = from("StatusValidChange")
.where("statusId", budgetStatus.statusId, "statusIdTo", parameters.statusId)
.queryOne()
// if there are no child elements, this can be used
GenericValue product = from("Product").where(parameters).queryOne() |
Code Block |
---|
<find-by-primary-key entity-name="ProductCategoryMember" map="lookupPKMap" value-field="lookedUpValue"/>
GenericValue lookedUpValue = findOne("ProductCategoryMember", lookupPKMap, false)
// this is also possible
lookedUpValue = from("ProductCategoryRole")
.where(lookupPKMap)
.queryOne() |
Code Block |
---|
<entity-condition entity-name="ProductCategoryContentAndInfo" list="productCategoryContentAndInfoList" filter-by-date="true" use-cache="true">
<condition-list combine="and">
<condition-expr field-name="productCategoryId" from-field="productCategoryList.productCategoryId"/>
<condition-expr field-name="prodCatContentTypeId" value="ALTERNATIVE_URL"/>
</condition-list>
<order-by field-name="-fromDate"/>
</entity-condition>
<!-- entity-condition can also be used with the "or" operator -->
<entity-condition entity-name="ProdCatalogCategory" list="prodCatalogCategoryList" filter-by-date="true">
<condition-list combine="and">
<condition-expr field-name="productCategoryId" from-field="parameters.productCategoryId"/>
<condition-list combine="or">
<condition-expr field-name="prodCatalogCategoryTypeId" value="PCCT_VIEW_ALLW"/>
<condition-expr field-name="prodCatalogCategoryTypeId" value="PCCT_PURCH_ALLW"/>
</condition-list>
</condition-list>
</entity-condition>
// the Groovy methods use the "and" and "equals" operator as default values
List productCategoryContentAndInfoList = from("ProductCategoryContentAndInfo")
.where("productCategoryId", productCategoryList.productCategoryId, "prodCatContentTypeId", "ALTERNATIVE_URL")
.cache().orderBy("-fromDate")
.filterByDate()
.queryList()
// with the use of the "or" operator you have to build your condition like this
EntityCondition condition = EntityCondition.makeCondition([
EntityCondition.makeCondition([
EntityCondition.makeCondition("prodCatalogCategoryTypeId", "PCCT_VIEW_ALLW"),
EntityCondition.makeCondition("prodCatalogCategoryTypeId", "PCCT_PURCH_ALLW")
], EntityOperator.OR),
EntityCondition.makeCondition("productCategoryId", parameters.productCategoryId)
])
List prodCatalogCategoryList = from("ProdCatalogCategory").where(condition).filterByDate().queryList() |
Code Block |
---|
<make-value entity-name="FinAccountTrans" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<!-- In this case multiple fields of the GenericValue are set -->
<make-value entity-name="ProductCategoryRollup" value-field="newLimitRollup"/>
<set field="newLimitRollup.productCategoryId" from-field="newEntity.productCategoryId"/>
<set field="newLimitRollup.parentProductCategoryId" from-field="productCategoryRole.productCategoryId"/>
<set field="newLimitRollup.fromDate" from-field="nowTimestamp"/>
def newEntity = makeValue("FinAccountTrans", parameters)
// you can set multiple fields of a GenericValue like this
def newLimitRollup = makeValue("ProductCategoryRollup", [
productCategoryId: newEntity.productCategoryId,
parentProductCategoryId: productCategoryRole.productCategoryId,
fromDate: nowTimestamp
]) |
Code Block |
---|
<set field="statusValidChange.prop" value="value"/>
statusValidChange.prop = "value" |
Code Block |
---|
<create-value value-field="newEntity"/>
newEntity.create() |
Code Block |
---|
<store-value value-field="newEntity"/>
<store-list list="listToStore"/>
newEntity.store()
delegator.storeAll(listToStore) |
Code Block |
---|
<clone-value value-field="productCategoryMember" new-value-field="newProductCategoryMember"/>
def newProductCategoryMember = productCategoryMember.clone() |
Code Block |
---|
<remove-value value-field="lookedUpValue"/>
lookedUpValue.remove() |
Code Block |
---|
<sequenced-id sequence-name="ProductCategory" field="newEntity.productCategoryId"/>
newEntity.productCategoryId = delegator.getNextSeqId("ProductCategory") |
Code Block |
---|
<check-id field="newEntity.productCategoryId"/>
UtilValidate.checkValidDatabaseId(newEntity.productCategoryId) |
Code Block |
---|
<make-next-seq-id value-field="newEntity" seq-field-name="linkSeqId"/>
delegator.setNextSubSeqId(newEntity, "linkSeqId", 5, 1)
// the numbers 5 and 1 are used in the Java implementation of the MiniLang method
// and can also be found as the default values in the MiniLang documentation |
PermissionsCAUTION: To also check for admin-permissions, this method has to be used:
hasEntityPermission(permission, action, userLogin)
If the method is used with wildcards, it is important to not forget the underscore, which comes before the parameter action
!
Code Block |
---|
<check-permission permission="CATALOG" action="_CREATE">
<alt-permission permission="CATALOG_ROLE" action="_CREATE"/>
<fail-property resource="ProductUiLabels" property="ProductCatalogCreatePermissionError"/>
</check-permission>
<check-errors/>
if (!(security.hasEntityPermission("CATALOG", "_CREATE", parameters.userLogin)
|| security.hasEntityPermission("CATALOG_ROLE", "_CREATE", parameters.userLogin))) {
return error(UtilProperties.getMessage("ProductUiLabels", "ProductCatalogCreatePermissionError", parameters.locale))
} |
Code Block |
---|
<set field="hasCreatePermission" value="false" type="Boolean"/>
<if-has-permission permission="${primaryPermission}" action="${mainAction}">
<set field="hasCreatePermission" value="true" type="Boolean"/>
</if-has-permission>
// this will automatically be set to false if the user doesn't have the permission
def hasCreatePermission = security.hasEntityPermission(primaryPermission, "_${mainAction}", parameters.userLogin) |
Anchor |
---|
| timestampAndSystemTime |
---|
| timestampAndSystemTime |
---|
|
Timestamp And System TimeThe first two simple-method are deprecated; the third method should have been used instead.
Code Block |
---|
<now-timestamp field="nowTimestamp"/>
Timestamp nowTimestamp = UtilDateTime.nowTimestamp() |
Code Block |
---|
<now-date-to-env field="nowDate"/>
Timestamp nowDate = UtilDateTime.nowTimestamp() |
Code Block |
---|
<!-- this method also has the parameter "type", which is set to 'java.sql.timestamp' as default -->
<now field="fooNow"/>
Timestamp fooNow = UtilDateTime.nowTimestamp() |
Code Block |
---|
<if-compare-field field="productCategoryMember.thruDate" to-field="expireTimestamp" operator="less" type="Timestamp">
<!-- code -->
</if-compare-field>
Timestamp thruDate = productCategoryMember.thruDate
if (thruDate && thruDate.before(expireTimestamp)) {
// code
} |
LoggingSince all of the log methods are know to the Groovy Language, it is possible to just nearly use them as they are in MiniLang.
For further explanation, here are some examples:
Code Block |
---|
<log level="verbose" message="Permission check failed, user does not have permission"/>
logVerbose("Permission check failed, user does not have the correct permission.") |
Code Block |
---|
<log level="info" message="Applying feature [${productFeatureId}] of type [${productFeatureTypeId}] to product [${productId}]"/>
logInfo("Applying feature [${productFeatureId}] of type [${productFeatureTypeId}] to product [${productId}]") |
General Code Block |
---|
<call-simple-method method-name="checkCategoryRelatedPermission"/>
<check-errors/>
// simple-methods inside of classes, as long as they are not services, will be called like normal methods
Map res = checkCategoryRelatedPermission("updateProductCategory", "UPDATE", null, null)
if (!ServiceUtil.isSuccess(res)) {
return res
} |
Code Block |
---|
<iterate list="subCategories" entry="subCategory">
<!-- code -->
</iterate>
for (def subCategory : subCategories) {
// code
}
// this is also possible (CAUTION: Eclipse sometimes doesn't know, that it already knows methods inside of closures)
subCategories.each { subCategory ->
// code
} |
Code Block |
---|
<iterate-map map="parameters.productFeatureIdByType" key="productFeatureTypeId" value="productFeatureId">
<!-- in here something should happen with value and key -->
</iterate-map>
// Map.Entry<String, String> should be changed to desired Object Type
for (Map.Entry<String, String> entry : parameters.productFeatureIdByType.entrySet()) {
def productFeatureTypeId = entry.getKey()
def productFeatureId = entry.getValue()
// in here something should happen with value and key
} |
Code Block |
---|
<if>
<condition>
<not>
<or>
<if-has-permission permission="CATALOG" action="_${checkAction}"/>
<and>
<if-has-permission permission="CATALOG_ROLE" action="_${checkAction}"/>
<not><if-empty field="roleCategories"/></not>
</and>
</or>
</not>
</condition>
<then>
<!-- code -->
</then>
</if>
if (!security.hasEntityPermission("CATALOG", "_${checkAction}", parameters.userLogin)
&& !(security.hasEntityPermission("CATALOG_ROLE", "_${checkAction}", parameters.userLogin)
&& roleCategories)) {
// code
} |
Code Block |
---|
<set field="validDate" from-field="parameters.validDate"/>
<if-not-empty field="validDate">
<filter-list-by-date list="productCategoryMembers" valid-date="validDate"/>
</if-not-empty>
def query = from("ProductCategoryMember").where("productCategoryId", parameters.productCategoryId)
if (parameters.validDate) {
query.filterByDate()
}
List productCategoryMembers = query.queryList() |
Code Block |
---|
<order-map-list list="productsList">
<order-by field-name="sequenceNum"/>
</order-map-list>
productsList = EntityUtil.orderBy(productsList, ["sequenceNum"]) |
Info |
---|
title | Where to find MiniLang implementation |
---|
|
If you find yourself in a position, where you don't know how to convert a certain tag from MiniLang to Groovy, you can always check the Java implementation of the MiniLang method. All of the methods have an existing Java implementation and you can find all of them in this folder: /ofbiz/trunk/framework/minilang/src/main/java/org/apache/ofbiz/minilang/method The interesting part of this implementation is the method exec() , which actually runs the MiniLang tag. The tag <remove-by-and> for example is realized using this part of code here: Code Block |
---|
| @Override
public boolean exec(MethodContext methodContext) throws MiniLangException {
@Deprecated
String entityName = entityNameFse.expandString(methodContext.getEnvMap());
if (entityName.isEmpty()) {
throw new MiniLangRuntimeException("Entity name not found.", this);
}
try {
Delegator delegator = getDelegator(methodContext);
delegator.removeByAnd(entityName, mapFma.get(methodContext.getEnvMap()));
} catch (GenericEntityException e) {
String errMsg = "Exception thrown while removing entities: " + e.getMessage();
Debug.logWarning(e, errMsg, module);
simpleMethod.addErrorMessage(methodContext, errMsg);
return false;
}
return true;
} |
In this you can find one important part of code, which is: Code Block |
---|
delegator.removeByAnd(entityName, mapFma.get(methodContext.getEnvMap())); |
This tells you, that, if you're trying to convert the tag <remove-by-and> , you can use delegator.removeByAnd() in Groovy. |
IMPORTANT: When a simple-method ends, it will automatically at least return a success-map.
All the Groovy Services have to return success
at least, too.
Code Block |
---|
|
return success() |
Anchor |
---|
gettingStarted | gettingStarted | Getting startedMiniLang files consist of services, which, in most cases, implement services.
The get converted to Groovy like the following:
Code Block |
---|
<!-- This is MiniLang -->
<simple-method method-name="createProductCategory" short-description="Create an ProductCategory">
<!-- Code -->
</simple-method>
// This is the converted Groovy equivalent
/**
* Create an ProductCategory
*/
def createProductCategory() {
// Code
} |
It will be useful for future developers, and everybody who has to check something in the code, to put at least the short-description
as the new Groovydoc. This will hopefully more or less explain, what the method should or shouldn't do.
If the short-description
isn't helpful enough, feel free complete it.
The structure of if
and else
in MiniLang is a little different than the one from Groovy or Java and can be a bit confusing when first seen, so here is an example:
Code Block |
---|
<if-empty field="parameters.productCategoryId">
<sequenced-id sequence-name="ProductCategory" field="newEntity.productCategoryId"/>
<else>
<set field="newEntity.productCategoryId" from-field="parameters.productCategoryId"/>
<check-id field="newEntity.productCategoryId"/>
<check-errors/>
</else>
</if-empty> |
Notice, that the else
always starts before the if
-tag is closed, but sometimes isn't indented as one would expect it.
When navigating through bigger if
-phrases, the navigation itself will be much easier through just clicking in the opening or closing if
-tag; Eclipse will automatically mark the matching opening or closing if
-tag for you.
Anchor |
---|
checkingFields | checkingFields | Checking Fields Code Block |
---|
<if-empty field="fieldName"></if-empty>
// checks if fieldName is existent and/or empty
if (!fieldName) {} |
Code Block |
---|
<if-empty field="fieldName.property"></if-empty>
// fieldName has to be existent, property doesn't need to
// if known, that property does exist, the ? can be left out
if (!fieldName?.property) {}
// CAUTION: every query like this in Groovy evaluates to a Boolean type
// everything that is empty or false will turn into false:
// null, [], [:], "", false -> false
// if you want to check if the field really is empty
if (UtilValidate.isEmpty(fieldName)) {} |
Code Block |
---|
<if>
<condition>
<or>
<if-empty field="field1"/>
<if-empty field="field2"/>
</or>
</condition>
<then>
<!-- Code in if -->
</then>
<else>
<!-- Code in else -->
</else>
</if>
if (!field1 || !field2) {
// Code in if
} else {
// Code in else
} |
Code Block |
---|
<if-compare-field field="product.primaryProductCategoryId" to-field="parameters.productCategoryId" operator="equals">
<!-- Code -->
</if-compare-field>
// this will even work, if product is not existent or null
if (UtilValidate.areEqual(product?.primaryProductCategoryId, parameters.productCategoryId)) {
// Code
} |
Code Block |
---|
<if-instance-of field="parameters.categories" class="java.util.List"></if-instance-of>
if (parameters.categories instanceof java.util.List) {} |
Anchor |
---|
settingFields | settingFields | Setting FieldsRelated articles
Content by Label |
---|
showLabels | false |
---|
max | 5 |
---|
spaces | OFBIZ |
---|
showSpace | false |
---|
sort | modified |
---|
reverse | true |
---|
type | page |
---|
cql | label in ("minilang","converison","ofbiztutorial","tutorial","mini-lang","convert","language","mini","groovy","ofbiz","dsl") and type = "page" and space = "OFBIZ" |
---|
labels | minilang mini language mini-lang groovy dsl convert converison tutorial ofbiz ofbiztutorial |
---|
|