我第一个真正的蚂蚁任务


*注意:在此博客文章中所表达的意见仅限于我个人,并不代表我的雇主的观点。*

既然雇主的通知不在我的视线范围内,我就可以开始谈论我想要谈论的事情了。在工作中,我们有一个很大的倡议,就是将CFQUERYPARAM放入没有CFQUERYPARAM的SQL语句中。由于这段代码相当老旧,其中大部分都没有使用这个方便的标记,因此正如您所想象的那样,要查看3000多个文件并验证CFQUERYPARAM是否被放到了需要的任何地方,这是一项相当大的任务。

在最初的工作之后,我的一位同事创建了一系列正则表达式来帮助验证工作,并指出我们可能做错了工作的任何地方。老实说,盯着屏幕看8个小时,寻找CFQUERY和英镑符号中的变量,可能很快就会变得模糊。如果有这些有用的正则表达式,我想最好有一个简单的ANT脚本,它会提示输入目录或文件名资源,并针对该资源运行这些正则表达式,并在Excel电子表格中创建任何潜在问题区域的报告。


为了演示,让我们看看ANT脚本本身是什么样子的。
<!--?xml version="1.0"?-->
<project name="CFQUERYPARAM Tester" default="main" basedir=".">
   <taskdef name="queryParamChecker" classname="com.apihealthcare.opt.QueryParamChecker">
 
   <target name="main">
      <input message="Please provide a directory path or file path to scan:" addproperty="path" defaultvalue="${basedir}">
 
      <queryparamchecker path="${path}" outputfile="C:\\anttasktestresults.xlsx">
   </queryparamchecker></target>
</taskdef></project>

如您所见,要做的第一件事是“导入”一个名为 QueryParamChecker。这是我编写的一个自定义ANT任务,它基于一组正则表达式扫描资源以查找潜在问题。然后我们使用 输入任务提示用户输入资源名称,目录或文件路径,然后将其传递给查询参数检查器任务。

那么如何编写自定义ANT任务呢?为此,我使用了Groovy,因此我从Eclipse中的一个新Groovy项目开始。然后,我将以下JAR文件添加到类路径中:

  • 蚂蚁罐子
  • commons-logging-1.1.jar
  • dom4j-1.6.1.jar
  • poi-3.7-beta3-20100924.jar
  • poi-ooxml-3.7-beta3-20100924.jar
  • poi-ooxml-schemas-3.7-beta3-20100924.jar
  • xmlbeans-2.3.0.jar

这些JAR文件为我们提供了用于创建Excel文档的Apache POI项目,以及构建自定义ANT任务所必需的ANT类。然后,我在新创建的项目中创建了一个新包,并将其称为**com.apiHealthCare.opt**。在这个包中,我创建了一个新的Groovy类,名为 QueryParamChecker。创建自定义ANT任务所必需的第一件事是导入Apache ANT类,然后扩展task类。新类必须重写 execute()方法,并提供 新任务将支持的每个属性的setter。

package com.apihealthcare.opt

import org.apache.tools.ant.*

class QueryParamChecker extends Task
{
   private String path
   private String outputFile

   @Override
   public void execute() throws BuildException {
   }

   public void setPath(String path) {
      this.path = path
   }

   public void setOutputFile(String outputFile) {
      this.outputFile = outputFile
   }
}


这是定制ANT任务的框架。但我想要的显然不只是一具骷髅。我需要它来检查资源中的CFQueryParam错误。有三条我用来做这件事的信息。第一个是一个正则表达式对象数组,它是我们这里要做的主要内容。接下来是一个文件扩展名数组,我们可以检查这些扩展名,因此它包含“。cfm”和“。cfc”。最后,我有一个正则表达式数组,用于过滤所有不需要的文件或文件夹,因为这个应用程序有许多不再使用的旧文件。这些是阵列。

/*
 * An array of regular expressions to check files against.
 * Modify this list to change the rules yo.
 */
