Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TH2-3857 #15

Open
wants to merge 51 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e69c873
Self-closing tag and th2-codec error events (#13)
eugene-zheltov May 27, 2022
717b38d
[TH2-3778] Set codec version from snapshot with fixes
eugene-zheltov Jun 7, 2022
6ab2f0b
[TH2-3778] Set codec version from snapshot with fixes
eugene-zheltov Jun 8, 2022
75773ee
[TH2-3778] Change verison of codec core
eugene-zheltov Jun 8, 2022
d785d78
[TH2-3778] Change verison of codec core
eugene-zheltov Jun 8, 2022
542bde8
[TH2-3778] Change version of codec core
eugene-zheltov Jun 9, 2022
a47c0b1
[TH2-3778] Change version of codec core
eugene-zheltov Jun 10, 2022
09e44f9
[TH2-3778] Change version of codec core to 4.7.1-TH2-3778-tmp-2495038…
eugene-zheltov Jun 14, 2022
1e3527b
[TH2-3857] tmp
eugene-zheltov Jun 28, 2022
4764809
[TH2-3857] tmp-2
eugene-zheltov Jul 4, 2022
4f61923
[TH2-3857] tmp-3
eugene-zheltov Jul 4, 2022
8d785c8
[TH2-3857] tmp-4
eugene-zheltov Jul 7, 2022
74c2c5a
[TH2-3857] tmp-4
eugene-zheltov Jul 11, 2022
68f0a0c
[TH2-3857] Successful parsing; to be refactored
eugene-zheltov Jul 12, 2022
cec90fe
[TH2-3857] Refactor XMLSchemaCore
eugene-zheltov Jul 14, 2022
16f85d0
[TH2-3857] tmp-5
eugene-zheltov Jul 18, 2022
07d3a28
TH2-3778 (#14)
eugene-zheltov Jul 19, 2022
8cfce4c
[TH2-3857] tmp-6
eugene-zheltov Jul 20, 2022
8cf2d64
[TH2-3857] Fix the absence of some list values
eugene-zheltov Jul 25, 2022
62c3030
[TH2-3857] Remove prints and change xsd processing a bit
eugene-zheltov Jul 26, 2022
24d6d97
[TH2-3857] Disable tests
eugene-zheltov Jul 26, 2022
06e3d3e
[TH2-3857] Minor changes
eugene-zheltov Jul 26, 2022
725b78e
Merge branch 'master' into TH2-3857
eugene-zheltov Jul 26, 2022
0c6a616
[TH2-3857] Move StreamReaderDelegateDecorator
eugene-zheltov Jul 27, 2022
7697106
[TH2-3857] Chagne xsdProperties loading
eugene-zheltov Jul 27, 2022
468a849
[TH2-3857] Add th2-id to the log when th2-codec-xml-via-xsd cannot de…
eugene-zheltov Jul 28, 2022
6e996da
[TH2-3857] Minor changes
eugene-zheltov Jul 29, 2022
90a4ce7
Merge remote-tracking branch 'origin/TH2-3857' into TH2-3857
eugene-zheltov Jul 29, 2022
652cf79
[TH2-3857] tmp
eugene-zheltov Aug 1, 2022
c8474de
[TH2-3857] tmp 2
eugene-zheltov Aug 2, 2022
14be420
[TH2-3857] tmp 3
eugene-zheltov Aug 2, 2022
71ff9c2
[TH2-3857] Implement new decoding
eugene-zheltov Aug 3, 2022
17507d0
[TH2-3857] Remove comments
eugene-zheltov Aug 3, 2022
207e5c1
[TH2-3857] Fix repeated elements of lists
eugene-zheltov Aug 3, 2022
cba5310
[TH2-3857] Fix missing empty tags
eugene-zheltov Aug 3, 2022
3b6e346
[TH2-3857] Remove usage of XmlPipelineCodec for now
eugene-zheltov Aug 3, 2022
fe488b8
[TH2-3857] Remove garbage
eugene-zheltov Aug 4, 2022
8d5a873
[TH2-3857] Fix truncated values
eugene-zheltov Aug 5, 2022
cea2111
[TH2-3857] Add missing attributes
eugene-zheltov Aug 5, 2022
bbe67f7
[TH2-3857] Disable test
eugene-zheltov Aug 5, 2022
0fb452b
[TH2-3857] Add prefixes to node names
eugene-zheltov Aug 5, 2022
7791388
[TH2-3857] Remove redundant ":" from node names
eugene-zheltov Aug 5, 2022
06dc868
[TH2-3857] Migrated to StAX
Nikita-Smirnov-Exactpro Aug 9, 2022
23f0005
[TH2-3857] Fixed problem related to release sub-nodes
Nikita-Smirnov-Exactpro Aug 10, 2022
a6aab96
[TH2-3857] Updated common libraries
Nikita-Smirnov-Exactpro Aug 10, 2022
7a66236
[TH2-3857] Consider empty xmlns property
eugene-zheltov Oct 6, 2022
a127615
[TH2-3857] Add a new parameter encodeValidation
eugene-zheltov Feb 16, 2023
3f9843f
[TH2-3857] Update dev-docker-publish.yml and docker-publish.yml
eugene-zheltov Feb 20, 2023
1d327a4
[TH2-3857] Update common version
eugene-zheltov Feb 20, 2023
bb17598
[TH2-3857] Update codec-core version
eugene-zheltov Feb 21, 2023
0510415
[TH2-3857] Update slf4j dependencies
eugene-zheltov Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Codec Xml via Xsd
![version](https://img.shields.io/badge/version-0.0.3-blue.svg)
# Codec Xml via Xsd (0.0.5)
![version](https://img.shields.io/badge/version-0.0.4-blue.svg)

# How it works:

Expand Down Expand Up @@ -57,13 +57,18 @@ Error from validation process can be disabled for test purposes by `dirtyValidat
### Configuration example

* typePointer - Path to message type value for decode (null by default)
* encodeValidation - This flag determines if messages are validated
during encoding (false by default). Note: this validation will significantly
slow down the performance and requires an archive of XSDs

## Temporary disabled
* dirtyValidation - Disable/enable error during validation phase. If disabled all errors will be only visible in log (false by default)
* expectsDeclaration - Disable/enable validation of declaration - is it exist or not (true by default)

```yaml
typePointer: /root/node/node2/type
dirtyValidation: false
expectsDeclaration: false
#dirtyValidation: false
#expectsDeclaration: false
```

For example:
Expand All @@ -77,7 +82,7 @@ spec:
custom-config:
codecSettings:
typePointer: /root/node/node2/type
dirtyValidation: false
# dirtyValidation: false
```

## Required pins
Expand Down Expand Up @@ -142,6 +147,18 @@ spec:

## Changelog

### v0.0.5

* Migrate to StAX parser
* Migrated from common:3.31.5 to common:3.40.0
* Migrated from bom:3.1.0 to common:3.2.0

### v0.0.4

#### Feature:

* Self-closing tags are ignored by the codec

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write about used technology


### v0.0.3

#### Feature:
Expand All @@ -159,4 +176,4 @@ spec:

#### Feature:

* First realization using underscore library as parser for json
* First realization using underscore library as parser for json
15 changes: 7 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ plugins {

ext {
sharedDir = file("${project.rootDir}/shared")
sailfishVersion = '3.2.1752'
}

group = 'com.exactpro.th2'
Expand Down Expand Up @@ -67,22 +66,22 @@ jar {
}

dependencies {
api platform('com.exactpro.th2:bom:3.1.0')
implementation 'com.exactpro.th2:common:3.31.5'
implementation 'com.exactpro.th2:codec:4.2.0'
implementation 'com.exactpro.th2:sailfish-utils:3.12.3'

implementation "com.exactpro.sf:sailfish-core:${sailfishVersion}"
api platform('com.exactpro.th2:bom:3.2.0')
implementation 'com.exactpro.th2:common:3.40.0'
implementation 'com.exactpro.th2:codec:4.7.2'

implementation 'org.slf4j:slf4j-log4j12'
implementation 'org.slf4j:slf4j-api'

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}"
implementation 'io.github.microutils:kotlin-logging:2.1.21'

implementation 'com.fasterxml.jackson.core:jackson-core:2.13.1'
implementation 'com.github.javadev:underscore:1.69'


implementation group: 'org.apache.ws.xmlschema', name: 'xmlschema-core', version: '2.3.0'

testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:${kotlin_version}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
# limitations under the License.
#

release_version=0.0.3
kotlin_version=1.5.31
release_version=0.0.5
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved
kotlin_version=1.5.32
description = 'th2 codec xml via xsd'

vcs_url=https://github.com/th2-net/th2-codec-xml-via-xsd
145 changes: 145 additions & 0 deletions src/main/kotlin/com/exactpro/th2/codec/xml/NodeContent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2022 Exactpro (Exactpro Systems Limited)
*
* 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.
*/
package com.exactpro.th2.codec.xml

import com.exactpro.th2.common.grpc.Message
import com.exactpro.th2.common.grpc.MessageMetadata
import com.exactpro.th2.common.grpc.Value
import com.exactpro.th2.common.message.addField
import com.exactpro.th2.common.value.toValue
import javax.xml.namespace.QName

class NodeContent(
private val nodeName: QName,
decorator: XmlCodecStreamReader
) {
private val messageBuilder = Message.newBuilder().apply {
metadata = MessageMetadata.getDefaultInstance() //FIXME: remove
}
private val textSB = StringBuilder()

private val childNodes: MutableMap<QName, MutableList<NodeContent>> = mutableMapOf()
private var isMessage: Boolean = false
private val isEmpty: Boolean
get() = !isMessage && textSB.isEmpty()

val name: String = nodeName.toNodeName()

init {
decorator.namespaceCount.let { size ->
if (size > 0) {
for (i in 0 until size) {
decorator.getNamespaceURI(i).also { value ->
val uri = value ?: ""
val prefix = decorator.namespaceContext.getPrefix(uri) ?: ""

messageBuilder.addField(makeFieldName(NAMESPACE, prefix, true), uri)
}
}
isMessage = true
}
}
decorator.attributeCount.let { size ->
if (size > 0) {
for (i in 0 until size) {
val localPart = decorator.getAttributeLocalName(i)
val prefix = decorator.getAttributePrefix(i)

messageBuilder.addField(makeFieldName(prefix, localPart, true), decorator.getAttributeValue(i))
}
isMessage = true
}
}
}

fun putChild(name: QName, node: NodeContent) {
childNodes.compute(name) { _, value ->
value
?.apply { add(node) }
?: mutableListOf(node)
}
isMessage = true
}

fun appendText(text: String) {
if (text.isNotBlank()) {
textSB.append(text)
}
}

fun release() {
if (isMessage) {
childNodes.forEach { (name, values) ->
try {
val notEmptyValues = values.filterNot(NodeContent::isEmpty)

if (notEmptyValues.isNotEmpty()) {
val first = notEmptyValues.first()
when (values.size) { // Clarefy type of element: list or single
0 -> error("Sub element $name hasn't got values")
1 -> messageBuilder.addField(first.name, first.toValue())
else -> messageBuilder.addField(
first.name,
notEmptyValues.asSequence()
.map(NodeContent::toValue)
.toListValue()
)
}
}
} catch (e: RuntimeException) {
throw IllegalStateException("The `$name` field can't be released in the `$nodeName` node", e)
}
}
if(textSB.isNotBlank()) {
messageBuilder.addField(TEXT_FIELD_NAME, textSB.toValue())
}
}
}

fun toMessage(): Message {
check(isMessage) {
"The $nodeName node isn't message"
}
return messageBuilder.build()
}

override fun toString(): String {
return "NodeContent(nodeName=$nodeName, childNodes=$childNodes, text=$textSB)"
}

private fun Sequence<Value>.toListValue(): Value = Value.newBuilder().apply {
listValueBuilder.apply {
forEach(::addValues)
}
}.build()

private fun toValue(): Value = if (isMessage) {
messageBuilder.toValue()
} else {
textSB.toValue()
}

companion object {
private const val NAMESPACE = "xmlns"
private const val TEXT_FIELD_NAME = "#text"

private fun QName.toNodeName(): String = makeFieldName(prefix, localPart)

private fun makeFieldName(first: String, second: String, isAttribute: Boolean = false): String {
return "${if (isAttribute) "-" else ""}$first${if (first.isNotBlank() && second.isNotBlank()) ":" else ""}$second"
}
}
}
121 changes: 121 additions & 0 deletions src/main/kotlin/com/exactpro/th2/codec/xml/XmlCodecStreamReader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2022 Exactpro (Exactpro Systems Limited)
*
* 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.
*/
package com.exactpro.th2.codec.xml

import com.exactpro.th2.common.grpc.Message
import com.exactpro.th2.common.grpc.RawMessage
import com.exactpro.th2.common.grpc.Value
import com.exactpro.th2.common.message.addField
import com.exactpro.th2.common.message.direction
import com.exactpro.th2.common.message.getField
import com.exactpro.th2.common.message.message
import com.exactpro.th2.common.message.sequence
import com.exactpro.th2.common.message.sessionAlias
import com.exactpro.th2.common.value.toValue
import com.google.protobuf.TextFormat
import java.io.ByteArrayInputStream
import java.util.*
import javax.xml.stream.XMLInputFactory
import javax.xml.stream.XMLStreamException
import javax.xml.stream.util.StreamReaderDelegate

class XmlCodecStreamReader(
private val rawMessage: RawMessage,
private val pointer: List<String>
)
: StreamReaderDelegate(
XML_INPUT_FACTORY.createXMLStreamReader(
ByteArrayInputStream(rawMessage.body.toByteArray())
)
) {
private lateinit var rootNode: NodeContent
private lateinit var messageType: String

private val elements = Stack<NodeContent>()

@Throws(XMLStreamException::class)
override fun next(): Int = super.next().also { eventCode ->
when (eventCode) {
START_ELEMENT -> {
val qName = name
NodeContent(qName, this).also { nodeContent ->
if (elements.isNotEmpty()) {
elements.peek()
.putChild(qName, nodeContent)
}

elements.push(nodeContent)

if (!this::messageType.isInitialized) {
messageType = localName
}
if (!this::rootNode.isInitialized) {
rootNode = nodeContent
}
}
}
CHARACTERS -> elements.peek().appendText(text)
END_ELEMENT -> elements.pop().also(NodeContent::release)
}
}

fun getMessage(): Message {
check(elements.isEmpty()) {
"Some of XML nodes of ${TextFormat.shortDebugString(rawMessage.metadata.id)} message aren't closed ${elements.joinToString { it.name }}"
}

return message().apply {
val rawMetadata = rawMessage.metadata

if (rawMessage.hasParentEventId()) {
parentEventId = rawMessage.parentEventId
}

addField(rootNode.name, rootNode.toMessage().toValue())

metadataBuilder.apply {
putAllProperties(rawMetadata.propertiesMap)
id = rawMetadata.id
timestamp = rawMetadata.timestamp
protocol = rawMetadata.protocol
messageType = extractMessageType([email protected])
}
}.build()
}

private fun Message.Builder.extractMessageType(defaultMessageType: String): String {
if (pointer.isEmpty()) return defaultMessageType

var currentNode = this.toValue()
pointer.forEachIndexed { index, element ->
check(currentNode.hasMessageValue()) {
"The `${pointer.take(index)}` node (${currentNode.kindCase}) isn't message in the th2 message $sessionAlias:$direction$sequence"
}
currentNode = requireNotNull(currentNode.messageValue.getField(element)) {
"The `${pointer.take(index + 1)}` element isn't found in message $sessionAlias:$direction$sequence"
}
}
check(currentNode.kindCase == Value.KindCase.SIMPLE_VALUE) {
"The `$pointer` node (${currentNode.kindCase}) isn't simple value in the th2 message $sessionAlias:$direction$sequence"
}

return currentNode.simpleValue
}

companion object {
private val XML_INPUT_FACTORY = XMLInputFactory.newInstance()
}
}
Loading