This is a small guide for everybody involved in converting the Mini Language into Groovy.
Info | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
This tutorial is directly linked to the efforts of converting all scripts in Mini Language to newer Groovy Scripts. To contribute, or just be up to date with the current process, you can look at the existing JIRA issue
|
Content
Groovy DSL
Services
Getting started
Checking Fields
Setting Fields
Starting Services
Preparing Service Results
Database Communication
Permissions
Timestamp And System Time
Logging
General
Anchor | ||||
---|---|---|---|---|
|
Section | |||||||
---|---|---|---|---|---|---|---|
|
Section | |||||||
---|---|---|---|---|---|---|---|
|
Section | |||||||
---|---|---|---|---|---|---|---|
|
Section |
---|
Anchor | ||||
---|---|---|---|---|
|
From MiniLang to Groovy
To see additional examples and finished conversions, which may help with occurring questions, click:
Jira | ||||||
---|---|---|---|---|---|---|
|
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 | ||||
---|---|---|---|---|
|
MiniLang 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 | ||||
---|---|---|---|---|
|
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 | ||||
---|---|---|---|---|
|
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"/> String failMessage = UtilProperties.getMessage("CommonUiLabels", "CommonGenericPermissionError", 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 | ||||
---|---|---|---|---|
|
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 | ||||
---|---|---|---|---|
|
Code Block |
---|
<field-to-result field="fieldBudgetId" result-name="budgetId"/> // MiniLang knows this implizitly def result = success() result.budgetId = fieldBudgetId return result |
Anchor | ||||
---|---|---|---|---|
|
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 |
Anchor | ||||
---|---|---|---|---|
|
CAUTION: 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 | ||||
---|---|---|---|---|
|
The 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 } |
Anchor | ||||
---|---|---|---|---|
|
Since 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}]") |
Anchor | ||||
---|---|---|---|---|
|
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 | |||||||
---|---|---|---|---|---|---|---|
| |||||||
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. The interesting part of this implementation is the method The tag
In this you can find one important part of code, which is:
This tells you, that, if you're trying to convert the tag |
Related articles
Content by Label | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Page properties | ||
---|---|---|
| ||
|