All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.datasonnet.plugins.DefaultXMLFormatPlugin.scala Maven / Gradle / Ivy

package com.datasonnet.plugins

/*-
 * Copyright 2019-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io._
import java.net.URL
import java.nio.charset.Charset

import com.datasonnet.document
import com.datasonnet.document.{DefaultDocument, MediaType, MediaTypes}
import com.datasonnet.plugins.xml.XML
import com.datasonnet.spi.{AbstractDataFormatPlugin, PluginException}
import ujson.Value

import scala.collection.mutable
import scala.jdk.CollectionConverters.MapHasAsScala

// See: http://wiki.open311.org/JSON_and_XML_Conversion/#the-badgerfish-convention
// http://www.sklar.com/badgerfish/
// http://dropbox.ashlock.us/open311/json-xml/
object DefaultXMLFormatPlugin extends AbstractDataFormatPlugin {
  private val XMLNS_KEY = "xmlns"
  val DEFAULT_NS_KEY = "$"
  private val DEFAULT_DS_NS_SEPARATOR = ":"
  private val DEFAULT_DS_ATTRIBUTE_KEY_PREFIX = "@"
  private val DEFAULT_DS_ORDERING_KEY = "~"
  private val DEFAULT_DS_TEXT_KEY_PREFIX = "$"
  private val DEFAULT_DS_VERSION = "1.0"
  private val DEFAULT_DS_CDATA_KEY_PREFIX = "#"
  private val DEFAULT_DS_MIXED_CONTENT = "both"

  val DS_NS_SEPARATOR = "namespaceseparator"
  val DS_ATTRIBUTE_KEY_PREFIX = "attributecharacter"
  val DS_ORDERING_KEY = "orderingkey"
  val DS_TEXT_KEY_PREFIX = "textvaluekey"
  val DS_CDATA_KEY_PREFIX = "cdatavaluekey"
  // anything that starts with NamespaceDeclarations.
  val DS_NAMESPACE_DECLARATIONS = "namespacedeclarations\\..*"
  val DS_ROOT_ELEMENT = "rootelement"
  val DS_OMIT_DECLARATION = "omitxmldeclaration"
  val DS_VERSION = "xmlversion"

  val DS_AUTO_EMPTY = "autoemptyelements"
  val DS_NULL_AS_EMPTY = "nullasemptyelement"

  supportedTypes.add(MediaTypes.APPLICATION_XML)
  supportedTypes.add(MediaTypes.TEXT_XML)
  supportedTypes.add(new MediaType("application", "*+xml"))

  writerParams.add(AbstractDataFormatPlugin.DS_PARAM_INDENT)
  writerParams.add(DS_NS_SEPARATOR)
  writerParams.add(DS_ATTRIBUTE_KEY_PREFIX)
  writerParams.add(DS_TEXT_KEY_PREFIX)
  writerParams.add(DS_CDATA_KEY_PREFIX)
  writerParams.add(DS_ORDERING_KEY)
  writerParams.add(DS_NAMESPACE_DECLARATIONS)
  writerParams.add(DS_ROOT_ELEMENT)
  writerParams.add(DS_OMIT_DECLARATION)
  writerParams.add(DS_VERSION)
  writerParams.add(DS_AUTO_EMPTY)
  writerParams.add(DS_NULL_AS_EMPTY)

  readerParams.add(DS_NS_SEPARATOR)
  readerParams.add(DS_ATTRIBUTE_KEY_PREFIX)
  readerParams.add(DS_TEXT_KEY_PREFIX)
  readerParams.add(DS_CDATA_KEY_PREFIX)
  readerParams.add(DS_ORDERING_KEY)
  readerParams.add(DS_NAMESPACE_DECLARATIONS)

  readerSupportedClasses.add(classOf[String].asInstanceOf[java.lang.Class[_]])
  readerSupportedClasses.add(classOf[java.net.URL].asInstanceOf[java.lang.Class[_]])
  readerSupportedClasses.add(classOf[java.io.File].asInstanceOf[java.lang.Class[_]])
  readerSupportedClasses.add(classOf[java.io.InputStream].asInstanceOf[java.lang.Class[_]])

  writerSupportedClasses.add(classOf[String].asInstanceOf[java.lang.Class[_]])
  writerSupportedClasses.add(classOf[OutputStream].asInstanceOf[java.lang.Class[_]])

  @throws[PluginException]
  override def read(doc: document.Document[_]): Value = {
    if (doc.getContent == null) return ujson.Null

    val effectiveParams = EffectiveParams(doc.getMediaType)

    doc.getContent.getClass match {
      case cls if classOf[String].isAssignableFrom(cls) => XML.loadString(doc.getContent.asInstanceOf[String], effectiveParams)
      case cls if classOf[URL].isAssignableFrom(cls) => XML.load(doc.getContent.asInstanceOf[URL], effectiveParams)
      case cls if classOf[File].isAssignableFrom(cls) => XML.loadFile(doc.getContent.asInstanceOf[File], effectiveParams)
      case cls if classOf[InputStream].isAssignableFrom(cls) => XML.load(doc.getContent.asInstanceOf[InputStream], effectiveParams)
      case _ => throw new PluginException(new IllegalArgumentException("Unsupported document content class, use the test method canRead before invoking read"))
    }
  }

  @throws[PluginException]
  override def write[T](input: Value, mediaType: MediaType, targetType: Class[T]): document.Document[T] = {
    if (!input.isInstanceOf[ujson.Obj]) {
      throw new PluginException("Input for XML writer must be an Object, got " + input.getClass)
    }

    val effectiveParams = EffectiveParams(mediaType)
    var charset = mediaType.getCharset
    if (charset == null) {
      charset = Charset.defaultCharset
    }

    var inputAsObj: mutable.Map[String, Value] = input.obj.asInstanceOf[mutable.Map[String, Value]]

    if (mediaType.getParameters.containsKey(DS_ROOT_ELEMENT)) {
      inputAsObj = mutable.Map((mediaType.getParameter(DS_ROOT_ELEMENT), input))
    }

    if (inputAsObj.keys.size > 1) {
      throw new PluginException("Object must have only one root element")
    }

    if (targetType.isAssignableFrom(classOf[String])) {
      val writer = new StringWriter()
      XML.writeXML(writer, inputAsObj.head.asInstanceOf[(String, ujson.Obj)], effectiveParams)

      new DefaultDocument(writer.toString, MediaTypes.APPLICATION_XML).asInstanceOf[document.Document[T]]
    }

    else if (targetType.isAssignableFrom(classOf[OutputStream])) {
      val out = new BufferedOutputStream(new ByteArrayOutputStream)
      XML.writeXML(new OutputStreamWriter(out, charset), inputAsObj.head.asInstanceOf[(String, ujson.Obj)], effectiveParams)

      new DefaultDocument(out, MediaTypes.APPLICATION_XML).asInstanceOf[document.Document[T]]
    }

    else {
      throw new PluginException(new IllegalArgumentException("Unsupported document content class, use the test method canRead before invoking read"))
    }
  }

  case class EffectiveParams(nsSeparator: String, textKeyPrefix: String,
                             cdataKeyPrefix: String, attrKeyPrefix: String,
                             orderingKey: String,
                             omitDeclaration: Boolean, version: String,
                             xmlnsKey: String, nullAsEmpty: Boolean,
                             autoEmpty: Boolean, declarations: Map[String, String])

  object EffectiveParams {
    def apply(mediaType: MediaType): EffectiveParams = {
      val nsSep = Option(mediaType.getParameter(DS_NS_SEPARATOR)).getOrElse(DEFAULT_DS_NS_SEPARATOR)
      val txtPref = Option(mediaType.getParameter(DS_TEXT_KEY_PREFIX)).getOrElse(DEFAULT_DS_TEXT_KEY_PREFIX)
      val cdataPref = Option(mediaType.getParameter(DS_CDATA_KEY_PREFIX)).getOrElse(DEFAULT_DS_CDATA_KEY_PREFIX)
      val attrPref = Option(mediaType.getParameter(DS_ATTRIBUTE_KEY_PREFIX)).getOrElse(DEFAULT_DS_ATTRIBUTE_KEY_PREFIX)
      val orderingKey = Option(mediaType.getParameter(DS_ORDERING_KEY)).getOrElse(DEFAULT_DS_ORDERING_KEY)
      val omitDecl = if (mediaType.getParameter(DS_OMIT_DECLARATION) != null) java.lang.Boolean.parseBoolean(mediaType.getParameter(DS_OMIT_DECLARATION)) else false
      val ver = Option(mediaType.getParameter(DS_VERSION)).getOrElse(DEFAULT_DS_VERSION)
      val xmlns = Option(mediaType.getParameter(DS_ATTRIBUTE_KEY_PREFIX)).getOrElse(DEFAULT_DS_ATTRIBUTE_KEY_PREFIX) + XMLNS_KEY
      val nullEmpty = if (mediaType.getParameter(DS_NULL_AS_EMPTY) != null) java.lang.Boolean.parseBoolean(mediaType.getParameter(DS_NULL_AS_EMPTY)) else false
      val autoEmpty = if (mediaType.getParameter(DS_AUTO_EMPTY) != null) java.lang.Boolean.parseBoolean(mediaType.getParameter(DS_AUTO_EMPTY)) else false
      val declarations: Map[String, String] = mediaType.getParameters.asScala.toList
        .filter(entryVal => entryVal._1.matches(DS_NAMESPACE_DECLARATIONS))
        .map(entryVal => (entryVal._2, entryVal._1.substring(DS_NAMESPACE_DECLARATIONS.length - 3)))
        .map(entry => if (entry._2 == "$") (entry._1, "") else entry)
        .toMap

      EffectiveParams(nsSep, txtPref, cdataPref, attrPref, orderingKey, omitDecl, ver, xmlns, nullEmpty, autoEmpty, declarations)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy