1
0
-1

Hi,

I am using IDEA to generate a service which uses an 'includeFile'.

I have managed to set the IDE to recognize the included file, so that it is available for all the IDE features. However, the type-check Ant task shows an error saying that the variable is not declared.

The code that is throwing the error is:

Map CHANNEL = Constants.CHANNEL

and the code in the Constants class file is:

public static Map<String, String> CHANNEL = [
        "CHANNEL_A": "Channel-A",
        "DEMO": "DEMO"
]

 

I have been able to resolve other type-check issues, but not sure about how to solve this one.

Any ideas would be appreciated.

Thanks

Mark

    CommentAdd your comment...

    4 answers

    1.  
      1
      0
      -1

      Hi Ben,

      I added the file to the "includeFiles" and that resolved the "Constants" errors. I was on the right track, but I think I had the relative path set incorrectly.

       

      However, now I am getting errors related to MarkupBuilder (NoClassDefFoundError groovy/lang/Object); I change this to a StreamingMarkupBuilder but I still get errors.

      I am trying to use a Closure which takes a Map as input and converts it to xml; the map is a simple list of key:value pairs, only one layer, no nested data structure.

      The static type-checker is complaining of various errors such as can't find matching method; cannot resolve dynamic method at run time; cannot assign value of type Object to Writable.

      I've spent a while manipulating what I consider the various options, but each one presents the same or a new errors.

      I've eliminated a lot of other static type-check errors, but this one is proving a challenge.

      Any ideas about options to use other than markup builder(s)?

      Thanks

      Mark

      1. Ben Warner

        Can you provide your code?

         

      2. Mark Murray

        See code below:

        The use case I am working with is:

        • collect encrypted url
        • decrypt url
        • collect request parameters
        • manipulate parameters as required, e.g. date -> day, month, year
        • convert list of parameters to xml to use in Input XML Prefill Mapping

        Thanks

         

        import com.avoka.core.groovy.GroovyLogger as logger
        import com.avoka.tm.query.PropertyQuery
        import com.avoka.tm.util.RedirectException
        import com.avoka.tm.util.XmlDoc
        import com.avoka.tm.vo.Form
        import com.avoka.tm.vo.SvcDef
        import com.avoka.tm.vo.Txn
        import com.avoka.tm.vo.User
        import groovy.xml.MarkupBuilder
        import groovy.xml.StreamingMarkupBuilder
        import org.w3c.dom.Document
        
        import javax.crypto.Cipher
        import javax.crypto.SecretKey
        import javax.crypto.SecretKeyFactory
        import javax.crypto.spec.IvParameterSpec
        import javax.crypto.spec.PBEKeySpec
        import javax.crypto.spec.SecretKeySpec
        import javax.servlet.http.HttpServletRequest
        import java.util.Map.Entry
        
        /**
         * This Fluent Form Prefill service provides pre-fill data for the Partner Member form.
         */
        class PartnerMemberPrefill {
        
            Map ENCRYPTION = Constants.ENCRYPTION
            Map CHANNEL = Constants.CHANNEL
        
            /*
             * Perform form prefill service
             *
             * return: the form XML prefill document
             * throws: RedirectException to redirect user to another page
             */
        
            Document invoke(SvcDef svcDef, Form form, Document formXml, Txn txn, HttpServletRequest request, User user) throws RedirectException {
        
                // Collect request parameters
                // TODO: what should the default be?
                String channel = request.getParameter("channel") ?: "online"
                String crmEnv = request.getParameter("org") ?: new PropertyQuery().setName("environmentName").setSpaceName("Beyond Bank").getValue()
        
                logger.info("channel: ${channel}")
                logger.info("crmEnv : ${crmEnv}")
        
                // Closure definitions
                // generate key
                Closure generateKey = { ->
                    try {
                        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
                        //String password = ((String) ENCRYPTION.PASSWORD).toCharArray()
                        byte[] salt = ((String) ENCRYPTION.SALT).getBytes("UTF-8")
        
                        PBEKeySpec spec = new PBEKeySpec(((String) ENCRYPTION.PASSWORD).toCharArray(), salt, 65536, 128)
                        SecretKey temp = factory.generateSecret(spec)
                        byte[] encoded = temp.getEncoded()
                        return new SecretKeySpec(encoded, "AES")
                    } catch (Exception any) {
                        logger.error("Unable to generate encryption key: ${any}.")
                        throw new Exception("Unable to generate encryption key: ${any}.")
                    }
                }
        
                // encrypt
                Closure encrypt = { String pInput ->
                    try {
                        byte[] iv = ((String) ENCRYPTION.IV).getBytes("UTF-8")
        
                        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding")
                        c.init(Cipher.ENCRYPT_MODE, (SecretKeySpec) generateKey(), new IvParameterSpec(iv))
        
                        byte[] encrypted = c.doFinal(pInput.getBytes("UTF-8"))
        
                        return Base64.getEncoder().encodeToString(encrypted)
                        //return DatatypeConverter.printBase64Binary(encrypted)
                    } catch (Exception any) {
                        logger.error("Unable to encrypt request data: ${any}.")
                        throw new Exception("Unable to encrypt request data: ${any}.")
                    }
                }
        
                // decrypt
                Closure decrypt = { String pInput ->
                    try {
                        byte[] iv = ((String) ENCRYPTION.IV).getBytes("UTF-8")
        
                        byte[] decoded = Base64.getDecoder().decode(pInput.getBytes("UTF-8"))
        
                        //def decoded = DatatypeConverter.parseBase64Binary(pInput);
        
                        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding")
                        c.init(Cipher.DECRYPT_MODE, (SecretKeySpec) generateKey(), new IvParameterSpec(iv))
        
                        byte[] decrypted = c.doFinal(decoded)
        
                        return new String(decrypted)
                    } catch (Exception any) {
                        logger.error("Unable to decrypt the request data: ${any}.")
                        throw new Exception("Unable to decrypt the request data: ${any}.")
                    }
                }
        
                /*
                Create prefill xml data
                Uses MarkupBuilder to produce safe, valid xml.
        
                parameters: pData; simple map of input data as key:value pairs
        
                returns: xml string
                */
                Closure createPrefillDataXml = { Map pData ->
        
                    Writable xml
                    xml = new StreamingMarkupBuilder().bind {
                        xmlPrefillData {
                            pData.each { Entry item ->
                                "${item.key}" ("${item.value}")
                            }
                        }
                    }
        
        //            StringWriter sw = new StringWriter()
        //            MarkupBuilder xml = new MarkupBuilder(sw)
        //
        //            xml.prefillXmlData {
        //                pData.each { item ->
        //                    "${item.key}"("${item.value}")
        //                }
        //            }
        
                    return xml.toString()
        
        //            new StringWriter().with { sw ->
        //                new MarkupBuilder(sw).with {
        //                    prefillXmlData {
        //                        pData.each { key, value ->
        //                            "${key}"("${value}")
        //                        }
        //                    }
        //                }
        //                output = sw.toString()
        //            }
        
                }
        
                // Process
                Map data = [:]
        
                data.channel = channel
                data.crmEnvironment = crmEnv
        
                if (channel.toUpperCase() == CHANNEL.MCP || channel.toUpperCase() == CHANNEL.DEMO) {
                    String queryData = request.getParameter("data")
        
                    // browser replaces '+' sign with space; need to manage this
                    String decryptedData = decrypt(queryData.replaceAll(/\s/, "+"))
        
                    if (!decryptedData?.startsWith("fname=")) {
                        logger.error("Decrypted data is not in correct format: ${decryptedData}")
                        throw new Exception("Decrypted data is not in correct format: ${decryptedData}")
                    }
        
                    decryptedData.tokenize("&").each { kvPair ->
                        List<String> item = kvPair.tokenize("=")
                        data.put(item[0], item[1])
                    }
        
                    // split dob into elements to populate each field of dob widget
                    List<String> dobElements = ((String)(data.dob)).tokenize("-")
        
                    String dobDay = dobElements[2].startsWith("0") ? dobElements[2].substring(1, 2) : dobElements[2]
                    String dobMonth = dobElements[1].startsWith("0") ? dobElements[1].substring(1, 2) : dobElements[1]
        
                    data.put("dobDay", dobDay)
                    data.put("dobMonth", dobMonth)
                    data.put("dobYear", dobElements[0])
        
                    if (data.products == "1") {
                        data.account_one = true
                        data.account_two = false
                    } else if (data.products == "2") {
                        data.account_one = false
                        data.account_two = true
                    } else if (data.products == "3") {
                        data.account_one = true
                        data.account_two = true
                    }
        
                    return new XmlDoc(createPrefillDataXml(data)).getDocument()
                }
        
                return null
            }
        }
      3. Ben Warner

        Hey Mark, I believe your code contains elements that will not resolve under static compilation:

         

        xml = new StreamingMarkupBuilder().bind {
            xmlPrefillData {
                pData.each { Entry item ->
                    "${item.key}" ("${item.value}")
                }
            }
        }

        You can however achieve the same result with the XmlDoc class provided in the Fluent API.

        XmlDoc xmlDoc = new XmlDoc('<xmlPrefillData/>')
        pData.keySet().each {
            xmlDoc.setText('/xmlPrefillData/' + it, pData.get(it))
        }
        return xmlDoc.toString()

         

        You may find this relevant:

        http://melix.github.io/blog/2014/02/markuptemplateengine_part2.html

      4. Mark Murray

        Hi Ben,

        Wow, that is quite some blog post.

        XmlDoc was what I was looking for, something that would produce xml but be safe for the static type-checker.

        I found I had to cast the value in the setText method to String:

        pData.keySet().each {
            xmlDoc.setText('/xmlPrefillData/' + it, (String) pData.get(it))
        }

         

        But it works! "Ant build completed successfully".

        Thanks for the input and ideas. Much appreciated.

        Now I can make some more progress with the Fluent SDK; it's a long journey, but I'm taking small steps every day, and learning some tips and tricks as I go.

        Thanks again.

        Mark

      5. Ben Warner

        Great to hear Mark! If you define your map as Map<String, String> you won't need to cast.

      6. Mark Murray

        Hey, I like that even better; neat and tidy. Thanks

      CommentAdd your comment...
    2.  
      2
      1
      0

      I've replicated your situation and don't get errors in type check.

      Can you confirm that your include file does not have a package declaration (i.e. it is in the default package)?

      If so, can you provide more of your code please.

        CommentAdd your comment...
      1.  
        1
        0
        -1

        You need to add the Contants.groovy file to the fileIncludes attribute of the groovyScript parameter in your service-def.json file.

        Ensure you enter a path that is relative to the service-def.json file itself. For example if your Constants.groovy file was in your sources root directory (src) then your file path would be:

        "parameters": [
          {
            "name": "groovyScript",
            "filePath": "WelcomeGreeting.groovy",
            "fileIncludes": ["../Constants.groovy"],
            "bind": true,
            "required": false,
            "clearOnExport": false,
            "readOnly": true
          },
          CommentAdd your comment...
        1.  
          1
          0
          -1

          Hi Ben,

           

          Confirmed - the Constants class does not have a package declaration.

          Here is the code from the class Constants:

          /**
           * A class of constants for global use in Avoka services.
           */
          class Constants {
          
              /* Empty constructor so that this class cannot be instantiated. */
          
              Constants() {}
          
              /*
              Constants for encryption parameters
               */
              public static Map<String, String> ENCRYPTION = [
                      "PASSWORD": "PASSWORD_VALUE",
                      "SALT": "SALT_VALUE",
                      "IV": "IV_VALUE_16_BYTE"
              ]
          
              /*
              Constants for channel
               */
              public static Map<String, String> CHANNEL = [
                      "MCP": "MCP",
                      "DEMO": "DEMO"
              ]
          }
          
          

           

          and here is the code from the prefill service that references the Constants file:

          import com.avoka.core.groovy.GroovyLogger as logger
          import com.avoka.tm.query.PropertyQuery
          import com.avoka.tm.util.RedirectException
          import com.avoka.tm.vo.Form
          import com.avoka.tm.vo.SvcDef
          import com.avoka.tm.vo.Txn
          import com.avoka.tm.vo.User
          import groovy.xml.MarkupBuilder
          import org.w3c.dom.Document
          import javax.crypto.Cipher
          import javax.crypto.SecretKey
          import javax.crypto.SecretKeyFactory
          import javax.crypto.spec.IvParameterSpec
          import javax.crypto.spec.PBEKeySpec
          import javax.crypto.spec.SecretKeySpec
          import javax.servlet.http.HttpServletRequest
          /**
           * This Fluent Form Prefill service provides pre-fill data for the Partner Member form.
           */
          class PartnerMemberPrefill {
              Map ENCRYPTION = Constants.ENCRYPTION
              Map CHANNEL = Constants.CHANNEL
              /*
               * Perform form prefill service
               *
               * return: the form XML prefill document
               * throws: RedirectException to redirect user to another page
               */
              Document invoke(SvcDef svcDef, Form form, Document formXml, Txn txn, HttpServletRequest request, User user) throws RedirectException {
                  // Collect request parameters
                  // TODO: what should the default be?
                  
                  // ... processing code removed for brevity
          
                  return createPrefillDataXml(data)
              }
          }

          and the type-check errors are as follows:

          24: [Static type checking] - The variable [Constants] is undeclared.
           @ line 24, column 22.
                 Map ENCRYPTION = Constants.ENCRYPTION
                                  ^
          25: [Static type checking] - The variable [Constants] is undeclared.
           @ line 25, column 19.
                 Map CHANNEL = Constants.CHANNEL
                               ^
          General error during instruction selection: java.lang.NoClassDefFoundError: groovy/lang/GroovyObject
          java.lang.RuntimeException: java.lang.NoClassDefFoundError: groovy/lang/GroovyObject
          	at org.codehaus.groovy.control.CompilationUnit.convertUncaughtExceptionToCompilationError(CompilationUnit.java:1089)
          	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1067)
          
          

          I have set up the IDE to have the file Constants as a module dependency, so that the reference to Constants in the prefill file is recognised; that avoids any IDE errors.

           

          Thanks

           

            CommentAdd your comment...