Статьи

Моя первая настоящая задача ANT


* Примечание: мнения, выраженные в этой записи блога, являются моими и не обязательно отражают точку зрения моего работодателя. *

Теперь, когда уведомление работодателя не соответствует моему способу, я действительно могу начать говорить о том, о чем я хочу поговорить. На работе у нас есть большая инициатива, чтобы поместить 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, поэтому я начал с нового проекта Groovy в Eclipse. Затем я добавил следующие JAR-файлы в мой classpath:

  • подпапке
  • Обще-каротаж 1.1.jar
  • dom4j-1.6.1.jar
  • пои-3,7-beta3-20100924.jar
  • пои-OOXML-3,7-beta3-20100924.jar
  • пои-OOXML-схемы-3,7-beta3-20100924.jar
  • XMLBeans-2.3.0.jar

Эти JAR-файлы дают нам проект Apache POI для создания документов Excel, а также необходимые классы ANT для создания пользовательской задачи ANT. Затем я создал новый пакет в своем недавно созданном проекте и назвал его ** com.apihealthcare.opt **. В этом пакете я создал новый класс Groovy с именем
QueryParamChecker . Первое, что необходимо для создания пользовательской задачи ANT, это импортировать классы Apache ANT, а затем расширить класс Task. Ваш новый класс должен переопределить метод
execute () и предоставить
сеттеры для каждого свойства, которое будет поддерживать ваша новая задача.

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_(.*)/
]

Отсюда я создал две функции. Первый проверит один файл на наличие ошибок в регулярных выражениях, а второй — рекурсивную структуру каталогов. Они оба очень похожи и, вероятно, могли бы быть написаны более многократно, но пока они делают свое дело. По сути, эти функции будут читать текст из файла и сравнивать его с каждым регулярным выражением в массиве ** проверок **. Если есть совпадения, они сохраняются в структуре и помещаются в массив ** badCodeResults **.

После этого вызывается метод ** _ writeOutputFile () **, чтобы взять элементы в ** badCodeResults ** и поместить их в электронную таблицу Excel. Для этого вы сначала создаете объект ** XSSFWorkbook **, ** XSSFCreationHelper ** и ** XSSFSheet **. Лист создается из рабочей книги; по сути, он создает новый лист в книге в Excel. Я зацикливаюсь на ** badCodeResults ** и создаю ячейки для пути к файлу, поврежденного текста, а также начального и конечного местоположений поврежденного текстового местоположения. В довершение я записываю файл на диск.

Ниже приведена задача в полном объеме. Вы также можете
скачать полный исходный код . Удачного кодирования!

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}"
   }
}