private def checks = [
   ~/(?i)in\s*\(\s*<cfqueryparam((?!list).)*>/,
   //~/(?i)in\s*\(\s*<cfqueryparam((?!cfsqltype).)*>/,
   ~/(?i)(#\s*cfsqltype=|#\s*maxlength=|#\s*list=|#\s*value=)/,
   ~/(?i)\sin(\s|\()[^>]*(value="#listqualify|value="#replace|value="#preservesinglequotes)/,
   ~/(?i)[^<]cfqueryparam/,
   ~/(?i)<cfqueryparam[^<>]*"\s*\/[^>]/,
   ~/(?i)<cfqueryparam\s*value=#/,
   ~/(?i)value="#dateadd/,
   ~/(?i)<cfqueryparams/,
   ~/(?i)cfsqltype=""/,
   ~/(?i)(#"list|#"value|#"cfsqltype|#"maxlength)/,
   ~/(?i)order\s*by\s*<cfqueryparam/,
   ~/(?i)(cfqueryparamvalue|cfqueryparamcfsqltype|cfqueryparammaxlength|cfqueryparamlist)/,
   ~/(?i)<cfqueryparam[^>]*(?=\/\s*"\s*>)/,
   ~/(?i)charindex\([^\)]*<cfqueryparam/,
   ~/(?i)[^<!-|<!]--[^>|-].*<cfqueryparam/,
   ~/(?i)session\.(?!(hasPermission|get|usersession|set).*)/
]

/*
 * An array of extensions that we care about. Ignore all else.
 */
private def validExtensions = [
   ".cfm",
   ".cfc"
]

/*
 * File name regex patterns to ignore.
 */
private def ignores = [
   ~/(?i)(.*?)unused_(.*)/
]


从这里我创建了两个函数。第一种方法将根据正则表达式检查单个文件的错误,第二种方法将递归一个目录结构。它们都非常相似,而且很可能被编写得更可重用,但目前它们完成了任务。本质上,这些函数将从文件中读取文本,并将其与**checks**数组中的每个正则表达式进行比较。如果存在匹配项,则将它们存储在一个结构中,并放入**BadCodeResults**数组中。

完成这些操作后,调用**_WriteOutputFile()**方法来获取**BadCodeResults**中的项,并将它们放入Excel电子表格中。为此,您首先创建一个**XSSFWorkbook**,一个**XSSFCreationHelper**和一个**XSSFSheet**对象。工作表是从工作簿中创建的;实际上,它在Excel的工作簿中创建一个新的工作表。I在**BadCodeResults**上循环,并为文件路径,违规文本以及违规文本位置的开始和结束位置创建单元格。然后将文件写入磁盘。

下面是整个任务。您还可以
download the full source code。快乐的编码!

package com.apihealthcare.opt

import org.apache.tools.ant.*
import groovy.io.FileType
import org.apache.poi.poifs.filesystem.*
import org.apache.poi.xssf.extractor.*
import org.apache.poi.xssf.usermodel.*

class QueryParamChecker extends Task
{
   private String path
   private String outputFile

   /*
    * An array of regular expressions to check files against.
    * Modify this list to change the rules yo.
    */
   private def checks = [
      ~/(?i)in\s*\(\s*<cfqueryparam((?!list).)*>/,
      //~/(?i)in\s*\(\s*<cfqueryparam((?!cfsqltype).)*>/,
      ~/(?i)(#\s*cfsqltype=|#\s*maxlength=|#\s*list=|#\s*value=)/,
      ~/(?i)\sin(\s|\()[^>]*(value="#listqualify|value="#replace|value="#preservesinglequotes)/,
      ~/(?i)[^<]cfqueryparam/,
      ~/(?i)<cfqueryparam[^<>]*"\s*\/[^>]/,
      ~/(?i)<cfqueryparam\s*value=#/,
      ~/(?i)value="#dateadd/,
      ~/(?i)<cfqueryparams/,
      ~/(?i)cfsqltype=""/,
      ~/(?i)(#"list|#"value|#"cfsqltype|#"maxlength)/,
      ~/(?i)order\s*by\s*<cfqueryparam/,
      ~/(?i)(cfqueryparamvalue|cfqueryparamcfsqltype|cfqueryparammaxlength|cfqueryparamlist)/,
      ~/(?i)<cfqueryparam[^>]*(?=\/\s*"\s*>)/,
      ~/(?i)charindex\([^\)]*|-].*<cfqueryparam/,
      ~/(?i)session\.(?!(hasPermission|get|usersession|set).*)/
   ]

   /*
    * An array of extensions that we care about. Ignore all else.
    */
   private def validExtensions = [
      ".cfm",
      ".cfc"
   ]

   /*
    * File name regex patterns to ignore.
    */
   private def ignores = [
      ~/(?i)(.*?)unused_(.*)/
   ]

   @Override
   public void execute() throws BuildException {
      def fileCheck = new File(this.path)
      def result = []

      if (fileCheck.isFile()) {
         result = _doFile()
      }
      else if (fileCheck.isDirectory()) {
         result = _doDirectory()
      }
      else
         throw new Exception("The path passed in doesn't seem to be a file or a directory!")

      _writeOutputFile(result)
   }

   public void setPath(String path) {
      this.path = path
   }

   public void setOutputFile(String outputFile) {
      this.outputFile = outputFile
   }

   private def _doFile() {
      def badCodeResults = []

      /*
       * The directory we are searching goes here!
       */
      def f = new File(this.path)
      assert f.isFile()

      def filesProcessed = 0
      def badFiles = 0

      def validFile = false
      def printed = false

      /*
       * Do we care about this particular file? If not set the
       * validFile flag to false.
       */
      validExtensions.each {
         if (f.name.endsWith(it)) validFile = true
      }

      ignores.each {
         def ignoreMe = f.name ==~ it
         if (validFile != false && ignoreMe) validFile = false
      }

      /*
       * Enter here if we care.
       */
      if (validFile) {
         filesProcessed++

         /*
          * Start looping over each regex we wish to run against this file.
          */
         checks.each { regex ->
            def matcher = f.text =~ regex
            def index = 0

            /*
             * Loop over any matches in the file.
             */
            while (matcher.find()) {
               /*
                * We have a bad code match! Put it into our results array.
                */
               if (matcher.group(0) != null && matcher.group(0) != "") {
                  if (!printed) {
                     println "File: ${f.name}..."
                     badFiles++
                  }
                  printed = true

                  badCodeResults << [
                     filePath: f.getAbsolutePath(),
                     offendingText: matcher.group(0),
                     start: matcher.start(),
                     end: matcher.end()
                  ]
               }
            }
         }

      }

      println "Processed ${filesProcessed} file(s)"
      println "${badFiles} bad file(s) found"

      badCodeResults
   }

   private def _doDirectory() {
      def badCodeResults = []

      /*
       * The directory we are searching goes here!
       */
      def rootPath = this.path
      def codeBase = new File(rootPath)
      assert codeBase.isDirectory()

      def filesProcessed = 0
      def badFiles = 0

      /*
       * Iterate over all files in our source directory.
       */
      codeBase.eachFileRecurse FileType.FILES, { f ->
      def validFile = false
      def printed = false

      /*
       * Do we care about this particular file? If not set the
       * validFile flag to false.
       */
      validExtensions.each {
         if (f.name.endsWith(it)) validFile = true
      }

      ignores.each {
         def ignoreMe = f.name ==~ it
         if (validFile != false && ignoreMe) validFile = false
      }

      /*
       * Enter here if we care.
       */
      if (validFile) {
         filesProcessed++

         /*
          * Start looping over each regex we wish to run against this file.
          */
         checks.each { regex ->
            def matcher = f.text =~ regex
            def index = 0

            /*
             * Loop over any matches in the file.
             */
            while (matcher.find()) {
               /*
                * We have a bad code match! Put it into our results array.
                */
               if (matcher.group(0) != null && matcher.group(0) != "") {
                  if (!printed) {
                     println "File: ${f.name}..."
                     badFiles++
                  }
                  printed = true

                  badCodeResults << [
                     filePath: f.getAbsolutePath() - rootPath,
                     offendingText: matcher.group(0),
                     start: matcher.start(),
                     end: matcher.end()
                  ]
               }
            }
         }

      }

      println "Processed ${filesProcessed} file(s)"
      println "${badFiles} bad file(s) found"

      badCodeResults
   }

   private def _writeOutputFile(badCodeResults) {
      /*
       * Create a workbook and worksheet.
       */
      XSSFWorkbook wb = new XSSFWorkbook()
      XSSFCreationHelper helper = wb.getCreationHelper()
      XSSFSheet sheet = wb.createSheet("Search Results")

      def rowIndex = 0

      /*
       * Loop over all our bad code results and write them to
       * rows in the Excel sheet.
       */
      badCodeResults.each {
         XSSFRow row = sheet.createRow(rowIndex++)

         row.createCell(0).setCellValue(helper.createRichTextString(it.filePath))
         row.createCell(1).setCellValue(helper.createRichTextString(it.offendingText))
         row.createCell(2).setCellValue(it.start)
         row.createCell(3).setCellValue(it.end)
      }

      /*
       * Write out the results file. Note the path.
       */
      FileOutputStream out = new FileOutputStream(this.outputFile)
      wb.write(out)
      out.close()

      println "Output results written to ${this.outputFile}"
   }
}