20 lutego 2013

Pierwszy projekt w Scali - obróbka XMLi przy migracji z WLI do IBM BPM

Chwała każdemu, komu udaje się dopiąć swego i wdrożyć nowy język w projekcie, aby produkt ciężkiej pracy umysłowej komuś ułatwił życie (niechby to był sam klient, albo koledzy programiści z zespołu).

A niechby to był taki malutki projekcik, jak ten mój, dzisiejszy do obrabiania XMLi. Nic nadzwyczajnego, ale czego oczekiwać od nowicjusza scalowego, który karierę w tym języku liczy w dniach (a nie tygodniach, czy miesiącach)?! Każdy przecież kiedyś był początkujący w rzeczach, w których teraz wiedzie prym. Ja właśnie zaczynam swoje pierwsze kroki w Scali i twory przypominają prawdziwe potwory, ale od czegoś zacząć należy!

Wierzę, że z pomocą Grześka, teamon'a oraz dmilith'a nauka Scali będzie tylko przyjemnością!

Pora na odsłonę mojego dzieła. Komentarze mile widziane.
package pl.japila.wli.transformations.control

import scala.xml.{ XML, Node, Elem }
import scala.xml.transform.{ RuleTransformer, RewriteRule }

object WLIControlMigrationMain {
  // FIXME: Remove it once the script reads input args or we find them a better place
  val pkg = "pl.japila.wli.transformations.control".replace(".", "/")
  
  def main(args: Array[String]) {
    // The file comes from the BPEL Exporter in WLI
    val processCtrlXml = XML.load(getClass.getResourceAsStream(s"/${pkg}/process_ctrl.wsdl"))
    val portType = (processCtrlXml \ "portType" \ "@name").text

    // The file is ours - the template for a JMS import
    val importXmlTemplate = XML.load(getClass.getResourceAsStream(s"/${pkg}/import-xml.template"))
    // The file name and the name of the import component must be alike
    val importComponentName = portType + "_MB_Publish_control"
    println(s"+++\n+++ Save the following file as ${importComponentName}.import:\n+++")
    println(importXmlTemplate.toString
      .replace("{importComponentName}", importComponentName)
      .replace("{portType}", portType))
    println(s"+++\n+++ Save the following file as process.component:\n+++")
    wireComponentWithImport(importComponentName)
  }

  def wireComponentWithImport(importComponentName: String) {
    val processComponentXML = XML.load(getClass.getResourceAsStream(s"/${pkg}/process.component"))
    val wire = XML.loadString(s"<wire target='${importComponentName}'/>")
    val wiredProcessComponentXML = new RuleTransformer(
                                     new AddChildrenTo("reference", wire))
                                       .transform(processComponentXML).head
    println(wiredProcessComponentXML)
  }

  // http://stackoverflow.com/questions/2199040/scala-xml-building-adding-children-to-existing-nodes
  def addChild(n: Node, newChild: Node) = n match {
    case Elem(prefix, label, attribs, scope, child @ _*) =>
      Elem(prefix, label, attribs, scope, child ++ newChild: _*)
    case _ => sys.error("Can only add children to elements!")
  }

  class AddChildrenTo(label: String, newChild: Node) extends RewriteRule {
    override def transform(n: Node) = n match {
      case Elem(_, `label`, _, _, _*) => addChild(n, newChild)
      case _ => n
    }
  }
}

3 komentarze:

  1. Gratuluję pierwszego projektu!
    Kilka drobnych uwag:
    W metodzie addChildrenTo możesz usunąć fragment "n @".
    Możesz również zamienić case other => other na case _ => n (ale zapis z other może oczywiście zostać).
    Zamiast XML.load i getResourceAsStream możesz użyć XML.loadFile i podać ścieżkę pliku po prostu.
    Po toString nie potrzebujesz dodawać nawiasów ()

    OdpowiedzUsuń
    Odpowiedzi
    1. Dzięki Grzesiek.

      Odnośnie zmian to:
      -> n @ jest faktycznie nadmiarowe, bo skoro wpada w pierwszy case to już jest n-em. Skopiowałem z SO bez większego zastanowienia.
      -> przyjmuję uwagę o case _. Zdaje się być idiomem, który przechwytuje wszystko pozostałe i taki jest właśnie zamiar tego wyrażenia.
      -> XML.loadFile ładuje plik ze znanego miejsca w systemie plików. Na chwilę obecną odpada, bo nie obsługuję podawania plików z linii poleceń i pracuję wyłącznie z tymi, które znajdę na CLASSPATH. Stąd właśnie użycie XML.load(InputStream).
      -> Z tymi nawiasami muszę sobie pożyć trochę, bo przy println ich nie chciałbym dawać, a muszą być, a w innych miejscach dodaję samoistnie.

      Dzięki jeszcze raz za uwagi. Pomocne wielce!

      p.s. Siedzę właśnie nad zipperami w Scali. Patrz http://en.wikipedia.org/wiki/Zipper_%28data_structure%29 oraz http://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf. Widziałem to pierwszy raz przy nauce Clojure i teraz pojawiło się ponownie przy tym zadaniu w Scali. To mogłoby być ciekawym tematem na szkolenie/warsztaty z zaawansowanej Scali.

      Usuń
    2. Poprawione! Jeszcze raz dziękuję za uwagę i uwagi :-)

      Usuń