diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c0d6ad4e2b..2f1047bf38 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,6 +26,8 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + env: + MAVEN_OPTS: "-Xms3g -Xmx6g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500" strategy: fail-fast: false @@ -37,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -51,8 +53,9 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # env: + # uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -60,10 +63,8 @@ jobs: # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language - - #- run: | - # make bootstrap - # make release + - run: | + mvn clean package -B -V -e -Pminimal-fix-latest -Dmaven.javadoc.skip=true -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true -Dspotless.check.skip=true -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120" - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index bef3487adf..3a49febb8c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,20 +13,49 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] + os: [ubuntu-latest, macOS-latest] java: [8, 11, 17] fail-fast: false max-parallel: 4 name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ matrix.java }} + cache: 'maven' - name: Test with Maven env: - MAVEN_OPTS: "-Xms3g -Xmx3g" - run: mvn test -B -V -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120" + MAVEN_OPTS: "-Xms3g -Xmx5g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500" + run: mvn install -B -V -Pminimal-fix-latest -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120" + + test-windows: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest] + java: [8, 11, 17] + fail-fast: false + max-parallel: 3 + name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Configure pagefile + uses: al-cheb/configure-pagefile-action@v1.3 + with: + minimum-size: 8GB + maximum-size: 16GB + - name: Set up Windows JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + - name: Test with Maven on Windows + env: + MAVEN_OPTS: "-Xms3g -Xmx5g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500" + run: mvn install -B -V -D"maven.javadoc.skip"="true" -P"skipBundlePlugin,minimal-fix-latest" -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f8f063ce17..77b6120417 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ target/ *.iws # ignore NetBeans configuration nb-configuration.xml +*.bak +.mvn/ +mvn* diff --git a/README.md b/README.md index afd5d7173c..08490c3870 100644 --- a/README.md +++ b/README.md @@ -42,17 +42,17 @@ Pull requests are always welcome! Best is if you added a unit test to show that Fastest: clone the repo and issue the following command. ``` -$ mvn clean package -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin +$ mvn clean package -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500 ``` Slower: if you only want to skip the acceptance test suite: ``` -$ mvn clean package -Dmaven.javadoc.skip=true -DskipAT=true -PskipBundlePlugin +$ mvn clean package -Dmaven.javadoc.skip=true -DskipAT=true -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500 ``` Slow: if you want to run all tests: ``` -$ mvn clean package -Dmaven.javadoc.skip=true -PskipBundlePlugin +$ mvn clean package -Dmaven.javadoc.skip=true -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500 ``` NB: If you want to use the resulting JARs in an OSGi environment you'll have to omit the `-PskipBundlePlugin` option. @@ -62,7 +62,7 @@ When the project is first created, it will not have the generated message classe If the IDE reports some errors after the compilation with `mvn clean package`, try to use `mvn clean install`, like: ``` -$ mvn clean install -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin +$ mvn clean install -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500 ``` ## configuration options @@ -283,3 +283,40 @@ void sendOrderCancelRequest() throws SessionNotFound { Session.sendToTarget(message, "TW", "TARGET"); } ``` + +## QuickFIX/J Runtime + +This project builds artefacts for the standard published FIX specification versions from FIX 4.0 to FIX Latest. + +* ```quickfixj-messages-fix40``` +* ```quickfixj-messages-fix41``` +* ```quickfixj-messages-fix42``` +* ```quickfixj-messages-fix43``` +* ```quickfixj-messages-fix44``` +* ```quickfixj-messages-fix50``` +* ```quickfixj-messages-fix50sp1``` +* ```quickfixj-messages-fix50sp2``` +* ```quickfixj-messages-fixlatest``` +* ```quickfixj-messages-fixt11``` +* ```quickfixj-messages-all``` - includes all of the above + +These artefacts are **test** dependencies of ```quickfixj-core```. They are **not** specified as _runtime_ dependencies, this makes it easier to customise QuickFIX/J deployments. + +If you have no need to customise a FIX integration then you can use the ```org.quickfixj``` artefacts built by this project. Simply include them as dependencies of your application. + +Artefacts for unused FIX specification versions can be omitted from your runtime. +Many integrations will not require ```quickfixj-messages-all``` and need only depend on artefacts for a subset of the FIX standard versions. Please note that FIX Protocol versions 5.0 and later depend on ```quickfixj-messages-fixt11``` which provides the implementation for the FIXT1.1 transport messages. + +Many integrations require specialisation of the FIX Messages, Components and/or Fields. This is accomplished by building and using custom artefacts. Please see [Customising QuickFIX/J](./customising-quickfixj.md) for more detail. + +### Application Dependencies for QuickFIX/J Messages Build + +![image info](./src/main/puml/dependencies_fixt11_fixlatest.png) + +![image info](./src/main/puml/dependencies_qfj_all.png) + +### Application Dependencies for Custom Messages Build + +![image info](./src/main/puml/custom_dependencies.png) + +![image info](./src/main/puml/custom_dependencies_fixt11_fixlatest.png) diff --git a/customising-quickfixj.md b/customising-quickfixj.md new file mode 100644 index 0000000000..0bab497287 --- /dev/null +++ b/customising-quickfixj.md @@ -0,0 +1,94 @@ + +# Customising QuickFIX/J + +The core QuickFIX/J module is agnostic to FIX Protocol Versions. At runtime a QuickFIX/J dictionary with supporting implementation packages is required to use type-safe classes. + +The specification for a FIX integration is called a "Rules of Engagement". The Rules of Engagement can be customised with the mutual agreement of the respective counter-parties. + +The message, component and field implementations can be provided by a specialised build, along with the corresponding QuickFIX/J dictionary for the custom Rules of Engagement. + +The standard distribution of ```quickfixj-core``` can be used with custom artefacts. You need only build artefacts for versions of the Protocol that you use. These can be maintained independently from the QuickFIX/J project, while depending on the QuickFIX/J for the core functionality and tools. + +To build custom artefacts it's helpful to understand how QuickFIX/J builds the Field, Component and Message classes from the QuickFIX/J dictionaries and from [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/). + +The QuickFIX/J reference implementations for FIX versions FIX4.0 to FIX5.0sp2 and for FIXT1.1 are generated from the QuickFIX dictionaries for the specific version. The dictionaries are located in the ```src/main/resources``` directory of the respective modules of the ```quickfixj-messages``` module. +Maintaining the FIX4.0 to FIX5.0sp2 builds intentionally provides consistency with the prior QuickFIX/J 2 release in order to ease migration to QuickFIX/J 3. + +The most recent standard is defined as [FIX Latest](https://www.fixtrading.org/online-specification/). The QuickFIX/J reference implementation for FIX Latest is generated from a [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) repository file. +An implementation or customisation of the FIX Standars derived from the FIX Orchestra repository is known as an "_orchestration_". +The standard FIX Orchestra repository requires some modification to work well with QuickFIX/J. +This is done by the ```quickfixj-orchestration``` module. +The ```quickfixj-orchestration``` module publishes a modified Orchestra artefact which can then be the basis of a custom FIX Latest build using QuickFIX/J . + +The complete reference FIX Latest specification results in a very large distribution. +To use FIX Latest, customisation of the [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) repository is advisable. +Please see [QuickFIX/J Orchestration](./quickfixj-orchestration/readme.md) for details. + +## Customisation Scenarios + +### **Enable the use of ```BigDecimal``` for FIX Decimal Data Types** + +This behaviour is controlled by the ```${generator.decimal}``` build property. It is "false" by default to avoid surprising side effects of incompatible data types. + +To enable the use of ```BigDecimal``` in code generation, set the ```${generator.decimal}``` property to "true" in [quickfixj-messages](./quickfixj-messages/readme.md) and build the message artefacts. + +``` + + true + +``` +See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.** + +### **Incompatible Data Types** + +Some incompatible changes have occurred in the evolution of the FIX protocol. For example see below changes to the type of **OrderQty (38)** : + +|FIX Version|Field Name|FIX Datatype|Base Type|QuickFIX/J Datatype| +|---|---|---|---|---| +|4.0|OrderQty|int|int|```int```| +|4.2|OrderQty|Qty|float|```Double``` or ```BigDecimal```| + +Only one ```quickfix.Field``` class with the same name may be loaded by the Java classloader so only one version of this Field should be in the classpath. QuickFix/J also verifies the data type using the supplied QuickFIX "Dictionary". + +Code generation using ```BigDecimal``` is incompatible at runtime with ```int``` for **OrderQty**. In this case, ```double``` is compatible with ```int``` at run time due to [widening primitive conversion](http://titanium.cs.berkeley.edu/doc/java-langspec-1.0/5.doc.html). + +Runtime incompatibilities can be resolved by: +* Amending the QuickFIX Dictionary to coerce the code generation and/or validation +* Changing the ordering of code generation and/or overwrite behaviour of code generation +* Omitting incompatible versions from your customised build +* Building artefacts independently for the conflicting versions and ensuring they are not used them in the same runtime + +See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.** + +### **Customising the FIX Protocol for specialised Rules of Engagement** + +A Rules of Engagement can include customisation Messages, Components and Fields, including User Defined elements. + +It is not necessary to maintain a fork of the entire QuickFIX/J project to provide customised QuickFIX Dictionaries and to +generate type-safe libraries that are interoperable with QuickFIX/J. + +[FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) is intended for customisation to produce machine-readable Rules of Engagement. + +Consider creating a new project (or projects) to build the Messages, Components and Fields as needed for your specific Rules of Engagement. + +Edit the QuickFIX Dictionary or FIX Orchestra Repository (Orchestration) as required and +build the Messages, Components and Fields packages using the tools provided by the QuickFIX/J projects. + +QuickFIX/J Dictionaries, FIX Orchestra Orchestrations and/or documents can also be generated. + +See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.** + +### **Managing incompatibility with Prior Versions of QuickFIX/J** + +From QuickFIX/J 3.0.0 the code generation for ```quickfix.Field``` prefers the FIX Orchestra Standard. This results in incompatible changes to the names of constants. + +For example : ```SettlType.REGULAR_FX_SPOT_SETTLEMENT``` becomes ```SettlType.REGULAR```. + +The required code changes may be trivial in most cases, but changes are elective. +The following describes how to use ```quickfixj-core``` from QuickFIX/J 3 without needing to implement code changes: +* build the required Message artefacts without the FIX Latest code generation. The Fields will then be generated only from legacy FIX Protocol Versions as they were prior to QuickFIX/J 3.0.0 - **or** +* if you want to use Messages, Components and/or Fields from FIX Latest while preferring legacy constants, +manipulate the order of code generation and/or the over-write behaviour of code behaviour to prefer earlier versions of FIX. +For example, generate FIX Latest first and overwrite the generated Field classes by subsequently running code generation for an earlier version. + +See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.** diff --git a/pom.xml b/pom.xml index 25afb60fee..fa928af0ec 100644 --- a/pom.xml +++ b/pom.xml @@ -51,15 +51,14 @@ http://www.quickfixj.org/jira/ - - 3.5.0 - - quickfixj-codegenerator quickfixj-dictgenerator - quickfixj-core + quickfixj-class-pruner-maven-plugin + quickfixj-orchestration + quickfixj-base quickfixj-messages + quickfixj-core quickfixj-examples quickfixj-all quickfixj-distribution @@ -70,18 +69,21 @@ UTF-8 1.8 2.0.6 - 4.13.2 + 4.11.0 + 2.2 + 5.6.1 8 8 - + 3.8.7 + 3.9.5 3.3.0 3.10.1 3.3.0 - 2.22.2 - 3.19.0 + 3.0.0 + 3.21.2 3.2.1 - 3.4.1 + 3.5.0 3.4.1 3.4.2 5.1.8 @@ -89,8 +91,214 @@ 3.0.0 1.6.13 3.3.0 + 3.3.4 + 3.0.0 + 3.7.1 + 3.3.0 + 1.2 + 8059 + 1.0.2 + 1.5.4 + 1.6.8 + 1.6.8 + 2.3.3 + 2.2.3 + 2.11.0 + 32.0.0-jre + OrchestraFIXLatest.xml + 1.0.2 + 0.9.1 + 2.0.0 + + + + io.fixprotocol.orchestrations + fix-standard + ${fix-orchestra.standard.version} + + + io.fixprotocol.orchestra + repository + ${fix-orchestra.repository.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + + + org.mockito + mockito-core + ${mockito-core.version} + test + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jaxb.version} + + + com.sun.xml.bind + jaxb-impl + runtime + ${jaxb.version} + + + org.apache.mina + mina-core + ${apache.mina.version} + + + commons-io + commons-io + ${commons.io.version} + + + org.apache.maven + maven-plugin-api + ${maven-plugin-api-version} + + + org.apache.maven.shared + maven-shared-utils + ${maven-shared-utils.version} + + + org.apache.maven.shared + file-management + ${file-management.version} + + + org.apache.maven + maven-core + ${maven.version} + + + org.apache.maven + maven-artifact + ${maven.version} + provided + + + org.apache.maven + maven-compat + ${maven.version} + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-annotations.version} + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + ${maven-plugin-testing-harness.version} + + + org.apache.maven + maven-project + provided + 2.2.1 + + + org.codehaus.plexus + plexus-utils + 3.3.1 + + + + com.google.guava + guava + ${guava.version} + + + com.sleepycat + je + 18.3.12 + true + + + + org.dom4j + dom4j + 2.1.3 + true + + + com.cloudhopper.proxool + proxool + ${proxool.version} + true + + + + avalon-framework + avalon-framework-api + + + + commons-logging + commons-logging + + + + + com.cloudhopper.proxool + proxool-cglib + ${proxool.version} + true + + + + avalon-framework + avalon-framework-api + + + + commons-logging + commons-logging + + + + + + jaxen + jaxen + ${jaxen.version} + runtime + + + + org.slf4j + slf4j-jdk14 + ${slf4j.version} + + + + @@ -110,6 +318,7 @@ ${maven-compiler-plugin-version} true + true 2g 4g @@ -132,6 +341,11 @@ maven-surefire-plugin ${maven-surefire-plugin-version} + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin-version} + org.apache.maven.plugins maven-pmd-plugin @@ -165,8 +379,9 @@ jar + true none - 3g + 5g @@ -223,18 +438,113 @@ + + org.codehaus.mojo + xml-maven-plugin + ${xml-maven-plugin-version} + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin-version} + + + net.sourceforge.plantuml + plantuml + ${plantuml-version} + + + com.github.jeluard + plantuml-maven-plugin + ${plantuml-maven-plugin-version} + + + net.sourceforge.plantuml + plantuml + + + + + maven-invoker-plugin + 3.2.2 + + + maven-plugin-plugin + 3.10.2 + + + maven-install-plugin + 2.5.2 + + + maven-clean-plugin + 3.1.0 + + + org.quickfixj.orchestra + quickfixj-from-fix-orchestra-code-generator-maven-plugin + ${org.quickfixj.orchestra.tools.version} + + + org.quickfixj.orchestra + quickfixj-from-fix-orchestra-dictionary-generator-maven-plugin + ${org.quickfixj.orchestra.tools.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.1.0 + - org.apache.felix maven-bundle-plugin + + + com.github.jeluard + plantuml-maven-plugin + + + ${basedir} + + **/*.puml + + + true + + + + net.sourceforge.plantuml + plantuml + ${plantuml-version} + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-versions + + enforce + + + + + ${maven.version} + + + + + + - skipBundlePlugin @@ -243,15 +553,15 @@ - - org.apache.felix - maven-bundle-plugin - - - - - - + + org.apache.felix + maven-bundle-plugin + + + + + + @@ -301,7 +611,6 @@ - ossrh diff --git a/quickfixj-all/pom.xml b/quickfixj-all/pom.xml index ac9843cc7b..e07a73f1ab 100644 --- a/quickfixj-all/pom.xml +++ b/quickfixj-all/pom.xml @@ -21,22 +21,17 @@ org.quickfixj - quickfixj-core - ${project.version} - - - org.quickfixj - quickfixj-messages-all + quickfixj-base ${project.version} org.quickfixj - quickfixj-codegenerator + quickfixj-core ${project.version} org.quickfixj - quickfixj-dictgenerator + quickfixj-messages-all ${project.version} @@ -77,8 +72,7 @@ - + com.sleepycat*;resolution:=optional, org.apache.maven*;resolution:=optional, diff --git a/quickfixj-base/pom.xml b/quickfixj-base/pom.xml new file mode 100644 index 0000000000..efd8994e32 --- /dev/null +++ b/quickfixj-base/pom.xml @@ -0,0 +1,291 @@ + + 4.0.0 + + org.quickfixj + quickfixj-parent + 3.0.0-SNAPSHOT + + + quickfixj-base + jar + + QuickFIX/J Base + Base classes for messages and fields + http://www.quickfixj.org + + + + + + + org.junit.vintage + junit-vintage-engine + test + + + org.mockito + mockito-core + test + + + org.hamcrest + hamcrest + test + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + + + + + + ${project.basedir}/src/test/resources + + + ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources + + + ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources + + + ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources + + + ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + quickfix/fixlatest/** + + + + + org.codehaus.mojo + xml-maven-plugin + + + extractRequiredFields + generate-sources + + transform + + + + + ${project.basedir}/../quickfixj-orchestration/target/generated-resources + ${project.build.directory}/generated-resources/extracted + + ${orchestra.file} + + ${project.basedir}/src/main/xsl/extractRequiredFields.xsl + + + + + + addRequiredFields + generate-sources + + transform + + + + + ${project.build.directory}/generated-resources/extracted + ${project.build.directory}/generated-resources + + ${orchestra.file} + + ${project.basedir}/src/main/xsl/addRequiredFields.xsl + + + + + + + + + org.quickfixj.orchestra + quickfixj-from-fix-orchestra-code-generator-maven-plugin + + + generate-sources + + codeGeneration + + fixLatest + + + + ${project.build.directory}/generated-resources/${orchestra.file} + ${project.build.directory}/generated-sources + + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + quickfix/* + org/** + quickfix/field/* + quickfix/field/converter/* + + + quickfix/fixt11 + quickfix/fixlatest + + + + + org.apache.maven.plugins + maven-source-plugin + + + quickfix/* + quickfix/mina/* + org/** + quickfix/field/* + quickfix/field/converter/* + + + quickfix/fixt11 + quickfix/fixlatest + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + true + 8 + src/main/java:${project.build.directory}/generated-sources + quickfix.fixt11:quickfix.fixlatest + + + + + + org.apache.felix + maven-bundle-plugin + + + quickfix,quickfix.*,org.quickfixj,org.quickfixj.* + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin-version} + + + true + + + + maven-jxr-plugin + 2.5 + + + + + + + + surefire-java8 + + 1.8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + false + + + + + + + + + + + + + + surefire + + [1.9,) + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + false + + + + + + + + + + + + + diff --git a/quickfixj-base/readme.md b/quickfixj-base/readme.md new file mode 100644 index 0000000000..a70d37a5b3 --- /dev/null +++ b/quickfixj-base/readme.md @@ -0,0 +1,19 @@ +# quickfixj-base + +The ```quickfixj-base``` module consists of Java classes on which generated QuickFIX/J Fields, Components and Messages depend. + +There is a mutual dependency for a small number of generated Fields used in the ```Standard Header``` and ```Standard Trailer```. These Fields are therefore generated by this module, compiled by it and provided in the jar artefact. + +To assure Java runtime compatibility these Fields should not be included in other QuickFIX/J or custom artefacts. The list of fields can be found in the [xslt transform](./src/main/xsl/extractRequiredFields.xsl) used by the build in ```./src/main/xsl/```. + +The Fields in question are those defined in the (perhaps counter-intuitive) template match expression. + + Example: + +``` + + + + + + + + + + + Used when a message is sent via a "hub" or "service bureau". If A sends to Q (the hub) who then sends to B via a separate FIX session, then when Q sends to B the value of this field should represent the SendingTime on the message A sent to Q. (always expressed in UTC (Universal Time Coordinated, also known as "GMT") + + + + + + + + + + + + + + + + + + + diff --git a/quickfixj-base/src/main/xsl/extractRequiredFields.xsl b/quickfixj-base/src/main/xsl/extractRequiredFields.xsl new file mode 100644 index 0000000000..be7aa86e48 --- /dev/null +++ b/quickfixj-base/src/main/xsl/extractRequiredFields.xsl @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quickfixj-core/src/test/java/quickfix/BusinessRejectReasonTextTest.java b/quickfixj-base/src/test/java/quickfix/BusinessRejectReasonTextTest.java similarity index 100% rename from quickfixj-core/src/test/java/quickfix/BusinessRejectReasonTextTest.java rename to quickfixj-base/src/test/java/quickfix/BusinessRejectReasonTextTest.java diff --git a/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java new file mode 100644 index 0000000000..edf5f5adc8 --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java @@ -0,0 +1,903 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.net.URL; +import java.net.URLClassLoader; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import quickfix.field.MsgType; +import quickfix.field.NoHops; + +public class DataDictionaryTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testDictionary() throws Exception { + DataDictionary dd = getDictionary(); + + assertEquals("wrong field name", "Currency", dd.getFieldName(15)); + assertEquals("wrong value description", "BUY", dd.getValueName(4, "B")); + assertEquals("wrong value for given value name", "2", dd.getValue(54, "SELL")); + assertEquals("wrong value type", FieldType.STRING, dd.getFieldType(1)); + assertEquals("wrong version", FixVersions.BEGINSTRING_FIX44, dd.getVersion()); + assertFalse("unexpected field values existence", dd.hasFieldValue(1)); + assertTrue("unexpected field values nonexistence", dd.hasFieldValue(4)); + assertFalse("unexpected field existence", dd.isField(9999)); + assertTrue("unexpected field nonexistence", dd.isField(4)); + assertTrue("unexpected field value existence", !dd.isFieldValue(4, "C")); + assertTrue("unexpected field value nonexistence", dd.isFieldValue(4, "B")); + assertTrue("wrong group info", dd.isGroup("A", 384)); + assertFalse("wrong group info", dd.isGroup("A", 1)); + assertNotNull("wrong group info", dd.getGroup("6", 232)); + assertTrue("incorrect header field", dd.isHeaderField(8)); + assertFalse("incorrect header field", dd.isHeaderField(1)); + assertTrue("incorrect trailer field", dd.isTrailerField(89)); + assertFalse("incorrect trailer field", dd.isTrailerField(1)); + assertTrue("incorrect message field", dd.isMsgField("A", 98)); + assertFalse("incorrect message field", dd.isMsgField("A", 1)); + // component field + assertTrue("incorrect message field", dd.isMsgField("6", 235)); + // group->component field + //assertTrue("incorrect message field", dd.isMsgField("6", 311)); + assertTrue("incorrect message type", dd.isMsgType("A")); + assertFalse("incorrect message type", dd.isMsgType("%")); + assertTrue("incorrect field requirement", dd.isRequiredField("A", 98)); + assertFalse("incorrect field requirement", dd.isRequiredField("A", 95)); + assertEquals("incorrect field name", "Account", dd.getFieldName(1)); + assertEquals("incorrect msg type", "0", dd.getMsgType("Heartbeat")); + assertEquals("incorrect msg type", "B", dd.getMsgType("News")); + assertFalse(dd.isMsgField("UNKNOWN_TYPE", 1)); + } + + @Test + public void testMissingFieldAttributeForRequired() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + assertConfigErrorForMissingAttributeRequired(data); + } + + private void assertConfigErrorForMissingAttributeRequired(String data) { + try { + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } catch (ConfigError e) { + // Expected + assertTrue(e.getMessage().contains("does not have a 'required'")); + } + } + + @Test + public void testMissingComponentAttributeForRequired() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + assertConfigErrorForMissingAttributeRequired(data); + } + + @Test + public void testMissingGroupAttributeForRequired() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + assertConfigErrorForMissingAttributeRequired(data); + } + + @Test + public void testHeaderTrailerRequired() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + DataDictionary dd = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(1, dd.getNumMessageCategories()); + assertEquals("0", dd.getMsgType("Heartbeat")); + + assertTrue("BeginString should be required", dd.isRequiredHeaderField(8)); + assertFalse("OnBehalfOfCompID should not be required", dd.isRequiredHeaderField(115)); + assertTrue("Checksum should be required", dd.isRequiredTrailerField(10)); + assertFalse("Signature should not be required", dd.isRequiredTrailerField(89)); + + // now tests for fields that aren't actually in the dictionary - should come back false + assertFalse("Unknown header field shows up as required", dd.isRequiredHeaderField(666)); + assertFalse("Unknown trailer field shows up as required", dd.isRequiredTrailerField(666)); + } + + @Test + public void testMessageWithNoChildren40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=msg"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessageWithTextElement40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=msg"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessagesWithNoChildren40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No messages defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessagesWithTextElement40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No messages defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testHeaderWithNoChildren40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += ""; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=HEADER"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testHeaderWithTextElement40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=HEADER"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testTrailerWithNoChildren40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=TRAILER"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testTrailerWithTextElement40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=TRAILER"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testFieldsWithNoChildren40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testFieldsWithTextElement40() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessageWithNoChildren50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=msg"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessageWithTextElement50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields found: msgType=msg"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessagesWithNoChildren50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No messages defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testMessagesWithTextElement50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No messages defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testHeaderWithNoChildren50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += ""; + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testHeaderWithTextElement50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testTrailerWithNoChildren50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testTrailerWithTextElement50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testFieldsWithNoChildren50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += ""; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testFieldsWithTextElement50() throws Exception { + String data = ""; + data += ""; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += ""; + + expectedException.expect(ConfigError.class); + expectedException.expectMessage("No fields defined"); + + new DataDictionary(new ByteArrayInputStream(data.getBytes())); + } + + @Test + public void testHeaderGroupField() throws Exception { + DataDictionary dd = getDictionary(); + assertTrue(dd.isHeaderGroup(NoHops.FIELD)); + } + + // QF C++ treats the string argument as a filename although it's + // named 'url'. QFJ string argument can be either but this test + // ensures the DD works correctly with a regular file path. + @Test + public void testDictionaryWithFilename() throws Exception { + DataDictionary dd = new DataDictionary("FIX40.xml"); + assertEquals("wrong field name", "Currency", dd.getFieldName(15)); + // It worked! + } + + // Support finding DD in classpath + @Test + public void testDictionaryInClassPath() throws Exception { + URLClassLoader customClassLoader = new URLClassLoader(new URL[] { new URL("file:etc") }, + getClass().getClassLoader()); + Thread currentThread = Thread.currentThread(); + ClassLoader previousContextClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(customClassLoader); + try { + DataDictionary dd = new DataDictionary("FIX40.xml"); + assertEquals("wrong field name", "Currency", dd.getFieldName(15)); + // It worked! + } finally { + currentThread.setContextClassLoader(previousContextClassLoader); + } + } + + // QFJ-235 + @Test + public void testWildcardEnumValue() throws Exception { + DataDictionary dd = getDictionary(); + assertTrue(dd.isFieldValue(65, "FOO")); + } + + @Test + public void testMessageCategory() throws Exception { + DataDictionary dd = getDictionary(); + assertTrue(dd.isAdminMessage(MsgType.LOGON)); + assertFalse(dd.isAppMessage(MsgType.LOGON)); + assertFalse(dd.isAdminMessage(MsgType.NEW_ORDER_SINGLE)); + assertTrue(dd.isAppMessage(MsgType.NEW_ORDER_SINGLE)); + } + + // QFJ-535 + @Test + public void testValidateFieldsOutOfOrderForGroups() throws Exception { + final DataDictionary dictionary = new DataDictionary(getDictionary()); + dictionary.setCheckUnorderedGroupFields(false); + Message messageWithGroupLevel1 = new Message( + "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" + + "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" + + "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001736=currency\001661=1\00110=130\001", + dictionary); + dictionary.validate(messageWithGroupLevel1); + + Message messageWithGroupLevel2 = new Message( + "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" + + "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" + + "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001539=1\001524=1\001538=1\001525=a\00110=145\001", + dictionary); + dictionary.validate(messageWithGroupLevel2); + } + + @Test + public void shouldLoadDictionaryWhenExternalDTDisEnabled() throws ConfigError { + new DataDictionary("FIX_External_DTD.xml", DocumentBuilderFactory::newInstance); + } + + @Test + public void shouldFailToLoadDictionaryWhenExternalDTDisDisabled() { + try { + new DataDictionary("FIX_External_DTD.xml"); + fail("should fail to load dictionary with external DTD"); + } catch (ConfigError e) { + assertEquals("External DTD: Failed to read external DTD 'mathml.dtd', because 'http' access is not allowed due to restriction set by the accessExternalDTD property.", e.getCause().getCause().getMessage()); + } + } + + /** + * For FIX.Latest a minor version is not required. + */ + @Test + public void testMissingMinorVersion() throws Exception { + String data = ""; + data += ""; + data = getCommonDataDictionaryString(data); + + DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(0, dataDictionary.getMinorVersion()); + } + + @Test + public void testFixLatestMajorVersion() throws Exception { + String data = ""; + data += ""; + data = getCommonDataDictionaryString(data); + + DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(0, dataDictionary.getMinorVersion()); + assertEquals("FIX.Latest", dataDictionary.getFullVersion()); + } + + @Test + public void testFixLatestMajorVersionAndEP() throws Exception { + String data = ""; + data += ""; + data = getCommonDataDictionaryString(data); + + DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(0, dataDictionary.getMinorVersion()); + assertEquals("FIX.Latest_EP260", dataDictionary.getFullVersion()); + } + + @Test + public void testSP() throws Exception { + String data = ""; + data += ""; + data = getCommonDataDictionaryString(data); + + DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(0, dataDictionary.getMinorVersion()); + assertEquals("FIX.5.0", dataDictionary.getVersion()); + assertEquals("FIX.5.0SP2", dataDictionary.getFullVersion()); + } + + @Test + public void testEPAndSP() throws Exception { + String data = ""; + data += ""; + data = getCommonDataDictionaryString(data); + + DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); + assertEquals(0, dataDictionary.getMinorVersion()); + assertEquals("FIX.5.0", dataDictionary.getVersion()); + assertEquals("FIX.5.0SP2_EP260", dataDictionary.getFullVersion()); + } + + private String getCommonDataDictionaryString(String data) { + data += "
"; + data += " "; + data += "
"; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += " "; + data += "
"; + return data; + } + + + // + // Group Validation Tests in RepeatingGroupTest + // + + private static DataDictionary testDataDictionary; + + /** + * Returns a singleton FIX 4.4 data dictionary. + * NOTE: the returned dictionary must not be modified in any way + * (e.g. by calling any of its setter methods). If it needs to + * be modified, it can be cloned by using the + * {@link DataDictionary#DataDictionary(DataDictionary) + * DataDictionary copy constructor}. + * + * @return a singleton FIX 4.4 data dictionary + * @throws Exception if the data dictionary cannot be loaded + */ + public static DataDictionary getDictionary() throws Exception { + if (testDataDictionary == null) { + testDataDictionary = getDictionary("FIX44.xml"); + } + return testDataDictionary; + } + + /** + * Loads and returns the named data dictionary. + * + * @param fileName the data dictionary file name (e.g. "FIX44.xml") + * @return a new data dictionary instance + * @throws Exception if the named data dictionary cannot be loaded + */ + public static DataDictionary getDictionary(String fileName) throws Exception { + return new DataDictionary(DataDictionaryTest.class.getClassLoader() + .getResourceAsStream(fileName)); + } + +} diff --git a/quickfixj-core/src/test/java/quickfix/DayConverterTest.java b/quickfixj-base/src/test/java/quickfix/DayConverterTest.java similarity index 100% rename from quickfixj-core/src/test/java/quickfix/DayConverterTest.java rename to quickfixj-base/src/test/java/quickfix/DayConverterTest.java diff --git a/quickfixj-core/src/test/java/quickfix/DictionaryTest.java b/quickfixj-base/src/test/java/quickfix/DictionaryTest.java similarity index 100% rename from quickfixj-core/src/test/java/quickfix/DictionaryTest.java rename to quickfixj-base/src/test/java/quickfix/DictionaryTest.java diff --git a/quickfixj-base/src/test/java/quickfix/ExceptionTest.java b/quickfixj-base/src/test/java/quickfix/ExceptionTest.java new file mode 100644 index 0000000000..e87eb0adf1 --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/ExceptionTest.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import junit.framework.TestCase; + +public class ExceptionTest extends TestCase { + + public void testDoNotSend() { + new DoNotSend(); + } + + public void testIncorrectDataFormat() { + IncorrectDataFormat e = new IncorrectDataFormat(5, "test"); + assertEquals(5, e.getField()); + assertEquals("test", e.getData()); + } + + public void testIncorrectTagValue() { + new IncorrectTagValue(5); + IncorrectTagValue e = new IncorrectTagValue(5, "test"); + } + + public void testRuntimeError() { + new RuntimeError(); + new RuntimeError("test"); + new RuntimeError(new Exception()); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/FieldConvertersTest.java b/quickfixj-base/src/test/java/quickfix/FieldConvertersTest.java similarity index 100% rename from quickfixj-core/src/test/java/quickfix/FieldConvertersTest.java rename to quickfixj-base/src/test/java/quickfix/FieldConvertersTest.java diff --git a/quickfixj-base/src/test/java/quickfix/FieldMapTest.java b/quickfixj-base/src/test/java/quickfix/FieldMapTest.java new file mode 100644 index 0000000000..30490b9adc --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/FieldMapTest.java @@ -0,0 +1,83 @@ +package quickfix; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Iterator; +import java.util.Optional; + +import org.junit.Test; + +/** + * Tests the {@link FieldMap} class. + * Specifically, verifies that the setters for {@link UtcTimeStampField} work correctly. + * + * @author toli + */ +public class FieldMapTest { + + private void testOrdering(int[] vals, int[] order, int[] expected) { + FieldMap map = new Message(order); + for (int v : vals) + map.setInt(v, v); + Iterator> it = map.iterator(); + for (int e : expected) + assertEquals(String.valueOf(e), it.next().getObject()); + } + + @Test + public void testOrdering() { + testOrdering(new int[]{1, 2, 3}, null, new int[]{1, 2, 3}); + testOrdering(new int[]{3, 2, 1}, null, new int[]{1, 2, 3}); + testOrdering(new int[]{1, 2, 3}, new int[]{1, 2, 3}, new int[]{1, 2, 3}); + testOrdering(new int[]{3, 2, 1}, new int[]{1, 2, 3}, new int[]{1, 2, 3}); + testOrdering(new int[]{1, 2, 3}, new int[]{1, 3, 2}, new int[]{1, 3, 2}); + testOrdering(new int[]{3, 2, 1}, new int[]{1, 3, 2}, new int[]{1, 3, 2}); + testOrdering(new int[]{1, 2, 3}, new int[]{1, 3}, new int[]{1, 3, 2}); + testOrdering(new int[]{3, 2, 1}, new int[]{1, 3}, new int[]{1, 3, 2}); + testOrdering(new int[]{1, 2, 3}, new int[]{3, 1}, new int[]{3, 1, 2}); + testOrdering(new int[]{3, 2, 1}, new int[]{3, 1}, new int[]{3, 1, 2}); + } + + @Test + public void testOptionalString() { + FieldMap map = new Message(); + map.setField(new StringField(128, "bigbank")); + Optional optValue = map.getOptionalString(128); + assertTrue(optValue.isPresent()); + assertEquals("bigbank", optValue.get()); + assertFalse(map.getOptionalString(129).isPresent()); + } + + @Test + public void testOptionalDecimal() { + FieldMap map = new Message(); + map.setField(new DecimalField(44, new BigDecimal("1565.10"))); + Optional optValue = map.getOptionalDecimal(44); + assertTrue(optValue.isPresent()); + assertEquals(0, optValue.get().compareTo(new BigDecimal("1565.10"))); + assertFalse(map.getOptionalDecimal(6).isPresent()); + } + + @Test + public void testNullFieldException() { + FieldMap map = new Message(); + StringField field = new StringField(0, null); + assertThrows(FieldException.class, () -> map.setField(field)); + } + + @Test + public void testRemoveGroup() { + FieldMap map = new Message(); + Group group = new Group(73, 11); + map.addGroup(group); + assertTrue(map.hasGroup(73)); + map.removeGroup(73); + assertFalse(map.hasGroup(73)); + } +} diff --git a/quickfixj-base/src/test/java/quickfix/FieldTest.java b/quickfixj-base/src/test/java/quickfix/FieldTest.java new file mode 100644 index 0000000000..a42f7c560d --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/FieldTest.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Date; + +import org.junit.Test; +import org.quickfixj.CharsetSupport; + +public class FieldTest { + + private void testFieldCalcuations(String value, int checksum, int length) { + Field field = new Field<>(12, value); + field.setObject(value); + assertEquals("12=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + + value = value.substring(0, value.length() - 1) + (char)(value.charAt(value.length() - 1) + 1); + checksum = (checksum + 1) & 0xFF; + field.setObject(value); + assertEquals("12=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + + field.setTag(13); + checksum = (checksum + 1) & 0xFF; + assertEquals("13=" + value, field.toString()); + assertEquals(checksum, field.getChecksum()); + assertEquals(length, field.getLength()); + } + + @Test + public void testFieldCalculationsWithDefaultCharset() { + testFieldCalcuations("VALUE", 30, 9); + } + + @Test + public void testFieldCalculationsWithUTF8Charset() throws UnsupportedEncodingException { + CharsetSupport.setCharset("UTF-8"); + try { + testFieldCalcuations("\u6D4B\u9A8C\u6570\u636E", 50, 16); + } finally { + CharsetSupport.setCharset(CharsetSupport.getDefaultCharset()); + } + } + + @Test + public void testDateField() { + DateField field = new DateField(11); + Date date = new Date(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new DateField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcDateOnlyField() { + UtcDateOnlyField field = new UtcDateOnlyField(11); + LocalDate date = LocalDate.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcDateOnlyField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcTimeOnlyField() { + UtcTimeOnlyField field = new UtcTimeOnlyField(11); + LocalTime date = LocalTime.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcTimeOnlyField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testUtcTimeStampField() { + UtcTimeStampField field = new UtcTimeStampField(11); + LocalDateTime date = LocalDateTime.now(); + field.setValue(date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + field = new UtcTimeStampField(11, date); + assertEquals(11, field.getTag()); + assertEquals(date, field.getValue()); + } + + @Test + public void testBooleanField() { + BooleanField field = new BooleanField(11); + field.setValue(true); + assertEquals(11, field.getTag()); + assertTrue(field.getValue()); + field.setValue(Boolean.FALSE); + assertEquals(11, field.getTag()); + assertFalse(field.getValue()); + field = new BooleanField(22, true); + assertEquals(22, field.getTag()); + assertTrue(field.getValue()); + field = new BooleanField(33, Boolean.TRUE); + assertEquals(33, field.getTag()); + assertTrue(field.getValue()); + } + + @Test + public void testDoubleField() { + DoubleField field = new DoubleField(11); + field.setValue(12.3); + assertEquals(11, field.getTag()); + assertEquals(12.3, field.getValue(), 0); + field.setValue(new Double(23.4)); + assertEquals(11, field.getTag()); + assertEquals(23.4, field.getValue(), 0); + field = new DoubleField(22, 34.5); + assertEquals(22, field.getTag()); + assertEquals(34.5, field.getValue(), 0); + field = new DoubleField(33, new Double(45.6)); + assertEquals(33, field.getTag()); + assertEquals(45.6, field.getValue(), 0); + } + + @Test(expected = NumberFormatException.class) + public void testDoubleFieldException() { + DoubleField field = new DoubleField(11, Double.NaN); + } + + @Test + public void testDecimalField() { + DecimalField field = new DecimalField(11); + field.setValue(12.3); + assertEquals(11, field.getTag()); + assertEquals(BigDecimal.valueOf(12.3), field.getValue()); + field.setValue(23.4); + assertEquals(11, field.getTag()); + assertEquals(BigDecimal.valueOf(23.4), field.getValue()); + field = new DecimalField(22, 34.5); + assertEquals(22, field.getTag()); + assertEquals(BigDecimal.valueOf(34.5), field.getValue()); + field = new DecimalField(33, 45.6); + assertEquals(33, field.getTag()); + assertEquals(BigDecimal.valueOf(45.6), field.getValue()); + } + + @Test(expected = NumberFormatException.class) + public void testDecimalFieldException() { + DecimalField field = new DecimalField(11, Double.POSITIVE_INFINITY); + } + + @Test + public void testCharField() { + CharField field = new CharField(11); + field.setValue('x'); + assertEquals(11, field.getTag()); + assertEquals('x', field.getValue()); + field.setValue(Character.valueOf('X')); + assertEquals(11, field.getTag()); + assertEquals('X', field.getValue()); + field = new CharField(22, 'a'); + assertEquals(22, field.getTag()); + assertEquals('a', field.getValue()); + field = new CharField(33, Character.valueOf('A')); + assertEquals(33, field.getTag()); + assertEquals('A', field.getValue()); + } + + @Test + public void testIntField() { + IntField field = new IntField(11); + field.setValue(12); + assertEquals(11, field.getTag()); + assertEquals(12, field.getValue()); + field.setValue(Integer.valueOf(23)); + assertEquals(11, field.getTag()); + assertEquals(23, field.getValue()); + field = new IntField(22, 23); + assertEquals(22, field.getTag()); + assertEquals(23, field.getValue()); + field = new IntField(33, Integer.valueOf(44)); + assertEquals(33, field.getTag()); + assertEquals(44, field.getValue()); + } + + @Test + public void testFieldhashCode() throws Exception { + assertEqualsAndHash(new IntField(11, 100), new IntField(11, 100)); + assertEqualsAndHash(new DoubleField(11, 100.0), new DoubleField(11, 100.0)); + assertEqualsAndHash(new StringField(11, "foo"), new StringField(11, "foo")); + assertEqualsAndHash(new BooleanField(11, true), new BooleanField(11, true)); + assertEqualsAndHash(new CharField(11, 'x'), new CharField(11, 'x')); + LocalDateTime date = LocalDateTime.now(); + assertEqualsAndHash(new UtcDateOnlyField(11, date.toLocalDate()), new UtcDateOnlyField(11, date.toLocalDate())); + assertEqualsAndHash(new UtcTimeOnlyField(11, date.toLocalTime()), new UtcTimeOnlyField(11, date.toLocalTime())); + assertEqualsAndHash(new UtcTimeStampField(11, date), new UtcTimeStampField(11, date)); + } + + private void assertEqualsAndHash(Field field1, Field field2) { + assertEquals("fields not equal", field1, field2); + assertEquals("fields hashcode not equal", field1.hashCode(), field2.hashCode()); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/FileUtilTest.java b/quickfixj-base/src/test/java/quickfix/FileUtilTest.java similarity index 97% rename from quickfixj-core/src/test/java/quickfix/FileUtilTest.java rename to quickfixj-base/src/test/java/quickfix/FileUtilTest.java index c50b4a21a4..56b10e29ce 100644 --- a/quickfixj-core/src/test/java/quickfix/FileUtilTest.java +++ b/quickfixj-base/src/test/java/quickfix/FileUtilTest.java @@ -43,7 +43,7 @@ public void testFileLocation() throws Exception { @Test public void testClassResourceLocation() throws Exception { - InputStream in = FileUtil.open(Message.class, "Session.class"); + InputStream in = FileUtil.open(Message.class, "FixVersions.class"); in.close(); assertNotNull("Resource not found", in); } diff --git a/quickfixj-base/src/test/java/quickfix/MessageTest.java b/quickfixj-base/src/test/java/quickfix/MessageTest.java new file mode 100644 index 0000000000..91432d48a5 --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/MessageTest.java @@ -0,0 +1,741 @@ +package quickfix; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Calendar; +import java.util.TimeZone; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import quickfix.field.ApplExtID; +import quickfix.field.ApplVerID; +import quickfix.field.BeginString; +import quickfix.field.BodyLength; +import quickfix.field.CheckSum; +import quickfix.field.CstmApplVerID; +import quickfix.field.MsgSeqNum; +import quickfix.field.MsgType; +import quickfix.field.SecureData; +import quickfix.field.SenderCompID; +import quickfix.field.SendingTime; +import quickfix.field.SessionRejectReason; +import quickfix.field.Signature; +import quickfix.field.SignatureLength; +import quickfix.field.TargetCompID; +import quickfix.field.TargetSubID; + +public class MessageTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + + @Test + public void testRepeatingField() throws Exception { + final Message m = new Message( + "8=FIX.4.0\0019=100\00135=D\00134=2\00149=TW\00156=ISLD\00111=ID\00121=1\001" + + "40=1\00154=1\00140=2\00138=200\00155=INTC\00110=160\001"); + assertFalse("message should be invalid", m.hasValidStructure()); + assertEquals("wrong invalid tag", 40, m.getInvalidTag()); + } + + @Test + public void testHeaderCustomFieldOrdering() throws Exception { + + class MyMessage extends Message { + + final int[] headerFieldOrder = { + BeginString.FIELD, + BodyLength.FIELD, + MsgType.FIELD, + TargetSubID.FIELD, + SendingTime.FIELD, + MsgSeqNum.FIELD, + SenderCompID.FIELD, + TargetCompID.FIELD + }; + + public MyMessage() { + super(); + header = new Header(headerFieldOrder); + } + } + + final MyMessage myMessage = new MyMessage(); + + myMessage.getHeader().setField(new SenderCompID("foo")); + myMessage.getHeader().setField(new MsgSeqNum(22)); + myMessage.getHeader().setString(SendingTime.FIELD, "20120922-11:00:00"); + myMessage.getHeader().setField(new TargetCompID("bar")); + + assertTrue(myMessage.toString().contains("52=20120922-11:00:00\00134=22\00149=foo\00156=bar")); + } + + @Test + public void testHeaderFieldWithCustomTransportDictionaryConstructorReadsHeaderField() throws Exception { + + final DataDictionary customSessionDictionary = new DataDictionary("FIXT11_Custom_Test.xml"); + customSessionDictionary.setAllowUnknownMessageFields(false); + + final DataDictionary standardSessionDictionary = new DataDictionary("FIXT11.xml"); + standardSessionDictionary.setAllowUnknownMessageFields(false); + + final DataDictionary applicationDictionary = new DataDictionary("FIX50.xml"); + applicationDictionary.setAllowUnknownMessageFields(false); + + final String sep = "\001"; + final StringBuilder sb = new StringBuilder(); + sb.append("8=FIXT1.1"); + sb.append(sep); + sb.append("9=112"); + sb.append(sep); + sb.append("35=6"); + sb.append(sep); + sb.append("49=SENDER_COMP_ID"); + sb.append(sep); + sb.append("56=TARGET_COMP_ID"); + sb.append(sep); + sb.append("34=20"); + sb.append(sep); + sb.append("52=20120922-11:00:00"); + sb.append(sep); + sb.append("12312=foo"); + sb.append(sep); + sb.append("23=123456"); + sb.append(sep); + sb.append("28=N"); + sb.append(sep); + sb.append("55=[N/A]"); + sb.append(sep); + sb.append("54=1"); + sb.append(sep); + sb.append("27=U"); + sb.append(sep); + sb.append("10=52"); + sb.append(sep); + final String messageData = sb.toString(); + + final Message standardMessage = new Message(messageData, standardSessionDictionary, applicationDictionary, true); + + // Test that field is in body not the header + assertTrue(standardMessage.toString().contains("12312=foo")); + assertFalse(standardMessage.getHeader().isSetField(12312)); + assertTrue(standardMessage.isSetField(12312)); + assertEquals("foo", standardMessage.getString(12312)); + + // Test that field is correctly classified in header with customSessionDictionary + final Message customMessage = new Message(messageData, customSessionDictionary, applicationDictionary, true); + assertTrue(customMessage.toString().contains("12312=foo")); + assertTrue(customMessage.getHeader().isSetField(12312)); + assertEquals("foo", customMessage.getHeader().getString(12312)); + assertFalse(customMessage.isSetField(12312)); + } + + @Test + public void testTrailerCustomFieldOrdering() throws Exception { + + class MyMessage extends Message { + + final int[] trailerFieldOrder = {Signature.FIELD, SignatureLength.FIELD, CheckSum.FIELD}; + + public MyMessage() { + super(); + trailer = new Trailer(trailerFieldOrder); + } + } + + final MyMessage myMessage = new MyMessage(); + + myMessage.getTrailer().setField(new Signature("FOO")); + myMessage.getTrailer().setField(new SignatureLength(3)); + assertTrue(myMessage.toString().contains("89=FOO\00193=3\001")); + } + + @Test + public void testFix5HeaderFields() { + assertTrue(Message.isHeaderField(ApplVerID.FIELD)); + assertTrue(Message.isHeaderField(CstmApplVerID.FIELD)); + } + + @Test + public void testApplExtIDIsHeaderField() { + assertTrue(Message.isHeaderField(ApplExtID.FIELD)); + } + + @Test + public void testHeaderFieldsMissing() throws Exception { + try { + new Message("1=FIX.4.2"); + } catch (final InvalidMessage e) { + // expected + } + } + + @Test + public void testMessageFromString() { + Message message = null; + + boolean badMessage = false; + try { + message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=036\001"); + } catch (final InvalidMessage e) { + badMessage = true; + } + assertTrue("Message should be invalid", badMessage); + + try { + message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"); + } catch (final InvalidMessage e) { + fail("Message should be valid (" + e.getMessage() + ")"); + } + assertEquals("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001", message.toString()); + } + + @Test + public void testIsEmpty() { + final Message message = new Message(); + assertTrue("Message should be empty on construction", message.isEmpty()); + message.getHeader().setField(new BeginString("FIX.4.2")); + assertFalse("Header should contain a field", message.isEmpty()); + message.clear(); + assertTrue("Message should be empty after clear", message.isEmpty()); + message.setField(0, new Field(20000, "MSFT")); + assertFalse("Body should contain a field", message.isEmpty()); + message.clear(); + assertTrue("Message should be empty after clear", message.isEmpty()); + message.getTrailer().setField(new CheckSum("10")); + assertFalse("Trailer should contain a field", message.isEmpty()); + message.clear(); + assertTrue("Message should be empty after clear", message.isEmpty()); + } + + @Test + public void testMessageSetGetString() { + final Message message = new Message(); + + try { + message.getString(5); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setString(5, "string5"); + + try { + assertEquals("string5", message.getString(5)); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + + expectedException.expect(FieldException.class); + message.setString(100, null); + } + + @Test + public void testMessageSetGetBoolean() { + final Message message = new Message(); + + try { + message.getBoolean(7); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setBoolean(7, true); + + try { + assertTrue(message.getBoolean(7)); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + } + + @Test + public void testMessageSetGetChar() { + final Message message = new Message(); + + try { + message.getChar(12); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setChar(12, 'a'); + + try { + assertEquals('a', message.getChar(12)); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + } + + @Test + public void testMessageSetGetChars() throws FieldNotFound { + final Message message = new Message(); + + try { + message.getChars(18); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setChars(18, 'a', 'b', '4'); + assertArrayEquals(new char[]{'a', 'b', '4'}, message.getChars(18)); + } + + @Test + public void testMessageSetGetCharsInvalidFormatException() throws FieldNotFound { + expectedException.expect(FieldException.class); + expectedException.expectMessage("invalid char array: [65, 32, 98, 32, 48, 53]"); + + final Message message = new Message(); + message.setString(123, "A b 05"); + message.getChars(123); + } + + @Test + public void testMessageSetGetInt() { + final Message message = new Message(); + + try { + message.getInt(56); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setInt(56, 23); + + try { + assertEquals(23, message.getInt(56)); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + } + + @Test + public void testMessageSetGetDouble() { + final Message message = new Message(); + + try { + message.getDouble(9812); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + message.setDouble(9812, 12.3443); + + try { + assertEquals(12.3443, message.getDouble(9812), 1e-10); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + } + + @Test + public void testMessageSetGetUtcTimeStamp() { + final Message message = new Message(); + + try { + message.getUtcTimeStamp(8); + fail("exception not thrown"); + } catch (final FieldNotFound e) { + } + + final TimeZone timezone = TimeZone.getTimeZone("GMT+0"); + final Calendar calendar = Calendar.getInstance(timezone); + calendar.set(2002, 8, 6, 12, 34, 56); + calendar.set(Calendar.MILLISECOND, 0); + + final LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(calendar.getTimeInMillis()), ZoneOffset.UTC); + message.setUtcTimeStamp(8, time); + + try { + assertEquals(message.getUtcTimeStamp(8), time); + } catch (final FieldNotFound e) { + fail("exception thrown"); + } + } + + @Test + public void testRemoveField() { + final Message message = new Message(); + message.setField(new StringField(12, "value")); + assertTrue(message.isSetField(12)); + message.removeField(12); + assertTrue(!message.isSetField(12)); + } + + @Test + public void testMessageIterator() { + Message message = new Message(); + java.util.Iterator> i = message.iterator(); + assertFalse(i.hasNext()); + try { + assertNull(i.next()); + fail("exception not thrown"); + } catch (final java.util.NoSuchElementException e) { + } + + try { + message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"); + i = message.iterator(); + assertTrue(i.hasNext()); + StringField field = (StringField) i.next(); + assertEquals(108, field.getField()); + assertEquals("30", field.getValue()); + + assertFalse(i.hasNext()); + try { + assertNull(i.next()); + fail("exception not thrown"); + } catch (final java.util.NoSuchElementException e) { + } + + final java.util.Iterator> j = message.getHeader().iterator(); + assertTrue(j.hasNext()); + field = (StringField) j.next(); + assertEquals(8, field.getField()); + assertEquals("FIX.4.2", field.getValue()); + field = (StringField) j.next(); + assertEquals(9, field.getField()); + assertEquals("12", field.getValue()); + field = (StringField) j.next(); + assertEquals(35, field.getField()); + assertEquals("A", field.getValue()); + + assertFalse(j.hasNext()); + try { + assertNull(j.next()); + fail("exception not thrown"); + } catch (final java.util.NoSuchElementException e) { + } + } catch (final InvalidMessage e) { + fail("exception thrown"); + } + } + + @Test + public void testIsAdmin() { + final Message message = new Message(); + + message.getHeader().setString(MsgType.FIELD, MsgType.HEARTBEAT); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.LOGON); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.LOGOUT); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.SEQUENCE_RESET); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.RESEND_REQUEST); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.TEST_REQUEST); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.REJECT); + assertTrue(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.NEW_ORDER_SINGLE); + assertFalse(message.isAdmin()); + + message.getHeader().setString(MsgType.FIELD, MsgType.QUOTE_RESPONSE); + assertFalse(message.isAdmin()); + } + + + /** + * Verify that an empty message can still be "printed" and doesn't result in any exceptions + */ + @Test + public void testEmptyMessageToString() throws Exception { + final Message msg = new quickfix.Message(); + assertNotNull(msg.toString()); + assertTrue("empty message contains no checksum", msg.toString().length() > 0); + } + + @Test + public void testParseEmptyString() throws Exception { + final String data = ""; + + // with validation + try { + new Message(data, DataDictionaryTest.getDictionary()); + } catch (final InvalidMessage im) { + } catch (final Throwable e) { + e.printStackTrace(); + fail("InvalidMessage expected, got " + e.getClass().getName()); + } + + // without validation + try { + new Message(data, DataDictionaryTest.getDictionary(), false); + } catch (final InvalidMessage im) { + } catch (final Throwable e) { + e.printStackTrace(); + fail("InvalidMessage expected, got " + e.getClass().getName()); + } + } + + /** + * Test for data fields with SOH. This test is based on report from a user on + * the QuickFIX mailing list. The problem was the user's configuration but this + * seems like a good unit test to keep in the suite. + */ + @Test + public void testDataFieldParsing() throws Exception { + final String data = "10001=Canonical.1.00\00110002=001058\00125001=01\00110003=SAPI_ADMRESP\00110004=SUBSCRIBE_RESP\001" + + "10009=705\00110012=01\00110005=SPGW\00110006=SAPI\00110007=0\00110010=16:25:11.537\001" + + "10045=SDQADL:01:/SDB/ENT/@/@/STKSDLL:7\00110955=Y\00110963=043\00110961=03\00111285=N\001" + + "11339=823,980\00110919=N\00111111=86795696\00110898=043\00110920=~\00110938=N\00111340=5- 9.99\001" + + "11343=0.20\00111344=~\00111341=~\00111342=0.15\00111345=10- 14.99\00111348=0.25\00111349=~\00111346=~\001" + + "11347=0.15\00111350=15- 19.99\00111353=0.30\00111354=~\00111351=~\00111352=0.20\00111338=23SEP05\001" + + "10981=0\00110485=N\00110761=0\00111220=~\00111224=N\00110808=N\00110921=~\00110960=N\00110957=N\00111329=N\001" + + "11286=0\00111214=USA\00110917=Y\00111288=0\00110906=N\00110737=0.01\00110956=~\00110967=~\00110965=~\00110809=0\001" + + "10762=N\00110763=N\00110712=1\00110905=09:30:00\00110918=YA0101\00110951=Y\00110469=1\00110949=1\00110487=Q\00110950=Y\001" + + "10899=N\00110380=N\00110696=03\00111082=18.41\00110217=12\00110954=N\00110708=E\00110958=N\00111213=US \00111334=N\001" + + "11332=N\00111331=N\00111330=N\00111335=N\00111333=N\00110767=3\00110974=~\00110980=AIRTRAN HOLDINGS \00111289=N\001" + + "10912=4\00110915=0501\00110914=0501\00110975=N\00110913=SLK\00110698=055\00110666=AAI\00110903=S\00111328=N\001" + + "10624=L\00111287=0\00110699=0\00110962=L\00111227=SUB1\00111229=5\00111228=1\00111236=16:24:41.521\00111277=16:25:11.630\001"; + + try { + final DataDictionary dictionary = DataDictionaryTest.getDictionary(); + final Message m = new Message(("8=FIX.4.4\0019=1144\00135=A\001" + + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00195=1092\001" + "96=" + + data + "\00110=5\001"), dictionary); + assertEquals(1144, m.bodyLength()); + final Message m2 = new Message(m.toString(), dictionary); + assertEquals(1144, m2.bodyLength()); + } catch (final InvalidMessage e) { + fail(e.getMessage()); + } + } + + @Test + public void testHeaderFieldInBody() throws Exception { + final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001" + + "98=0\001212=4\001384=2\001372=D\001385=R\001372=8\001385=S\00110=103\001", + DataDictionaryTest.getDictionary()); + + assertFalse(message.hasValidStructure()); + + assertTrue(message.getHeader().isSetField(212)); + + assertEquals(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER, message + .getException().getSessionRejectReason()); + assertEquals(212, message.getException().getField()); + } + + @Test + public void testTrailerFieldInBody() throws Exception { + final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001" + + "98=0\00193=5\001384=2\001372=D\001385=R\001372=8\001385=S\00110=63\001", + DataDictionaryTest.getDictionary()); + + assertFalse(message.hasValidStructure()); + + final SignatureLength signatureLength = new SignatureLength(); + message.getTrailer().getField(signatureLength); + assertEquals(5, signatureLength.getValue()); + } + + // Includes test for QFJ-413. Repeating group check for size = 0 + @Test + public void testMessageGroupCountValidation() throws Exception { + final String data = "8=FIX.4.4\0019=222\00135=D\00149=SenderCompId\00156=TargetCompId\00134=37\001" + + "52=20070223-22:28:33\00111=183339\00122=8\00138=1\00140=2\00144=12\00148=BHP\00154=2\001" + + "55=BHP\00159=1\00160=20060223-22:38:33\001526=3620\00178=0\00179=AllocACC1\00180=1010.1\001" + + "79=AllocACC2\00180=2020.2\001453=2\001448=8\001447=D\001452=4\001448=AAA35354\001447=D\001452=3\00110=079\001"; + final Message message = new Message(); + final DataDictionary dd = DataDictionaryTest.getDictionary(); + message.fromString(data, dd, true); + try { + dd.validate(message); + fail("No exception thrown"); + } catch (final FieldException e) { + final String emsg = e.getMessage(); + assertNotNull("No exception message", emsg); + assertTrue(emsg.startsWith("Incorrect NumInGroup")); + } + } + + /** + * QFJ-760 + */ + @Test + public void testMessageWithMissingChecksumField() throws Exception { + // checksum is "merged" into field 452, i.e. SOH is missing between field 452 and 10 + String badMessage = "8=FIX.4.4\0019=275\00135=D\00134=3\00149=441000-XXXXX-X-XXXX-001\001" + + "52=20131113-10:22:31.567\00156=XXXXX\0011=A1\00111=9fef3663330e209e1bce\00118=H\001" + + "22=4\00138=200\00140=M\00148=XX0005519XXXX\00154=1\00155=[N/A]\00158=MassTest\00159=0\001" + + "60=20131113-10:22:31.567\001100=XXXX\001526=9fef3663330e209e1bce\001453=1\001" + + "448=XXXXXXXX030\001447=D\001452=3610=016\001"; + + Message msg = new Message(); + try { + msg.fromString(badMessage, DataDictionaryTest.getDictionary(), true); + fail(); + } catch (final InvalidMessage e) { + final String emsg = e.getMessage(); + assertNotNull("No exception message", emsg); + assertTrue(emsg.startsWith("Field not found")); + } + } + + @Test + public void testFalseMessageStructureException() { + try { + final DataDictionary dd = DataDictionaryTest.getDictionary(); + // duplicated tag 98 + // QFJ-65 + new Message("8=FIX.4.4\0019=22\00135=A\00198=0\00198=0\001108=30\00110=223\001", dd, + true); + // For now, this will not cause an exception if the length and checksum are correct + } catch (final Exception e) { + final String text = e.getMessage(); + assertTrue("Wrong exception message: " + text, !text.contains("Actual body length")); + } + } + + @Test + public void testComponentInGroup() { + try { + final DataDictionary dd = DataDictionaryTest.getDictionary(); + // duplicated tag 98 + // QFJ-65 + // 8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001 + // 57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001 + // 570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001 + // 917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001 + // 552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001 + // 448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001 + // 452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001 + // 452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001 + // 624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001 + // 9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001 + // 524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001 + // 602=BRN FMG0010! 63=8 608-FXXXXX 624=1 637=80.09 687=1.0 654=41296073 9019=1 9023=1 9020=20100201 9021=20100228 539=4 524=805\001 + // 525=D\001538=4\001524=11122556 525=D\001538=51 524=Newedge 525=D 538=60 524=U 525=D 538=54 10=112 + new Message( + "8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001" + + "57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001" + + "570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001" + + "917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001" + + "552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001" + + "448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001" + + "452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001" + + "452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001" + + "624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001" + + "9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001" + + "524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001602=BRN FMG0010!\001" + + "63=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\0019019=1\0019023=1\001" + + "9020=20100201\001021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001" + + "525=D\001538=51\001524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001" + + "602=BRN FMG0010!\00163=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\001" + + "9019=1\0019023=1\0019020=20100201\001021=20100228\001", + dd, true); + // For now, this will not cause an exception if the length and checksum are correct + } catch (final Exception e) { + final String text = e.getMessage(); + assertTrue("Wrong exception message: " + text, !text.contains("Actual body length")); + } + } + + @Test + public void testFalseMessageStructureException2() { + try { + final DataDictionary dd = DataDictionaryTest.getDictionary(); + // duplicated raw data length + // QFJ-121 + new Message("8=FIX.4.4\0019=22\00135=A\00196=X\001108=30\00110=223\001", dd, true); + } catch (final Exception e) { + final String text = e.getMessage(); + assertTrue("Wrong exception message: " + text, + text != null && !text.contains("Actual body length")); + } + } + + // QFJ-770/QFJ-792 + @Test + public void testRepeatingGroupCountWithUnknownFields() throws Exception { + String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|" + + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|" + + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|" + + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|" + + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|"; + + DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary()); + Message message = new Message(); + message.fromString(test.replaceAll("\\|", "\001"), dictionary, true); + Group group = message.getGroup(1, 711); + String underlyingSymbol = group.getString(311); + assertEquals("780508", underlyingSymbol); + } + + @Test + // QFJ-940 + public void testRawString() throws Exception { + + String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|" + + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|" + + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|" + + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|" + + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|"; + + DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary()); + Message message = new Message(); + message.fromString(test.replaceAll("\\|", "\001"), dictionary, true); + assertEquals(test, message.toRawString().replaceAll("\001", "\\|")); + } + + // QFJ-722 + @Test + public void testIfMessageHeaderIsCreatedWithEveryConstructor() throws Exception { + final String rawMessage = "8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"; + final DataDictionary dataDictionary = new DataDictionary(DataDictionaryTest.getDictionary()); + + final Message emptyConstructor = new Message(); + assertNotNull(emptyConstructor.getHeader()); + + final Message secondConstructor = new Message(new int[]{}); + assertNotNull(secondConstructor.getHeader()); + + final Message thirdConstructor = new Message(rawMessage); + assertNotNull(thirdConstructor.getHeader()); + + final Message fourthConstructor = new Message(rawMessage, false); + assertNotNull(fourthConstructor.getHeader()); + + final Message fifthConstructor = new Message(rawMessage, dataDictionary); + assertNotNull(fifthConstructor.getHeader()); + + final Message sixthConstructor = new Message(rawMessage, dataDictionary, false); + assertNotNull(sixthConstructor.getHeader()); + + final Message seventhConstructor = new Message(rawMessage, dataDictionary, dataDictionary, false); + assertNotNull(seventhConstructor.getHeader()); + } + + // QFJ-66 Should not throw exception when parsing data field in header + @Test + public void testHeaderDataField() throws Exception { + final Message m = new Message("8=FIX.4.2\0019=53\00135=A\00190=4\00191=ABCD\001" + + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00110=241\001", + DataDictionaryTest.getDictionary()); + assertEquals("ABCD", m.getHeader().getString(SecureData.FIELD)); + } +} diff --git a/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java b/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java new file mode 100644 index 0000000000..fdde176b1a --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import quickfix.field.BeginString; +import quickfix.field.MsgType; +import quickfix.field.SenderCompID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import org.junit.Test; + +/** + * NOTE: There are two MessageUtilsTests. + * One in quickfixj-base, one in quickfixj-core, which each test + * some functionality. This test excludes some test cases that cannot + * be tested in this module due to classes that are generated in a + * later step, e.g. MessageFactories. + */ +public class MessageUtilsTest { + + @Test + public void testGetStringField() throws Exception { + String messageString = "8=FIX.4.2\0019=12\00135=X\001108=30\00110=049\001"; + assertEquals("wrong value", "FIX.4.2", MessageUtils.getStringField(messageString, + BeginString.FIELD)); + assertEquals("wrong value", "X", MessageUtils.getStringField(messageString, MsgType.FIELD)); + assertNull(messageString, MessageUtils.getStringField(messageString, SenderCompID.FIELD)); + } + + @Test + public void testSessionIdFromRawMessage() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; + SessionID sessionID = MessageUtils.getSessionID(messageString); + assertEquals(sessionID.getBeginString(), "FIX.4.0"); + assertEquals("TW", sessionID.getSenderCompID()); + assertEquals("ISLD", sessionID.getTargetCompID()); + } + + @Test + public void testReverseSessionIdFromRawMessage() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\00150=TWS\001" + + "142=TWL\00152=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; + SessionID sessionID = MessageUtils.getReverseSessionID(messageString); + assertEquals(sessionID.getBeginString(), "FIX.4.0"); + assertEquals("ISLD", sessionID.getSenderCompID()); + assertEquals("TW", sessionID.getTargetCompID()); + assertEquals("TWS", sessionID.getTargetSubID()); + assertEquals("TWL", sessionID.getTargetLocationID()); + } + + @Test + public void testMessageType() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; + assertEquals("A", MessageUtils.getMessageType(messageString)); + } + + @Test + public void testMessageTypeError() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; + try { + MessageUtils.getMessageType(messageString); + fail("expected exception"); + } catch (InvalidMessage e) { + // expected + } + } + + @Test + public void testMessageTypeError2() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00135=1"; + try { + MessageUtils.getMessageType(messageString); + fail("expected exception"); + } catch (InvalidMessage e) { + // expected + } + } + + @Test + public void testGetNonexistentStringField() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; + assertNull(MessageUtils.getStringField(messageString, 35)); + } + + @Test + public void testGetStringFieldWithBadValue() throws Exception { + String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223"; + assertNull(MessageUtils.getStringField(messageString, 10)); + } + +} diff --git a/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java b/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java new file mode 100644 index 0000000000..3c9dea46c4 --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Calendar; + +public class MockSystemTimeSource implements SystemTimeSource { + private long[] systemTimes = { System.currentTimeMillis() }; + private int offset; + + public MockSystemTimeSource() { + // empty + } + + public MockSystemTimeSource(long time) { + setSystemTimes(time); + } + + public void setSystemTimes(long[] times) { + systemTimes = times; + } + + void setSystemTimes(long time) { + systemTimes = new long[] { time }; + } + + public void setTime(Calendar c) { + setSystemTimes(c.getTimeInMillis()); + } + + @Override + public long getTime() { + if (systemTimes.length - offset > 1) { + offset++; + } + return systemTimes[offset]; + } + + public void increment(long delta) { + if (systemTimes.length - offset == 1) { + systemTimes[offset] += delta; + } + } + + @Override + public LocalDateTime getNow() { + // TODO maybe we need nano-precision later on + return LocalDateTime.ofInstant(Instant.ofEpochMilli(getTime()), ZoneOffset.UTC); + } + +} diff --git a/quickfixj-core/src/test/java/quickfix/SessionIDTest.java b/quickfixj-base/src/test/java/quickfix/SessionIDTest.java similarity index 100% rename from quickfixj-core/src/test/java/quickfix/SessionIDTest.java rename to quickfixj-base/src/test/java/quickfix/SessionIDTest.java diff --git a/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java b/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java new file mode 100644 index 0000000000..66ac2a6f2e --- /dev/null +++ b/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java @@ -0,0 +1,38 @@ +package quickfix; + +import org.junit.Test; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +import static org.junit.Assert.*; + +public class UTCDateOnlyFieldTest { + + public static final int MD_ENTRY_DATE_FIELD = 272; + + @Test + public void testUtcDateOnlyFieldFromLocalDate() { + LocalDate localDate = LocalDate.of(2023, 3, 19); + UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD,localDate); + assertTrue(utcDateOnlyField.valueEquals(localDate)); + assertEquals(localDate,utcDateOnlyField.getValue()); + assertEquals(MD_ENTRY_DATE_FIELD, utcDateOnlyField.getField()); + } + + @Test + public void testUtcDateOnlyFieldFromNow() { + UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD); + assertNotNull(utcDateOnlyField.getValue()); + assertEquals(MD_ENTRY_DATE_FIELD, utcDateOnlyField.getField()); + } + + @Test + public void testAssignment() { + LocalDate localDate = LocalDate.of(2023, 12, 31); + UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD); + utcDateOnlyField.setValue(localDate); + assertTrue(utcDateOnlyField.valueEquals(localDate)); + assertEquals(localDate,utcDateOnlyField.getValue()); + } +} diff --git a/quickfixj-core/src/test/resources/FIXT11_Custom_Test.xml b/quickfixj-base/src/test/resources/FIXT11_Custom_Test.xml similarity index 100% rename from quickfixj-core/src/test/resources/FIXT11_Custom_Test.xml rename to quickfixj-base/src/test/resources/FIXT11_Custom_Test.xml diff --git a/quickfixj-base/src/test/resources/FIX_External_DTD.xml b/quickfixj-base/src/test/resources/FIX_External_DTD.xml new file mode 100644 index 0000000000..b1953366fd --- /dev/null +++ b/quickfixj-base/src/test/resources/FIX_External_DTD.xml @@ -0,0 +1,27 @@ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + +
diff --git a/quickfixj-base/src/test/resources/configWithSessionVariables.ini b/quickfixj-base/src/test/resources/configWithSessionVariables.ini new file mode 100644 index 0000000000..e06f510dbc --- /dev/null +++ b/quickfixj-base/src/test/resources/configWithSessionVariables.ini @@ -0,0 +1,20 @@ +#comment +[DEFAULT] +Empty= +ConnectionType=acceptor +SocketAcceptPort=5001 +FileStorePath=store +StartTime=00:00:00 +EndTime=00:00:00 +TestLong=1234 +TestLong2=abcd +TestDouble=12.34 +TestDouble2=abcd +TestBoolTrue=Y +TestBoolFalse=N +SenderCompID=TW + +[SESSION] +BeginString=FIX.4.2 +TargetCompID=CLIENT3_${CLIENT_PLACEHOLDER1}_${CLIENT_PLACEHOLDER2} +DataDictionary=../spec/FIX42.xml diff --git a/quickfixj-core/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def b/quickfixj-base/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def similarity index 100% rename from quickfixj-core/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def rename to quickfixj-base/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def diff --git a/quickfixj-class-pruner-maven-plugin/pom.xml b/quickfixj-class-pruner-maven-plugin/pom.xml new file mode 100644 index 0000000000..a9567f1811 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/pom.xml @@ -0,0 +1,96 @@ + + + + 4.0.0 + + + org.quickfixj + quickfixj-parent + 3.0.0-SNAPSHOT + + + quickfixj-class-pruner-maven-plugin + maven-plugin + + QuickFIX/J Class Pruner Maven Plugin + + + ${maven.version} + + + + UTF-8 + + + + + + org.apache.maven + maven-plugin-api + provided + + + org.apache.maven + maven-core + provided + + + org.apache.maven + maven-artifact + provided + + + org.apache.maven + maven-compat + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + + + org.apache.maven.shared + file-management + + + org.junit.vintage + junit-vintage-engine + test + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + test + + + + + + + maven-surefire-plugin + + + org.apache.maven.plugins + maven-plugin-plugin + + true + + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + + diff --git a/quickfixj-class-pruner-maven-plugin/readme.md b/quickfixj-class-pruner-maven-plugin/readme.md new file mode 100644 index 0000000000..1f865f9ce6 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/readme.md @@ -0,0 +1,59 @@ +# Class Pruner Maven Plugin + +This plugin has a very specialised purpose. + +The purpose is to minimise the number of Java classes and generated sources for packaging artefacts of specific FIX versions. This is needed because the introduction of FIX Latest standard results in a very large number of Fields that in turn requires excessive memory and build time for the creation of Javadoc artefacts. + +This is a result of the design of QuickFIX/J in that the Fields package is shared in common for multiple versions of the FIX Protocol. Accordingly the Component and Messages packages are compiled against the common package of Fields. The Messages, Components and Fields can be assembled together as in ```quickfixj-messages-all``` or separately as in ```quickfixj-messages-fix40``` to ```quickfixj-messages-fixlatest``` and ```quickfixj-messages-fixt11```. In each of these assemblies the requisite Fields are included. These packages can be used together at runtime without conflict if the Field classes are identical. + +Broadly speaking the later versions of FIX are super-sets of the prior versions. There are cases of Fields being deprecated but in QuickFIX/J distributions these Fields are included in subsequent distributions. + +As mentioned the FIX Latest distribution is very large and it is built in its entirety by QuickFIX/J to verify compatibility. The complete set of Fields is not required for older versions of the standard which are still in common use. + +This plugin can be used before packaging a version of the standard to prune the Java sources and classes that are not included in the FIX specification for that version. This allows Javadoc to be created and produces a more compact and concise distribution. + +The plugin parses the specified QuickFIX dictionaries to identify the required Fields and deletes redundant Java sources and classes from the specified generated sources and classes directories. + +It can be used as follows. + +``` +... + + + + org.quickfixj + class-pruner-maven-plugin + ${project.version} + + + prune + + prune + + + ${project.basedir}/../quickfixj-messages-all/target/classes/quickfix/field + ${project.basedir}/../quickfixj-messages-all/target/generated-sources/quickfix/field + + ${project.basedir}/../quickfixj-messages-all/target/classes/ + + **/*.xml + + + **/FIXLatest.xml + OrchestraFIXLatest.xml + FIX50SP2.xml + FIX50SP1.xml + FIX44.xml + + false + + + + + +... +``` +Note: +* only *later* versions of the FIX specification than the one being packaged are excluded from parsing +* FIX dictionaries not used in the code generation are excluded (redundant Dictionary files are present in the project) +* the use of this plugin depends on correct ordering of the modules with the most recent versions of the FIX protocol being packaged before packaging the older versions of the protocol. diff --git a/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java b/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java new file mode 100644 index 0000000000..874d960ff9 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java @@ -0,0 +1,205 @@ +package org.quickfixj; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.shared.model.fileset.FileSet; +import org.apache.maven.shared.model.fileset.util.FileSetManager; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Goal that prunes specified classes + * This is intended to be used before javadoc and packaging to reduce memory requirements and build time + */ +@Mojo( name = "prune", defaultPhase = LifecyclePhase.INSTALL ) +public class ClassPrunerMojo extends AbstractMojo { + + /** + * fileSet defining the QFJ Dictionary Locations. + */ + @Parameter ( property = "fileset", required = true ) + private FileSet fileset; + + /** + * Location of classes to delete + */ + @Parameter( defaultValue = "classes", property = "classesDirectory", required = true ) + private File classesDirectory; + + /** + * Location of the generated source to delete + */ + @Parameter( defaultValue = "generated-sources", property = "generatedSourcesDirectory", required = true ) + private File generatedSourcesDirectory; + + + public void execute() + throws MojoExecutionException + { + this.getLog().info("executing mojo."); + + if ( !classesDirectory.exists() || !classesDirectory.isDirectory() ) + { + String errorMsg = new StringBuilder(classesDirectory.getAbsolutePath()).append(" must exist and be a directory.").toString(); + this.getLog().error(errorMsg.toString()); + throw new MojoExecutionException( errorMsg.toString() ); + } else { + this.getLog().info(new StringBuilder("Classes Directory : ").append(classesDirectory.getAbsolutePath()).toString()); + } + + if ( !generatedSourcesDirectory.exists() || !generatedSourcesDirectory.isDirectory() ) + { + String errorMsg = new StringBuilder(generatedSourcesDirectory.getAbsolutePath()).append(" must exist and be a directory.").toString(); + this.getLog().error(errorMsg.toString()); + throw new MojoExecutionException( errorMsg.toString() ); + } else { + this.getLog().info(new StringBuilder("Generated Sources Directory : ").append(generatedSourcesDirectory.getAbsolutePath()).toString()); + } + + if (null == fileset) { + String errorMsg = "filset must not be null."; + this.getLog().error(errorMsg); + throw new MojoExecutionException( errorMsg ); + } + + Set fieldNames = new HashSet(); + + collectFieldNames(fieldNames); + + try { + pruneGeneratedSources(fieldNames); + pruneClasses(fieldNames); + } catch (IOException e) { + String errorMsg = "Exception pruning directories."; + this.getLog().error(errorMsg, e); + throw new MojoExecutionException( errorMsg, e); + } + } + + private void pruneClasses(Set fieldNames) throws IOException { + prune(fieldNames, this.classesDirectory, ".class", "Java class"); + } + + private void pruneGeneratedSources(Set fieldNames) throws IOException { + prune(fieldNames, this.generatedSourcesDirectory, ".java", "Java source"); + } + + private void prune(Set fieldNames, File targetDirectory, String fileSuffix, String descriptor) throws IOException { + Set files = listFiles(targetDirectory); + Set namesOfFilesToKeep = fieldNames.stream().map(file -> new StringBuilder(file).append(fileSuffix).toString()).collect(Collectors.toSet()); + files.removeAll(namesOfFilesToKeep); + List fileList = new ArrayList(files); + Collections.sort(fileList); + this.getLog().info(descriptor + "s to delete : " + fileList.size()); + for (String fileName : fileList) { + this.getLog().info("Deleting " + descriptor + " : " + fileName); + File file = new File( targetDirectory, fileName ); + Files.delete(file.toPath()); + } + } + + private static Set listFiles(File directory) throws IOException { + try (Stream stream = Files.list(directory.toPath())) { + return stream + .filter(file -> !Files.isDirectory(file)) + .map(Path::getFileName) + .map(Path::toString) + .filter(fileName -> !fileName.matches(".*\\.xml")) + .collect(Collectors.toSet()); + } + } + + private void collectFieldNames(Set fieldNames) throws MojoExecutionException { + Set fileNamesToParse = collectFileNameToParse(); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + try { + db = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + String msg = "ParserConfigurationException creating Document Builder"; + this.getLog().error(msg, e); + throw new MojoExecutionException(msg, e); + } + + for (String fileName: fileNamesToParse) { + Document document; + try { + document = db.parse(new File(fileName)); + // optional, but recommended + // http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work + document.getDocumentElement().normalize(); + addNames(document.getDocumentElement(), "fields/field", fieldNames); + } catch (SAXException | IOException e) { + String msg = "Exception parsing file " + fileName; + this.getLog().error(msg, e); + throw new MojoExecutionException(msg, e); + } + } + + List fieldList = new ArrayList(fieldNames); + Collections.sort(fieldList); + for (String fieldName : fieldList) { + this.getLog().info("Found field : " + fieldName); + } + this.getLog().info("Found field total : " + fieldList.size()); + } + + private Set collectFileNameToParse() { + FileSetManager fileSetManager = new FileSetManager(); + this.getLog().info("fileset " + fileset.toString()); + + String currentDir = System.getProperty("user.dir"); + this.getLog().info("Current working directory : " + currentDir); + + Set includedFiles = new HashSet(Arrays.asList(fileSetManager.getIncludedFiles( fileset ))); + Set fileNamesToParse = new HashSet(); + + String baseDirectory = fileset.getDirectory(); + for (String includedFile: includedFiles) { + this.getLog().info("will parse file : " + includedFile); + String fileName = new StringBuilder(baseDirectory).append(File.separator).append(includedFile).toString(); + fileNamesToParse.add(fileName); + } + return fileNamesToParse; + } + + private static void addNames(Element element, String path, Set fieldNames) { + int separatorOffset = path.indexOf("/"); + if (separatorOffset == -1) { + NodeList fieldNodeList = element.getElementsByTagName(path); + for (int i = 0; i < fieldNodeList.getLength(); i++) { + fieldNames.add(((Element) fieldNodeList.item(i)).getAttribute("name")); + } + } else { + String tag = path.substring(0, separatorOffset); + NodeList subnodes = element.getElementsByTagName(tag); + for (int i = 0; i < subnodes.getLength(); i++) { + addNames((Element) subnodes.item(i), path.substring(separatorOffset + 1), fieldNames); + } + } + } +} diff --git a/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java b/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java new file mode 100644 index 0000000000..cf94698f3c --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java @@ -0,0 +1,408 @@ +package org.quickfixj; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.testing.MojoRule; +import org.apache.maven.shared.model.fileset.FileSet; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.io.Files; + +public class ClassPrunerMojoTest +{ + private List classesList = new ArrayList(Arrays.asList( + "Account" + ,"AccruedInterestAmt" + ,"AccruedInterestRate" + ,"AdvId" + ,"AdvRefID" + ,"AdvSide" + ,"AdvTransType" + ,"AllocAccount" + ,"AllocAvgPx" + ,"AllocHandlInst" + ,"AllocID" + ,"AllocLinkID" + ,"AllocLinkType" + ,"AllocNetMoney" + ,"AllocRejCode" + ,"AllocShares" + ,"AllocStatus" + ,"AllocText" + ,"AllocTransType" + ,"AvgPrxPrecision" + ,"AvgPx" + ,"BeginSeqNo" + ,"BeginString" + ,"BidForwardPoints" + ,"BidPx" + ,"BidSize" + ,"BidSpotRate" + ,"BodyLength" + ,"BrokerOfCredit" + ,"CashOrderQty" + ,"CashSettlAgentAcctName" + ,"CashSettlAgentAcctNum" + ,"CashSettlAgentCode" + ,"CashSettlAgentContactName" + ,"CashSettlAgentContactPhone" + ,"CashSettlAgentName" + ,"CheckSum" + ,"ClientID" + ,"ClOrdID" + ,"Commission" + ,"CommType" + ,"CoveredOrUncovered" + ,"CumQty" + ,"Currency" + ,"CustomerOrFirm" + ,"CxlQty" + ,"CxlRejReason" + ,"CxlType" + ,"DeliverToCompID" + ,"DeliverToLocationID" + ,"DeliverToSubID" + ,"DKReason" + ,"DlvyInst" + ,"EffectiveTime" + ,"EmailThreadID" + ,"EmailType" + ,"EncryptMethod" + ,"EndSeqNo" + ,"ExDestination" + ,"ExecBroker" + ,"ExecID" + ,"ExecInst" + ,"ExecRefID" + ,"ExecTransType" + ,"ExecType" + ,"ExpireTime" + ,"ForexReq" + ,"FutSettDate" + ,"FutSettDate2" + ,"GapFillFlag" + ,"HandlInst" + ,"Headline" + ,"HeartBtInt" + ,"IDSource" + ,"IOIID" + ,"IOINaturalFlag" + ,"IOIOthSvc" + ,"IOIQltyInd" + ,"IOIQualifier" + ,"IOIRefID" + ,"IOIShares" + ,"IOITransType" + ,"Issuer" + ,"LastCapacity" + ,"LastForwardPoints" + ,"LastMkt" + ,"LastPx" + ,"LastShares" + ,"LastSpotRate" + ,"LeavesQty" + ,"LinesOfText" + ,"ListExecInst" + ,"ListID" + ,"ListNoOrds" + ,"ListSeqNo" + ,"LocateReqd" + ,"MaturityDay" + ,"MaturityMonthYear" + ,"MaxFloor" + ,"MaxShow" + ,"MinQty" + ,"MiscFeeAmt" + ,"MiscFeeCurr" + ,"MiscFeeType" + ,"MsgSeqNum" + ,"MsgType" + ,"NetMoney" + ,"NewSeqNo" + ,"NoAllocs" + ,"NoDlvyInst" + ,"NoExecs" + ,"NoIOIQualifiers" + ,"NoMiscFees" + ,"NoOrders" + ,"NoRelatedSym" + ,"NoRpts" + ,"NotifyBrokerOfCredit" + ,"NumDaysInterest" + ,"OfferForwardPoints" + ,"OfferPx" + ,"OfferSize" + ,"OfferSpotRate" + ,"OnBehalfOfCompID" + ,"OnBehalfOfLocationID" + ,"OnBehalfOfSubID" + ,"OpenClose" + ,"OptAttribute" + ,"OrderID" + ,"OrderQty" + ,"OrderQty2" + ,"OrdRejReason" + ,"OrdStatus" + ,"OrdType" + ,"OrigClOrdID" + ,"OrigSendingTime" + ,"OrigTime" + ,"PegDifference" + ,"PossDupFlag" + ,"PossResend" + ,"PrevClosePx" + ,"Price" + ,"ProcessCode" + ,"PutOrCall" + ,"QuoteID" + ,"QuoteReqID" + ,"RawData" + ,"RawDataLength" + ,"RefAllocID" + ,"RefSeqNum" + ,"RelatdSym" + ,"ReportToExch" + ,"ResetSeqNumFlag" + ,"RptSeq" + ,"Rule80A" + ,"SecondaryOrderID" + ,"SecureData" + ,"SecureDataLen" + ,"SecurityDesc" + ,"SecurityExchange" + ,"SecurityID" + ,"SecuritySettlAgentAcctName" + ,"SecuritySettlAgentAcctNum" + ,"SecuritySettlAgentCode" + ,"SecuritySettlAgentContactName" + ,"SecuritySettlAgentContactPhone" + ,"SecuritySettlAgentName" + ,"SecurityType" + ,"SenderCompID" + ,"SenderLocationID" + ,"SenderSubID" + ,"SendingTime" + ,"SettlBrkrCode" + ,"SettlCurrAmt" + ,"SettlCurrency" + ,"SettlCurrFxRate" + ,"SettlCurrFxRateCalc" + ,"SettlDeliveryType" + ,"SettlDepositoryCode" + ,"SettlInstCode" + ,"SettlInstID" + ,"SettlInstMode" + ,"SettlInstSource" + ,"SettlInstTransType" + ,"SettlLocation" + ,"SettlmntTyp" + ,"Shares" + ,"Side" + ,"Signature" + ,"SignatureLength" + ,"StandInstDbID" + ,"StandInstDbName" + ,"StandInstDbType" + ,"StopPx" + ,"StrikePrice" + ,"Subject" + ,"Symbol" + ,"SymbolSfx" + ,"TargetCompID" + ,"TargetLocationID" + ,"TargetSubID" + ,"TestReqID" + ,"Text" + ,"TimeInForce" + ,"TradeDate" + ,"TransactTime" + ,"Urgency" + ,"URLLink" + ,"ValidUntilTime" + ,"ValuationBusinessCenter" + ,"ValuationDate" + ,"ValuationMethod" + ,"ValuationReferenceModel" + ,"ValuationSource" + ,"ValuationTime" + ,"ValueCheckAction" + ,"ValueCheckType" + ,"ValueOfFutures" + ,"VegaMultiplier" + ,"VenueType" + ,"VerificationMethod" + ,"VersusPurchaseDate" + ,"VersusPurchasePrice" + ,"Volatility" + ,"VoluntaryRegulatoryReport" + ,"WarningText" + ,"WaveNo" + ,"WireReference" + ,"WorkingIndicator" + ,"WtAverageLiquidity" + ,"Yield" + ,"YieldCalcDate" + ,"YieldRedemptionDate" + ,"YieldRedemptionPrice" + ,"YieldRedemptionPriceType" + ,"YieldType")); + + List someFieldNamesThatShouldBePruned = + new ArrayList<>(Arrays.asList("ValuationBusinessCenter", + "ValuationDate", + "ValuationMethod", + "ValuationReferenceModel", + "ValuationSource", + "ValuationTime", + "ValueCheckAction", + "ValueCheckType", + "ValueOfFutures", + "VegaMultiplier", + "VenueType", + "VerificationMethod", + "VersusPurchaseDate", + "VersusPurchasePrice", + "Volatility", + "VoluntaryRegulatoryReport", + "WarningText", + "WireReference", + "WorkingIndicator", + "WtAverageLiquidity", + "Yield", + "YieldCalcDate", + "YieldRedemptionDate", + "YieldRedemptionPrice", + "YieldRedemptionPriceType", + "YieldType")); + private File classesDirectory; + private File generatedSourcesDirectory; + + @Rule + public MojoRule rule = new MojoRule() + { + @Override + protected void before() throws Throwable + { + } + + @Override + protected void after() + { + } + }; + + @After + public void clearDown() throws Exception { + ClassPrunerMojoTest.clearDownDirectory(generatedSourcesDirectory); + ClassPrunerMojoTest.clearDownDirectory(classesDirectory); + } + + private static void clearDownDirectory(File directory) throws Exception { + if (null != directory && directory.exists()) { + FileUtils.cleanDirectory(directory); + } + directory.delete(); + } + + /** + * This test represents pruning Field classes that do not exist if FIX 5.0sp2 and earlier. + * The inputs are QFJ dictionaries for FIX 5.0sp2 (and some earlier versions) and + * directories that contain files with the names of Fields found in FIX Latest. + * The files for Fields found in FIX 5.0sp2 and earlier are retained, the excess files are pruned. + * @throws Exception if any + */ + @Test + public void testPrune() + throws Exception + { + File pom = new File("target/test-classes/project-to-test/"); + assertNotNull( pom ); + assertTrue( pom.exists() ); + + ClassPrunerMojo myMojo = ( ClassPrunerMojo ) rule.lookupConfiguredMojo( pom, "prune" ); + assertNotNull( myMojo ); + + classesDirectory = ( File ) rule.getVariableValueFromObject( myMojo, "classesDirectory" ); + assertNotNull( classesDirectory ); + if (!classesDirectory.exists()) { + classesDirectory.mkdirs(); + } + //TODO create directory, populate with test files + myMojo.getLog().info("classesDirectory : " + classesDirectory.getAbsolutePath() ); + createFilesForTest(classesList, classesDirectory, ".class"); + createFilesForTest(someFieldNamesThatShouldBePruned, classesDirectory, ".class"); + + generatedSourcesDirectory = ( File ) rule.getVariableValueFromObject( myMojo, "generatedSourcesDirectory" ); + assertNotNull( generatedSourcesDirectory ); + if (!generatedSourcesDirectory.exists()) { + generatedSourcesDirectory.mkdirs(); + } + myMojo.getLog().info("generatedSourcesDirectory : " + generatedSourcesDirectory.getAbsolutePath() ); + createFilesForTest(classesList, generatedSourcesDirectory, ".java"); + createFilesForTest(someFieldNamesThatShouldBePruned, generatedSourcesDirectory, ".java"); + + myMojo.execute(); + + Object variableValueFromObject = rule.getVariableValueFromObject( myMojo, "fileset" ); + FileSet dictionaryFileSet = ( FileSet ) variableValueFromObject; + assertNotNull( dictionaryFileSet ); + + // The following are based on files created above + List someFieldNamesThatShouldStillExist = new ArrayList<>(Arrays.asList("WaveNo", "ValidUntilTime", "Account")); + + for (String fieldName : someFieldNamesThatShouldStillExist) { + File source = new File( generatedSourcesDirectory, fieldName.concat(".java") ); + assertTrue( source.exists() ); + File clazz = new File( classesDirectory, fieldName.concat(".class") ); + assertTrue( clazz.exists() ); + } + + for (String fieldName : someFieldNamesThatShouldBePruned) { + File source = new File( generatedSourcesDirectory, fieldName.concat(".java") ); + assertFalse( source.exists() ); + File clazz = new File( classesDirectory, fieldName.concat(".class") ); + assertFalse( clazz.exists() ); + } + + int numberOfFieldsFromTheCombinedDictionaries = 209; + assertEquals(classesDirectory.list().length, numberOfFieldsFromTheCombinedDictionaries); + assertEquals(generatedSourcesDirectory.list().length, numberOfFieldsFromTheCombinedDictionaries); + } + + private static void createFilesForTest(List classesList, File classesDirectory, String extension) throws IOException { + classesList.stream().forEach(throwingConsumerWrapper(f -> {File file = new File(classesDirectory, f.concat(extension)); Files.touch(file);})); + } + + @FunctionalInterface + public interface ThrowingConsumer { + void accept(T t) throws E; + } + + //https://www.baeldung.com/java-lambda-exceptions + static Consumer throwingConsumerWrapper( + ThrowingConsumer throwingConsumer) { + return i -> { + try { + throwingConsumer.accept(i); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + +} + diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml new file mode 100644 index 0000000000..96513ba048 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.quickfixj + project-to-test + 1.0-SNAPSHOT + jar + Test MyMojo + + + + + org.quickfixj + quickfixj-class-pruner-maven-plugin + + not-really-classes + not-really-generated-sources + + target/test-classes/qfj-dictionaries + + **/*.xml + + + **/FIX50SP2.modified.xml + **/FIXT11.xml + + false + + + + + + diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml new file mode 100644 index 0000000000..514a89d65f --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml @@ -0,0 +1,857 @@ + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml new file mode 100644 index 0000000000..458497c537 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml @@ -0,0 +1,1268 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml new file mode 100644 index 0000000000..069cf4837b --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml @@ -0,0 +1,9942 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml new file mode 100644 index 0000000000..d763b29641 --- /dev/null +++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml @@ -0,0 +1,383 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/quickfixj-codegenerator/pom.xml b/quickfixj-codegenerator/pom.xml index 6d8598ee35..92b170442b 100644 --- a/quickfixj-codegenerator/pom.xml +++ b/quickfixj-codegenerator/pom.xml @@ -25,17 +25,32 @@ org.apache.maven maven-plugin-api - 3.8.7 + provided org.apache.maven maven-project - 2.2.1 + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided net.sf.saxon Saxon-HE - 11.4 + 12.3 + + + org.junit.vintage + junit-vintage-engine + test + + + commons-io + commons-io + test @@ -63,7 +78,7 @@ maven-plugin-plugin - 3.7.0 + 3.10.2 diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java index e0cb060d2a..ec208887cb 100644 --- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java +++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java @@ -23,6 +23,11 @@ * Signals an error in the code generation software. */ public class CodeGenerationException extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = -7143250551383159031L; + public CodeGenerationException(Throwable cause) { super(cause); } diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java index c94b2fcbff..5faeef257c 100644 --- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java +++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java @@ -23,6 +23,9 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.FileUtils; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.LifecyclePhase; import java.io.File; @@ -30,75 +33,70 @@ * A mojo that uses the quickfix code generator to generate * Java source files from a QuickFIX Dictionary. * - * @goal generate - * @phase generate-sources * @description QuickFIX/J code generation plugin - * @author Claudio Bantaloukas + * @author Claudio Bantaloukas */ +@Mojo( name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES ) public class GenerateMojo extends AbstractMojo { /** * The dictionary file to use for mapping messages to java. - * - * @parameter expression="${basedir}/src/main/quickfixj/dictionary/FIX44.xml" */ + @Parameter(defaultValue="${basedir}/src/main/quickfixj/dictionary/FIX44.xml") private File dictFile; /** * The source directory containing *.xsd files. - * - * @parameter expression="${basedir}/src/resources/quickfixj/codegenerator" */ + @Parameter(defaultValue="${basedir}/src/resources/quickfixj/codegenerator") private File schemaDirectory; /** * The directory to output the generated sources to. - * - * @parameter expression="${project.build.directory}/generated-sources/" */ + @Parameter(defaultValue="${project.build.directory}/generated-sources/") private File outputDirectory; /** * Enable BigDecimal representation. - * - * @parameter default-value="false" */ + @Parameter(defaultValue="false") private boolean decimal; /** * Enable orderedFields. - * - * @parameter default-value="false" */ + @Parameter(defaultValue="false") private boolean orderedFields; /** * The package for the generated source. - * - * @parameter */ + @Parameter(required = true) private String packaging; /** * The base field class to use. - * - * @parameter default-value = "quickfix.field" */ + @Parameter(defaultValue = "quickfix.field") private String fieldPackage = "quickfix.field"; /** * The default UtcTimestampPrecision to be used during field code generation. - * - * @parameter */ + @Parameter(required = false) private String utcTimestampPrecision; + /** + * Defines whether the code generator should overwrite existing files with the same name + */ + @Parameter(defaultValue = "true") + private boolean overwrite = true; + /** * The Maven project to act upon. - * - * @parameter expression="${project}" - * @required */ + @Parameter(defaultValue = "${project}", required = true) private MavenProject project; /** @@ -143,7 +141,7 @@ public void execute() throws MojoExecutionException { task.setOutputBaseDirectory(outputDirectory); task.setFieldPackage(fieldPackage); task.setUtcTimestampPrecision(utcTimestampPrecision); - task.setOverwrite(true); + task.setOverwrite(overwrite); task.setOrderedFields(orderedFields); task.setDecimalGenerated(decimal); generator.generate(task); @@ -326,4 +324,18 @@ public String getUtcTimestampPrecision() { public void setUtcTimestampPrecision(String utcTimestampPrecision) { this.utcTimestampPrecision = utcTimestampPrecision; } + + /** + * @return whether the code generator should overwrite existing files with the same name + */ + public boolean isOverwrite() { + return overwrite; + } + + /** + * @param overwrite sets whether the code generator should overwrite existing files with the same name + */ + public void setOverwrite(boolean overwrite) { + this.overwrite = overwrite; + } } diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java index b4e90f9b00..615e639661 100644 --- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java +++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java @@ -138,7 +138,7 @@ private void generateFieldClasses(Task task) throws ParserConfigurationException Transformer transformer = createTransformer(task, "Fields.xsl"); for (String fieldName : fieldNames) { String outputFile = outputDirectory + fieldName + ".java"; - if (!new File(outputFile).exists()) { + if (!new File(outputFile).exists() || task.isOverwrite()) { // if overwrite is set, then transform and generate the file logDebug("field: " + fieldName); Map parameters = new HashMap<>(); parameters.put("fieldName", fieldName); @@ -305,13 +305,7 @@ private void generateCodeFile(Task task, Document document, Map if (!task.isOverwrite()) { return; } - if (outputFile.lastModified() > task.getSpecificationLastModified()) { - logDebug("Skipping file " + outputFile.getName()); - return; - } } - logDebug("spec has mod " + task.getSpecificationLastModified() + - " output has mod " + outputFile.lastModified()); DOMSource source = new DOMSource(document); FileOutputStream fos = new FileOutputStream(outputFile); diff --git a/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl b/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl index 8cfe01c884..d7eeae25cd 100644 --- a/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl +++ b/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl @@ -75,21 +75,21 @@ import java.time.LocalTime; public class extends Field { - static final long serialVersionUID = ; + static final long serialVersionUID = ; - public static final int FIELD = ; - - public () { - super(); - } + public static final int FIELD = ; + + public () { + super(); + } - public ( data) { - super(, data, true); - } + public ( data) { + super(, data, true); + } public (double data) { - super(, new (data)); - } + super(, new (data)); + } @Override @@ -112,6 +112,8 @@ public class extends LocalTime LocalDate LocalDate + String + String boolean double @@ -140,6 +142,8 @@ public class extends UtcTimeOnly UtcDateOnly UtcDateOnly + String + String Boolean Double @@ -177,32 +181,32 @@ public class extends - public static final String = ""; - - public static final String = ""; - - public static final String = ""; - + public static final String = ""; + + public static final String = ""; + + public static final String = ""; + public static final boolean = ; - - public static final int = ; - - public static final int = ; - - public static final String = ""; - - public static final String = ""; - - public static final char = ''; - + + public static final int = ; + + public static final int = ; + + public static final String = ""; + + public static final String = ""; + + public static final char = ''; + - public static final String OPTION = "OPT"; + public static final String OPTION = "OPT"; - public static final String FUTURE = "FUT"; + public static final String FUTURE = "FUT"; diff --git a/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java b/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java new file mode 100644 index 0000000000..a5358b071f --- /dev/null +++ b/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java @@ -0,0 +1,135 @@ +package org.quickfixj.codegenerator; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Scanner; + +import org.apache.maven.plugin.MojoExecutionException; +import org.junit.Before; +import org.junit.Test; +import org.apache.commons.io.FileUtils; + +public class OverwriteTest { + + private File outputDirectory = new File ("./target/test-output/"); + private File dictDirectory = new File ("./src/test/resources"); + private File schemaDirectory = new File ("./src/main/resources/org/quickfixj/codegenerator"); + private String fieldPackage = "quickfix.field"; + private String utcTimestampPrecision = null; + private boolean orderedFields = true; + private boolean decimal = true; + private MessageCodeGenerator generator; + + @Before + public void setup() throws IOException { + if (outputDirectory.exists()){ + FileUtils.cleanDirectory(outputDirectory); + } else { + outputDirectory.mkdirs(); + } + generator = new MessageCodeGenerator(); + System.out.println("Successfully created an instance of the QuickFIX source generator"); + } + + @Test + public void testFieldOverwrittenWhenOverwriteTrue() { + + boolean overwrite = true; + + MessageCodeGenerator.Task task = new MessageCodeGenerator.Task(); + System.out.println("Initialising code generator task"); + + try { + String packaging = "quickfix.fix41"; + File fix41Dictfile = new File( dictDirectory, "FIX41.xml" ); + generate(generator, task, fix41Dictfile, packaging, overwrite); + + packaging = "quickfix.fix42"; // this does not affect this test + File fix42Dictfile = new File( dictDirectory, "FIX42.xml" ); + generate(generator, task, fix42Dictfile, packaging, overwrite); + } catch (MojoExecutionException e) { + e.printStackTrace(); + fail(); + } + + String expectedFilePath = outputDirectory.getAbsolutePath() + "/quickfix/field/AllocShares.java"; + File file = new File(expectedFilePath); + assertTrue(file.exists()); + + boolean isAllocSharesDecimal = isAllocSharesDecimal(file); + assertTrue(isAllocSharesDecimal); + } + + @Test + public void testFieldNotOverwrittenWhenOverwriteFalse() { + + boolean overwrite = false; + + MessageCodeGenerator.Task task = new MessageCodeGenerator.Task(); + System.out.println("Initialising code generator task"); + + try { + String packaging = "quickfix.fix41"; + File fix41Dictfile = new File( dictDirectory, "FIX41.xml" ); + generate(generator, task, fix41Dictfile, packaging, overwrite); + + packaging = "quickfix.fix42"; // this does not affect this test + File fix42Dictfile = new File( dictDirectory, "FIX42.xml" ); + generate(generator, task, fix42Dictfile, packaging, overwrite); + } catch (MojoExecutionException e) { + e.printStackTrace(); + fail(); + } + + String expectedFilePath = outputDirectory.getAbsolutePath() + "/quickfix/field/AllocShares.java"; + File file = new File(expectedFilePath); + assertTrue(file.exists()); + + boolean isAllocSharesDecimal = isAllocSharesDecimal(file); + assertFalse(isAllocSharesDecimal); + } + + private void generate(MessageCodeGenerator generator, MessageCodeGenerator.Task task, File dictfile, + String packaging, boolean overwrite) throws MojoExecutionException { + if (dictfile != null && dictfile.exists()) { + task.setSpecification(dictfile); + } else { + throw new MojoExecutionException("File could not be found or was NULL!"); + } + + System.out.println("Processing " + dictfile); + + task.setName(dictfile.getName()); + task.setTransformDirectory(schemaDirectory); + task.setMessagePackage(packaging); + task.setOutputBaseDirectory(outputDirectory); + task.setFieldPackage(fieldPackage); + task.setUtcTimestampPrecision(utcTimestampPrecision); + task.setOverwrite(overwrite); + task.setOrderedFields(orderedFields); + task.setDecimalGenerated(decimal); + generator.generate(task); + } + + private boolean isAllocSharesDecimal(File file) { + boolean isAllocSharesDecimal = false; + try (Scanner scanner = new Scanner(file)) { + //now read the file line by line... + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if(line.contains("AllocShares extends DecimalField")) { + isAllocSharesDecimal = true; + break; + } + } + } catch(FileNotFoundException e) { + e.printStackTrace(); + fail(); + } + return isAllocSharesDecimal; + } + +} diff --git a/quickfixj-codegenerator/src/test/resources/FIX41.xml b/quickfixj-codegenerator/src/test/resources/FIX41.xml new file mode 100644 index 0000000000..4a328f079b --- /dev/null +++ b/quickfixj-codegenerator/src/test/resources/FIX41.xml @@ -0,0 +1,36 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
diff --git a/quickfixj-codegenerator/src/test/resources/FIX42.xml b/quickfixj-codegenerator/src/test/resources/FIX42.xml new file mode 100644 index 0000000000..d48a87328f --- /dev/null +++ b/quickfixj-codegenerator/src/test/resources/FIX42.xml @@ -0,0 +1,41 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
diff --git a/quickfixj-core/pom.xml b/quickfixj-core/pom.xml index cd7c61e943..9329bfa0f4 100644 --- a/quickfixj-core/pom.xml +++ b/quickfixj-core/pom.xml @@ -1,490 +1,368 @@ - 4.0.0 - - org.quickfixj - quickfixj-parent - 3.0.0-SNAPSHOT - + 4.0.0 + + org.quickfixj + quickfixj-parent + 3.0.0-SNAPSHOT + - quickfixj-core - bundle + quickfixj-core + bundle - QuickFIX/J Core engine - The core QuickFIX/J engine - http://www.quickfixj.org + QuickFIX/J Core engine + The core QuickFIX/J engine + http://www.quickfixj.org - - **/AcceptanceTestSuite.java - org.quickfixj.Version - + + **/AcceptanceTestSuite.java + org.quickfixj.Version + - - - junit - junit - ${junit.version} - test - - - org.mockito - mockito-core - 4.11.0 - test - - - org.hamcrest - hamcrest - 2.2 - test - - - hsqldb - hsqldb - 1.8.0.10 - test - - - tyrex - tyrex - 1.0.1 - test - - - org.slf4j - slf4j-jdk14 - ${slf4j.version} - test - + + + org.quickfixj + quickfixj-base + ${project.version} + + + org.quickfixj + quickfixj-messages-fixlatest + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix50sp2 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix50sp1 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix50 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix44 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix43 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix42 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix41 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fix40 + ${project.version} + test + + + org.quickfixj + quickfixj-messages-fixt11 + ${project.version} + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.mockito + mockito-core + test + + + org.hamcrest + hamcrest + test + + + hsqldb + hsqldb + 1.8.0.10 + test + + + tyrex + tyrex + 1.0.1 + test + + + org.slf4j + slf4j-jdk14 + ${slf4j.version} + test + + + org.apache.mina + mina-core + + + org.slf4j + slf4j-api + ${slf4j.version} + + + com.cloudhopper.proxool + proxool + 0.9.1 + true + + + + avalon-framework + avalon-framework-api + + + + commons-logging + commons-logging + + + + + com.cloudhopper.proxool + proxool-cglib + 0.9.1 + true + + + + avalon-framework + avalon-framework-api + + + + commons-logging + commons-logging + + + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + runtime + true + + + com.sleepycat + je + 18.3.12 + true + + - - org.apache.mina - mina-core - 2.1.6 - - - org.slf4j - slf4j-api - ${slf4j.version} - + + + - - com.cloudhopper.proxool - proxool - 0.9.1 - true - - - - avalon-framework - avalon-framework-api - - - - commons-logging - commons-logging - - - - - com.cloudhopper.proxool - proxool-cglib - 0.9.1 - true - - - - avalon-framework - avalon-framework-api - - - - commons-logging - commons-logging - - - - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - true - - - com.sleepycat - je - 18.3.12 - true - - + + + src/test/resources + + + src/main/resources + + - - - - ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources - - + + + org.apache.maven.plugins + maven-jar-plugin + + + quickfix/** + org/** + quickfix/field/* + quickfix/field/converter/* + FIX*.xml + + + + quickfix/fix*/** + + + + + org.apache.maven.plugins + maven-source-plugin + + + quickfix/** + org/** + quickfix/field/* + quickfix/field/converter/* + FIX*.xml + + + + quickfix/fix*/** + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + true + 8 + src/main/java:${project.build.directory}/generated-sources + + + + + + org.apache.felix + maven-bundle-plugin + + + quickfix,quickfix.*,org.quickfixj,org.quickfixj.* + + + + + + - - - src/test/resources - - - ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources - - - ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources - - - src/main/resources - - + + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin-version} + + + true + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.3.0 + + + - - - org.quickfixj - quickfixj-codegenerator - ${project.version} - - - fixt11 - - generate - - - ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources/FIXT11.xml - quickfix.fixt11 - quickfix.field - ${generator.decimal} - - - - fix50sp2 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources/FIX50SP2.modified.xml - quickfix.fix50sp2 - quickfix.field - ${generator.decimal} - - - - fix50sp1 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources/FIX50SP1.modified.xml - quickfix.fix50sp1 - quickfix.field - ${generator.decimal} - - - - fix50 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources/FIX50.xml - quickfix.fix50 - quickfix.field - ${generator.decimal} - - - - fix44 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources/FIX44.modified.xml - quickfix.fix44 - quickfix.field - ${generator.decimal} - - - - fix43 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources/FIX43.xml - quickfix.fix43 - quickfix.field - ${generator.decimal} - - - - fix42 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources/FIX42.xml - quickfix.fix42 - quickfix.field - ${generator.decimal} - - - - fix41 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources/FIX41.xml - quickfix.fix41 - quickfix.field - ${generator.decimal} - - - - fix40 - - generate - - - ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources/FIX40.xml - quickfix.fix40 - quickfix.field - ${generator.decimal} - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin-version} - - - quickfix/** - org/** - quickfix/field/converter/* - FIX*.xml - - - quickfix/field/* - quickfix/fix*/** - - - - - org.apache.maven.plugins - maven-source-plugin - - - quickfix/** - org/** - quickfix/field/converter/* - FIX*.xml - - - quickfix/field/* - quickfix/fix*/** - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin-version} - - - attach-javadocs - - jar - - - none - 3g - false - - src/main/java - - - - - - org.apache.felix - maven-bundle-plugin - - - quickfix,quickfix.field.*,quickfix.mina.*,org.quickfixj.* - - - quickfix.fix40;resolution:=optional, - quickfix.fix41;resolution:=optional, - quickfix.fix42;resolution:=optional, - quickfix.fix43;resolution:=optional, - quickfix.fix44;resolution:=optional, - quickfix.fix50;resolution:=optional, - quickfix.fix50sp1;resolution:=optional, - quickfix.fix50sp2;resolution:=optional, - quickfix.fixt11;resolution:=optional, - - quickfix,quickfix.field,* - - - - - - - - - - - - maven-surefire-report-plugin - ${maven-surefire-plugin-version} - - - true - - - - maven-jxr-plugin - 3.3.0 - - - - - - - - skipAT - - - skipAT - true - - - - - - - - - surefire-java8 - - 1.8 - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - -Xmx512m -Djava.net.preferIPv4Stack=true - - false - - **/*Test.java - ${acceptance.tests} - - - **/*ForTest.java - **/Abstract*Test.java - **/AcceptanceTestSuite$* - - - 5 - 60000 - 5 - false - - - - - - - - - surefire - - [1.9,) - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - -Xmx512m -Djava.net.preferIPv4Stack=true - --add-opens java.base/java.lang=ALL-UNNAMED - - false - - **/*Test.java - ${acceptance.tests} - - - **/*ForTest.java - **/Abstract*Test.java - **/AcceptanceTestSuite$* - - - 5 - 60000 - 5 - false - - - - - - - + + + + skipAT + + + skipAT + true + + + + + + + + + surefire-java8 + + 1.8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -Xmx2g -Djava.net.preferIPv4Stack=true + + false + + **/*Test.java + ${acceptance.tests} + + + **/*ForTest.java + **/Abstract*Test.java + **/AcceptanceTestSuite$* + + + 5 + 60000 + 5 + false + + + + + + + + + surefire + + [1.9,) + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -Xmx2g -Djava.net.preferIPv4Stack=true + --add-opens java.base/java.lang=ALL-UNNAMED + + false + + **/*Test.java + ${acceptance.tests} + + + **/*ForTest.java + **/Abstract*Test.java + **/AcceptanceTestSuite$* + + + 5 + 60000 + 5 + false + + + + + + + diff --git a/quickfixj-core/readme.md b/quickfixj-core/readme.md new file mode 100644 index 0000000000..e1a31e128c --- /dev/null +++ b/quickfixj-core/readme.md @@ -0,0 +1,13 @@ +# quickfixj-core + +This module builds the QuickFIX/J FIX "engine" including supporting capabilities. + +The core engine depends on ```quickfixj-base```. A small number of derived Fields are provided by ```quickfixj-base``. + + This module has **test** dependency on generated message classes. The distinction of *test* dependency is significant as it allows easier [customisation](../customising-quickfixj.md) of QuickFIX/J deployments. + +## Developing quickfixj-core + +To develop this module build ```quickfixj-base``` and ```quickfixj-messages``` first, or build the whole project. + +Full ```quickfixj-messages``` build times can be long. See [the ```quickfixj-messages``` readme](../quickfixj-messages/readme.md) for how to do a minimal development build of the messages. Once these dependencies are built then ```quickfixj-core``` can be built and tested independently for higher productivity. diff --git a/quickfixj-core/src/main/doc/usermanual/installation.html b/quickfixj-core/src/main/doc/usermanual/installation.html index f05bea37a7..58c88bad9a 100644 --- a/quickfixj-core/src/main/doc/usermanual/installation.html +++ b/quickfixj-core/src/main/doc/usermanual/installation.html @@ -13,39 +13,77 @@

QuickFIX/J User Manual

Installation and Building the Code.

Runtime Dependencies

Java Virtual Machine:

-

JVM compatible with Oracle JRE Java 1.7.x or higher.

+

JVM compatible with JRE Java 1.8.x or higher.

Required run-time libraries:

+

QuickFIX/J has "base" and "core" libraries for the FIX "Engine" and one or more Message libraries are required at runtime.

+ +

The required Message libraries depend on the FIX Protocol Version. A QuickFIX/J process can support more than one FIX Protocol version at runtime, using different Session configurations.

+ +

Please note that customised versions of the FIX message artefacts can be built to meet specific Rules of Engagement. Please refer to the QuickFIX/J github repository for details.

+

-(Note: The actual JAR files may have version numbers in them.) +(Note: The actual JAR files have semantic version numbers in the name. For example : quickfixj-messages-fixlatest-3.0.0.jar)

- - - - - - - - + + + + + + + + + + + + + + + + +
LibraryDescription
The QFJ core JAR and message JARs. -
    -
  • quickfixj-core.jar -
  • quickfixj-msg-fix40.jar -
  • quickfixj-msg-fix41.jar -
  • quickfixj-msg-fix42.jar -
  • quickfixj-msg-fix43.jar -
  • quickfixj-msg-fix44.jar -
  • quickfixj-msg-fix50.jar -
  • quickfixj-msg-fix50sp1.jar -
  • quickfixj-msg-fix50sp2.jar -
- or -
    -
  • quickfixj-all.jar (includes core and message JARs) -
-
QFJ runtime libraries
LibraryDescription
The QuickFIX/J base and core JARs and message JARs. +
    +
  • quickfixj-base-{VERSION}.jar +
  • quickfixj-core-{VERSION}.jar +
+
QuickFIX/J fundamental libraries
With one or more message JAR for FIX versions before FIX 5.0 +
    +
  • quickfixj-messages-fix40-{VERSION}.jar +
  • quickfixj-messages-fix41-{VERSION}.jar +
  • quickfixj-messages-fix42-{VERSION}.jar +
  • quickfixj-messages-fix43-{VERSION}.jar +
  • quickfixj-messages-fix44-{VERSION}.jar +
+ AND/OR + + + + + + + +
+ The FIX Transport Layer JAR +
    +
  • quickfixj-messages-fixt11-{VERSION}.jar
  • +
+
+ With one or more FIX Application Layer message JAR for FIX versions 5.0 and later +
    +
  • quickfixj-messages-fix50-{VERSION}.jar +
  • quickfixj-messages-fix50sp1-{VERSION}.jar +
  • quickfixj-messages-fix50sp2-{VERSION}.jar +
  • quickfixj-messages-fixlatest-{VERSION}.jar +
+
+
Message Libraries required at runtime
OR
+
    +
  • quickfixj-all-{VERSION}.jar (includes core, base and message JARs) +
+
JAR including base, core and message JARs

@@ -108,12 +146,11 @@

Optional run-time libraries:

Building QuickFIX/J

These instructions are for developers who don't want - to use the prebuilt binaries or are intending to modify and rebuild the QuickFIX/J + to use the pre-built binaries or are intending to modify and rebuild the QuickFIX/J code. If you are building the code from the command line you'll need - to download and install Maven (version 3.2.5 or newer). If you are building from - an IDE, Maven is usually included. + to download and install Maven (version 3.5.0 or newer). -Building from source requires Java 7+. +Building from source requires Java 8 or later.

    @@ -152,6 +189,7 @@

    Command-line Switches

    For example, in order to generate fields with BigDecimals and skip acceptance tests:
    mvn test -Dgenerator.decimal=true -DskipAT=true

    +

    Find further details in the project readme

    IDE support:

    When the project is first created, it will not have the generated message classes and compile errors will occur! Best is to compile once on the command line before importing @@ -163,52 +201,62 @@

    Maven Integration:

    If you are using the Maven build system, you can reference the pre-built QuickFIX/J libraries hosted at the Central Repository repository.

    -

    Add the following to your dependencies section, with appropriate modifications based on - the logging subsystem you choose:
    -

    +

    You need only include message libraries for the FIX protocol version that you require.

    +

    Note : the quickfixj-messages-fixt11 jar is required for FIX versions 5.0 and later.

    +

    The following example shows maven dependencies, any SLF4J compatible logging implementation can be used:
    +

     <!-- QuickFIX/J dependencies -->
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-core</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
    +</dependency>
    +<dependency>
    +    <groupId>org.quickfixj</groupId>
    +    <artifactId>quickfixj-base</artifactId>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-messages-fix40</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-messages-fix41</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-messages-fix42</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-messages-fix43</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.quickfixj</groupId>
         <artifactId>quickfixj-messages-fix44</artifactId>
    -    <version>2.0.0</version>
    +    <version>3.0.0</version>
     </dependency>
     <dependency>
         <groupId>org.slf4j</groupId>
    -    <artifactId>slf4j-log4j12</artifactId>
    -    <version>1.7.22</version>
    +    <artifactId>slf4j-api</artifactId>
    +    <version>2.0.6</version>
     </dependency>
     <dependency>
         <groupId>org.slf4j</groupId>
    -    <artifactId>slf4j-api</artifactId>
    -    <version>1.7.22</version>
    -</dependency>
    + <artifactId>slf4j-log4j12</artifactId> + <version>2.0.6/version> +</dependency> + +

    +

    Please refer to the QuickFIX/J github repository for more details about working with QuickFIX/J.

    +

    Generating the database for JDBC based store and log

    Everything needed to generate your database is in the src/main/resources/config/sql subdirectories. For MySQL, there are the script and batch files create_mysql.sh and create_mysql.bat. @@ -219,7 +267,7 @@

    Generating the database for JDBC based store and log< 'root' to another user. To add a password, use pass the -p option after the username. Similar scripts are provided for MSSQL, PostgreSQL and Oracle.

    -

    Special notes for Oracle

    +

    Special notes for Oracle

    Oracle treats empty strings as null values. Null values are not allowed for primary key fields. The fields used in the primary keys are: diff --git a/quickfixj-core/src/main/doc/usermanual/support.html b/quickfixj-core/src/main/doc/usermanual/support.html index caec340aa2..0fc87ec45c 100644 --- a/quickfixj-core/src/main/doc/usermanual/support.html +++ b/quickfixj-core/src/main/doc/usermanual/support.html @@ -13,7 +13,7 @@

    QuickFIX/J User Manual

    QuickFIX/J Support

    Mailing Lists

    -

    QuickFIX/J issues can be discussed on the mailing +

    QuickFIX/J issues can be discussed on the mailing lists hosts at the SourceForge project site.

    Web Site

    @@ -21,9 +21,11 @@

    Web Site

    site.

    More information about the original C++ QuickFIX can be found at the quickfixengine.org web site.

    +

    Git Hub

    +

    Further detailed documents and source code can be found on github

    FIX Protocol

    -

    More information about the FIX protocol can be found at the FIX - Protocol Limited web site.

    +

    More information about the FIX protocol can be found at the FIX + Trading Community web site.

    diff --git a/quickfixj-core/src/main/doc/usermanual/usage/codegen.html b/quickfixj-core/src/main/doc/usermanual/usage/codegen.html index 51a703669d..2191667a04 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/codegen.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/codegen.html @@ -9,86 +9,91 @@

    QuickFIX/J User Manual

    Customizing Message Code Generation

    -QuickFIX/J includes message libraries generated from standard FIX meta data. However, -there are several ways to customize code generation in QuickFIX/J. -

    -

    -The simplest customization is to just modify one or more data dictionaries -(e.g., FIX44.xml) and rebuild QFJ. This allows you to add custom fields, -define new messages not included in the specification, change whether fields -are required or optional, and so on. -

    -

    -You can also do more advanced customization by using the -quickfix.codegen.MessageCodeGenerator class. You can define -code generation tasks (quickfix.codegen.MessageCodeGenerator.Task) -that customize various aspects of the generation -process. The types of customizations currently supported include: -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyDescription
    specificationpath to the XML file containing the FIX meta data (e.g. /my/dir/CUSTOM_FIX.xml)
    transformDirectorypath to the XSLT transforms used to generate the message-related source code. Usually this - will reference the standard QFJ XSLT templates in quickfix/codegen. However, you can - modify the templates and put the modified templates wherever you want.
    outputBaseDirectoryThe base directory where generated source code will be placed.
    overwriteControls whether existings files are overwritten. Usually this would be true.
    messagePackageThe Java package for the generated messages. This would be something like "my.message.fix42". - In QFJ, for example, the package for the FIX 4.2 generated messages is "quickfix.fix42".
    fieldPackageThe Java package for the generated field. This would be something like "my.message.fix42.fields". - In QFJ, the fields are generated in the package "quickfix.fields" for all versions (newer versions - overwrite older versions). By changing this property, you could generate fields into separate packages - for each specification version.
    orderedFieldsGenerates message classes where regular (nongroup) body fields are ordered as specified in - the meta. Although the FIX specification does not require this ordering, some exchanges do - require it. There may be a slight (probably very slight) performance degradation when - using this option.
    decimalGeneratedGenerates BigDecimals for price, quantity, and similar fields. The default code - generation generated doubles to be compatible with the QuickFIX C++ implementation.
    utcTimestampPrecisionThe default UtcTimestampPrecision to be used during field code generation.
    -

    -To generate you own message library, you can create a program that uses the MessageCodeGenerator -to process the Task you have defined. Then use a simple Ant script to do the code -generation, compile the classes, and create a message JAR file. Depending on the type of customization -you've done, you may need to write a MessageFactory implementation, include that in the -JAR file, and then reference the factory in the session configuration. For Ant script examples, see -the QFJ core build scripts. -

    -The most advanced customization is to modify the XSLT templates used to generate the -message source code. If you are interested in this level of customization, please look -at the XSLT template source code for more information. +QuickFIX/J includes message libraries generated from FIX standard meta-data. However, it is very common to need to +customize the FIX Protocol to suit an organisations specific "Rules of Engagement". +There is more than one way to customize Message and Field code generation in QuickFIX/J.

    +
      +
    • For FIX versions prior to "FIX Latest" : +
        +
      • You may fork the entire QuickFIX/J repository and modify one or more data dictionaries (e.g., FIX44.xml) and rebuild QFJ. + This allows you to add custom Fields, define new Messages not included in the specification, change whether Fields are required or optional, and so on. + This approach is good for prototyping and/or experimentation but is not recommended in the middle to long term due to the disadvantage of needing to maintain a fork of the QuickFIX/J project.
      • +
      • You may create an independent project to build only your QuickFIX/J Messages and Fields using the tools provided by + the QuickFIX/J project. See the readme in the QuickFIX/J Messages module (quickfixj-messages) for further details. + This allows you to manage dependencies on the QuickFIX/J core runtime independent of the custom Message and Field packages + (within the scope of binary compatible QuickFIX/J major releases).
      • +
      +

      You can also do more advanced customization by using the + quickfix.codegen.MessageCodeGenerator class. You can define + code generation tasks (quickfix.codegen.MessageCodeGenerator.Task) + that customize various aspects of the generation + process. The types of customizations currently supported include: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      PropertyDescription
      specificationpath to the XML file containing the FIX meta data (e.g. /my/dir/CUSTOM_FIX.xml)
      transformDirectorypath to the XSLT transforms used to generate the message-related source code. Usually this + will reference the standard QFJ XSLT templates in quickfix/codegen. However, you can + modify the templates and put the modified templates wherever you want.
      outputBaseDirectoryThe base directory where generated source code will be placed.
      overwriteControls whether existings files are overwritten. Usually this would be true.
      messagePackageThe Java package for the generated messages. This would be something like "my.message.fix42". + In QFJ, for example, the package for the FIX 4.2 generated messages is "quickfix.fix42".
      fieldPackageThe Java package for the generated field. This would be something like "my.message.fix42.fields". + In QFJ, the fields are generated in the package "quickfix.fields" for all versions (newer versions + overwrite older versions). By changing this property, you could generate fields into separate packages + for each specification version.
      orderedFieldsGenerates message classes where regular (nongroup) body fields are ordered as specified in + the meta. Although the FIX specification does not require this ordering, some exchanges do + require it. There may be a slight (probably very slight) performance degradation when + using this option.
      decimalGeneratedGenerates BigDecimals for price, quantity, and similar fields. The default code + generation generated doubles to be compatible with the QuickFIX C++ implementation.
      utcTimestampPrecisionThe default UtcTimestampPrecision to be used during field code generation.
      +

      The most complex customization for this case is to modify the XSLT templates used to generate the + message source code. If you are interested in this level of customization, please look + at the XSLT template source code for more information.

      +

      You can build a QuickFIX/J Dictionary and generate code directly from a FIX Orchestra specification + using the new QuickFIX/J project tools. See the readme in the QuickFIX/J Messages module (quickfixj-messages) for further details.

      +
    • +
    • For FIX Latest: +

      It is recommended to create an independent project to build only your QuickFIX/J Messages and Fields using the tools provided by + the QuickFIX/J project. See the readme in the QuickFIX/J Messages module (quickfixj-messages) for further details. + This allows you to manage dependencies on the QuickFIX/J core runtime independent of the custom Message and Field packages + (within the scope of binary compatible QuickFIX/J major releases).

      +
    • +
    diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index 6708d0e9ca..8a661ef381 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -692,10 +692,12 @@

    QuickFIX Settings

    Java default cipher suites - UseSNI - Configures the SSL engine to use Server Name Indication. This option is only useful to initiators. - Y
    N - N + EndpointIdentificationAlgorithm + Sets the endpoint identification algorithm. If the algorithm parameter is non-null, the endpoint identification/verification procedures must be handled during SSL/TLS handshaking. See + Endpoint Identification + Algorithm Names + + diff --git a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java index 87fcb08f79..d55440bc0e 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java @@ -35,6 +35,7 @@ import static quickfix.FixVersions.FIX50; import static quickfix.FixVersions.FIX50SP1; import static quickfix.FixVersions.FIX50SP2; +import static quickfix.FixVersions.FIXLATEST; /** * The default factory for creating FIX message instances. @@ -55,7 +56,7 @@ public class DefaultMessageFactory implements MessageFactory { * Equivalent to {@link #DefaultMessageFactory(String) DefaultMessageFactory}({@link ApplVerID#FIX50 ApplVerID.FIX50}). */ public DefaultMessageFactory() { - this(ApplVerID.FIX50SP2); + this(ApplVerID.FIXLATEST); } /** @@ -84,6 +85,7 @@ public DefaultMessageFactory(String defaultApplVerID) { addFactory(FIX50); addFactory(FIX50SP1); addFactory(FIX50SP2); + addFactory(FIXLATEST); } private void addFactory(String beginString) { diff --git a/quickfixj-core/src/main/java/quickfix/MessageSessionUtils.java b/quickfixj-core/src/main/java/quickfix/MessageSessionUtils.java new file mode 100644 index 0000000000..a6eec9e3a6 --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/MessageSessionUtils.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import quickfix.field.ApplVerID; +import quickfix.field.BeginString; +import quickfix.field.DefaultApplVerID; + +public class MessageSessionUtils { + + /** + * NOTE: This method is intended for internal use. + * + * @param session the Session that will process the message + * @param messageString + * @return the parsed message + * @throws InvalidMessage + */ + public static Message parse(Session session, String messageString) throws InvalidMessage { + final String beginString = MessageUtils.getStringField(messageString, BeginString.FIELD); + final String msgType = MessageUtils.getMessageType(messageString); + final MessageFactory messageFactory = session.getMessageFactory(); + final DataDictionaryProvider ddProvider = session.getDataDictionaryProvider(); + final ApplVerID applVerID; + final DataDictionary sessionDataDictionary = ddProvider == null ? null : ddProvider + .getSessionDataDictionary(beginString); + final quickfix.Message message; + final DataDictionary payloadDictionary; + + if (!MessageUtils.isAdminMessage(msgType) || MessageUtils.isLogon(messageString)) { + if (FixVersions.BEGINSTRING_FIXT11.equals(beginString)) { + applVerID = getApplVerID(session, messageString); + } else { + applVerID = MessageUtils.toApplVerID(beginString); + } + final DataDictionary applicationDataDictionary = ddProvider == null ? null : ddProvider + .getApplicationDataDictionary(applVerID); + payloadDictionary = MessageUtils.isAdminMessage(msgType) + ? sessionDataDictionary + : applicationDataDictionary; + } else { + applVerID = null; + payloadDictionary = sessionDataDictionary; + } + + final boolean doValidation = payloadDictionary != null; + final boolean validateChecksum = session.isValidateChecksum(); + + message = messageFactory.create(beginString, applVerID, msgType); + message.parse(messageString, sessionDataDictionary, payloadDictionary, doValidation, + validateChecksum); + + return message; + } + + private static ApplVerID getApplVerID(Session session, String messageString) + throws InvalidMessage { + ApplVerID applVerID = null; + + final String applVerIdString = MessageUtils.getStringField(messageString, ApplVerID.FIELD); + if (applVerIdString != null) { + applVerID = new ApplVerID(applVerIdString); + } + + if (applVerID == null) { + applVerID = session.getTargetDefaultApplicationVersionID(); + } + + if (applVerID == null && MessageUtils.isLogon(messageString)) { + final String defaultApplVerIdString = MessageUtils.getStringField(messageString, + DefaultApplVerID.FIELD); + if (defaultApplVerIdString != null) { + applVerID = new ApplVerID(defaultApplVerIdString); + } + } + + if (applVerID == null) { + throw MessageUtils.newInvalidMessageException("Can't determine ApplVerID from message " + messageString, MessageUtils.getMinimalMessage(messageString)); + } + + return applVerID; + } +} diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index 7e52be07bf..77a918e85e 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -272,7 +272,7 @@ public class Session implements Closeable { /** * Session setting to control precision in message timestamps. * Valid values are "SECONDS", "MILLIS", "MICROS", "NANOS". Default is "MILLIS". - * Only valid for FIX version >= 4.2. + * Only valid for FIX version ">"= 4.2. */ public static final String SETTING_TIMESTAMP_PRECISION = "TimeStampPrecision"; @@ -283,7 +283,7 @@ public class Session implements Closeable { /** * Session setting that causes the session to reset sequence numbers when initiating - * a logon (>= FIX 4.2). + * a logon (">"= FIX 4.2). */ public static final String SETTING_RESET_ON_LOGON = "ResetOnLogon"; @@ -317,7 +317,7 @@ public class Session implements Closeable { public static final String SETTING_USE_CLOSED_RESEND_INTERVAL = "ClosedResendInterval"; /** - * Allow unknown fields in messages. This is intended for unknown fields with tags < 5000 + * Allow unknown fields in messages. This is intended for unknown fields with tags "<" 5000 * (not user defined fields) */ public static final String SETTING_ALLOW_UNKNOWN_MSG_FIELDS = "AllowUnknownMsgFields"; @@ -1379,7 +1379,7 @@ private String formatEndSeqNum(int seqNo) { } private Message parseMessage(String messageData) throws InvalidMessage { - return MessageUtils.parse(this, messageData); + return MessageSessionUtils.parse(this, messageData); } private boolean isTargetTooLow(int msgSeqNum) throws IOException { @@ -1726,9 +1726,10 @@ private void setRejectReason(Message reject, int field, String reason, private void generateBusinessReject(Message message, int err, int field) throws FieldNotFound, IOException { - final Message reject = messageFactory.create(sessionID.getBeginString(), - MsgType.BUSINESS_MESSAGE_REJECT); final Header header = message.getHeader(); + ApplVerID targetDefaultApplicationVersionID = getTargetDefaultApplicationVersionID(); + final Message reject = messageFactory.create(sessionID.getBeginString(), targetDefaultApplicationVersionID, + MsgType.BUSINESS_MESSAGE_REJECT); reject.reverseRoute(header); initializeHeader(reject.getHeader()); @@ -1742,8 +1743,8 @@ private void generateBusinessReject(Message message, int err, int field) throws final String reason = BusinessRejectReasonText.getMessage(err); setRejectReason(reject, field, reason, field != 0); getLog().onErrorEvent( - "Reject sent for message " + msgSeqNum + (reason != null ? (": " + reason) : "") - + (field != 0 ? (": tag=" + field) : "")); + "Reject sent for message number " + msgSeqNum + (reason != null ? (": " + reason) : "") + + (field != 0 ? (": tag=" + field) : "") + " Message [" + message + "]" + " Reject [" + reject + "]"); sendRaw(reject, 0); } diff --git a/quickfixj-core/src/main/java/quickfix/SocketInitiator.java b/quickfixj-core/src/main/java/quickfix/SocketInitiator.java index 7da2f1b3f7..fca1b0d089 100644 --- a/quickfixj-core/src/main/java/quickfix/SocketInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/SocketInitiator.java @@ -125,9 +125,6 @@ private void initialize() throws ConfigError { if (isStarted.compareAndSet(false, true)) { eventHandlingStrategy.setExecutor(longLivedExecutor); createSessionInitiators(); - for (Session session : getSessionMap().values()) { - Session.registerSession(session); - } startInitiators(); eventHandlingStrategy.blockInThread(); } diff --git a/quickfixj-core/src/main/java/quickfix/mina/AbstractIoHandler.java b/quickfixj-core/src/main/java/quickfix/mina/AbstractIoHandler.java index 207238d62f..79203dccc6 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/AbstractIoHandler.java +++ b/quickfixj-core/src/main/java/quickfix/mina/AbstractIoHandler.java @@ -37,7 +37,7 @@ import quickfix.SessionID; import quickfix.SessionSettings; -import static quickfix.MessageUtils.parse; +import static quickfix.MessageSessionUtils.parse; /** * Abstract class used for acceptor and initiator IO handlers. diff --git a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java index 2b7cb2b359..93a10c98c9 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java +++ b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java @@ -23,6 +23,7 @@ import org.apache.mina.core.buffer.SimpleBufferAllocator; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.ssl.SslFilter; import quickfix.Acceptor; import quickfix.Application; import quickfix.ConfigError; @@ -45,7 +46,6 @@ import quickfix.mina.message.FIXProtocolCodecFactory; import quickfix.mina.ssl.SSLConfig; import quickfix.mina.ssl.SSLContextFactory; -import quickfix.mina.ssl.SSLFilter; import quickfix.mina.ssl.SSLSupport; import javax.net.ssl.SSLContext; @@ -132,10 +132,9 @@ private void installSSL(AcceptorSocketDescriptor descriptor, log.info("Installing SSL filter for {}", descriptor.getAddress()); SSLConfig sslConfig = descriptor.getSslConfig(); SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - SSLFilter sslFilter = new SSLFilter(sslContext); - sslFilter.setUseClientMode(false); + SslFilter sslFilter = new SslFilter(sslContext); sslFilter.setNeedClientAuth(sslConfig.isNeedClientAuth()); - sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() + sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() : SSLSupport.getSupportedProtocols(sslContext)); diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java index 49bbdf620f..bdbc10f459 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java @@ -22,16 +22,12 @@ import org.apache.mina.core.session.IoSession; import org.apache.mina.proxy.AbstractProxyIoHandler; -import quickfix.mina.ssl.SSLFilter; - class InitiatorProxyIoHandler extends AbstractProxyIoHandler { private final InitiatorIoHandler initiatorIoHandler; - private final SSLFilter sslFilter; - InitiatorProxyIoHandler(InitiatorIoHandler initiatorIoHandler, SSLFilter sslFilter) { + InitiatorProxyIoHandler(InitiatorIoHandler initiatorIoHandler) { super(); this.initiatorIoHandler = initiatorIoHandler; - this.sslFilter = sslFilter; } @Override @@ -60,9 +56,6 @@ public void exceptionCaught(IoSession ioSession, Throwable cause) throws Excepti } @Override - public void proxySessionOpened(IoSession ioSession) throws Exception { - if (this.sslFilter != null) { - this.sslFilter.initiateHandshake(ioSession); - } + public void proxySessionOpened(IoSession ioSession) { } } diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java index e6c97eeeb8..b3826b091f 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -24,6 +24,7 @@ import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.proxy.ProxyConnector; import org.apache.mina.transport.socket.SocketConnector; import quickfix.ConfigError; @@ -40,7 +41,6 @@ import quickfix.mina.message.FIXProtocolCodecFactory; import quickfix.mina.ssl.SSLConfig; import quickfix.mina.ssl.SSLContextFactory; -import quickfix.mina.ssl.SSLFilter; import quickfix.mina.ssl.SSLSupport; import javax.net.ssl.SSLContext; @@ -153,9 +153,9 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { boolean hasProxy = proxyType != null && proxyPort > 0 && socketAddresses[nextSocketAddressIndex] instanceof InetSocketAddress; - SSLFilter sslFilter = null; + SslFilter sslFilter = null; if (sslEnabled) { - sslFilter = installSslFilter(ioFilterChainBuilder, !hasProxy); + sslFilter = installSslFilter(ioFilterChainBuilder); } ioFilterChainBuilder.addLast(FIXProtocolCodecFactory.FILTER_NAME, new ProtocolCodecFilter(new FIXProtocolCodecFactory())); @@ -175,9 +175,7 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { ); proxyConnector.setHandler(new InitiatorProxyIoHandler( - new InitiatorIoHandler(fixSession, sessionSettings, networkingOptions, eventHandlingStrategy), - sslFilter - )); + new InitiatorIoHandler(fixSession, sessionSettings, networkingOptions, eventHandlingStrategy))); newConnector = proxyConnector; } @@ -188,16 +186,15 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { ioConnector = newConnector; } - private SSLFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder, boolean autoStart) + private SslFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder) throws GeneralSecurityException { final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - final SSLFilter sslFilter = new SSLFilter(sslContext, autoStart); - sslFilter.setUseClientMode(true); - sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() + final SslFilter sslFilter = new SslFilter(sslContext); + sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() : SSLSupport.getSupportedProtocols(sslContext)); - sslFilter.setUseSNI(sslConfig.isUseSNI()); + sslFilter.setEndpointIdentificationAlgorithm(sslConfig.getEndpointIdentificationAlgorithm()); ioFilterChainBuilder.addLast(SSLSupport.FILTER_NAME, sslFilter); return sslFilter; } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java index 667750f341..4d45fe9f8c 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java @@ -20,6 +20,7 @@ package quickfix.mina.ssl; import java.util.Arrays; +import java.util.Objects; /** * Groups together SSL related configuration. @@ -36,58 +37,7 @@ public class SSLConfig { private String[] enabledProtocols; private String[] enabledCipherSuites; private boolean needClientAuth; - private boolean useSNI; - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SSLConfig other = (SSLConfig) obj; - if (!Arrays.equals(enabledCipherSuites, other.enabledCipherSuites)) - return false; - if (!Arrays.equals(enabledProtocols, other.enabledProtocols)) - return false; - if (keyManagerFactoryAlgorithm == null) { - if (other.keyManagerFactoryAlgorithm != null) - return false; - } else if (!keyManagerFactoryAlgorithm.equals(other.keyManagerFactoryAlgorithm)) - return false; - if (keyStoreName == null) { - if (other.keyStoreName != null) - return false; - } else if (!keyStoreName.equals(other.keyStoreName)) - return false; - if (!Arrays.equals(keyStorePassword, other.keyStorePassword)) - return false; - if (keyStoreType == null) { - if (other.keyStoreType != null) - return false; - } else if (!keyStoreType.equals(other.keyStoreType)) - return false; - if (needClientAuth != other.needClientAuth) - return false; - if (trustManagerFactoryAlgorithm == null) { - if (other.trustManagerFactoryAlgorithm != null) - return false; - } else if (!trustManagerFactoryAlgorithm.equals(other.trustManagerFactoryAlgorithm)) - return false; - if (trustStoreName == null) { - if (other.trustStoreName != null) - return false; - } else if (!trustStoreName.equals(other.trustStoreName)) - return false; - if (!Arrays.equals(trustStorePassword, other.trustStorePassword)) - return false; - if(useSNI != other.useSNI) - return false; - if (trustStoreType == null) { - return other.trustStoreType == null; - } else return trustStoreType.equals(other.trustStoreType); - } + private String endpointIdentificationAlgorithm; public String[] getEnabledCipherSuites() { return enabledCipherSuites; @@ -129,31 +79,12 @@ public String getTrustStoreType() { return trustStoreType; } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(enabledCipherSuites); - result = prime * result + Arrays.hashCode(enabledProtocols); - result = prime * result + ((keyManagerFactoryAlgorithm == null) ? 0 : keyManagerFactoryAlgorithm.hashCode()); - result = prime * result + ((keyStoreName == null) ? 0 : keyStoreName.hashCode()); - result = prime * result + Arrays.hashCode(keyStorePassword); - result = prime * result + ((keyStoreType == null) ? 0 : keyStoreType.hashCode()); - result = prime * result + (needClientAuth ? 1231 : 1237); - result = prime * result - + ((trustManagerFactoryAlgorithm == null) ? 0 : trustManagerFactoryAlgorithm.hashCode()); - result = prime * result + ((trustStoreName == null) ? 0 : trustStoreName.hashCode()); - result = prime * result + Arrays.hashCode(trustStorePassword); - result = prime * result + ((trustStoreType == null) ? 0 : trustStoreType.hashCode()); - return result; - } - public boolean isNeedClientAuth() { return needClientAuth; } - public boolean isUseSNI() { - return useSNI; + public String getEndpointIdentificationAlgorithm() { + return endpointIdentificationAlgorithm; } public void setEnabledCipherSuites(String[] enabledCipherSuites) { @@ -184,8 +115,8 @@ public void setNeedClientAuth(boolean needClientAuth) { this.needClientAuth = needClientAuth; } - public void setUseSNI(boolean useSNI) { - this.useSNI = useSNI; + public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm) { + this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; } public void setTrustManagerFactoryAlgorithm(String trustManagerFactoryAlgorithm) { @@ -203,4 +134,33 @@ public void setTrustStorePassword(char[] trustStorePassword) { public void setTrustStoreType(String trustStoreType) { this.trustStoreType = trustStoreType; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SSLConfig sslConfig = (SSLConfig) o; + return needClientAuth == sslConfig.needClientAuth && + Objects.equals(keyStoreName, sslConfig.keyStoreName) && + Arrays.equals(keyStorePassword, sslConfig.keyStorePassword) && + Objects.equals(keyManagerFactoryAlgorithm, sslConfig.keyManagerFactoryAlgorithm) && + Objects.equals(keyStoreType, sslConfig.keyStoreType) && + Objects.equals(trustStoreName, sslConfig.trustStoreName) && + Arrays.equals(trustStorePassword, sslConfig.trustStorePassword) && + Objects.equals(trustManagerFactoryAlgorithm, sslConfig.trustManagerFactoryAlgorithm) && + Objects.equals(trustStoreType, sslConfig.trustStoreType) && + Arrays.equals(enabledProtocols, sslConfig.enabledProtocols) && + Arrays.equals(enabledCipherSuites, sslConfig.enabledCipherSuites) && + Objects.equals(endpointIdentificationAlgorithm, sslConfig.endpointIdentificationAlgorithm); + } + + @Override + public int hashCode() { + int result = Objects.hash(keyStoreName, keyManagerFactoryAlgorithm, keyStoreType, trustStoreName, trustManagerFactoryAlgorithm, trustStoreType, needClientAuth, endpointIdentificationAlgorithm); + result = 31 * result + Arrays.hashCode(keyStorePassword); + result = 31 * result + Arrays.hashCode(trustStorePassword); + result = 31 * result + Arrays.hashCode(enabledProtocols); + result = 31 * result + Arrays.hashCode(enabledCipherSuites); + return result; + } } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java deleted file mode 100644 index b9c036f9d2..0000000000 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * Copyright (c) quickfixengine.org All rights reserved. - * - * This file is part of the QuickFIX FIX Engine - * - * This file may be distributed under the terms of the quickfixengine.org - * license as defined by quickfixengine.org and appearing in the file - * LICENSE included in the packaging of this file. - * - * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING - * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE. - * - * See http://www.quickfixengine.org/LICENSE for licensing information. - * - * Contact ask@quickfixengine.org if any conditions of this licensing - * are not clear to you. - ******************************************************************************/ - -package quickfix.mina.ssl; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import javax.net.ssl.SSLContext; - -import javax.net.ssl.SSLException; -import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An extended SSL filter based on MINA {@link SslFilter} that applies - * some adaptations. - */ -public class SSLFilter extends SslFilter { - - private final Logger log = LoggerFactory.getLogger(getClass()); - private boolean useSNI; - - public SSLFilter(SSLContext sslContext, boolean autoStart) { - super(sslContext, autoStart); - } - - public SSLFilter(SSLContext sslContext) { - super(sslContext); - } - - /** - * Called from {@link SslFilter#onPreAdd} every time a new - * session is created which makes it impossible to override enabled cipher - * suites configuration. - */ - @Override - public void setEnabledCipherSuites(String[] cipherSuites) { - } - - public void setCipherSuites(String[] cipherSuites) { - super.setEnabledCipherSuites(cipherSuites); - } - - /** - * Called before filter is added into the chain. - * We activate Server Name Indication if it is enabled in the session config. - */ - @Override - public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) - throws SSLException { - - if (useSNI) { - IoSession session = parent.getSession(); - SocketAddress remoteAddress = session.getRemoteAddress(); - - if (remoteAddress instanceof InetSocketAddress) { - // activate the SNI support in the JSSE SSLEngine - log.info("Activating TLS SNI support for peer address: {}", remoteAddress); - session.setAttribute(PEER_ADDRESS, remoteAddress); - } - } - - super.onPreAdd(parent, name, nextFilter); - } - - public void setUseSNI(boolean useSNI) { - this.useSNI = useSNI; - } -} diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java index d821f2f64f..52550ad813 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java @@ -39,7 +39,7 @@ public class SSLSupport { public static final String SETTING_TRUST_MANAGER_FACTORY_ALGORITHM = "TrustManagerFactoryAlgorithm"; public static final String SETTING_TRUST_STORE_TYPE = "TrustStoreType"; public static final String SETTING_NEED_CLIENT_AUTH = "NeedClientAuth"; - public static final String SETTING_USE_SNI = "UseSNI"; + public static final String SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM = "EndpointIdentificationAlgorithm"; public static final String SETTING_ENABLED_PROTOCOLS = "EnabledProtocols"; public static final String SETTING_CIPHER_SUITES = "CipherSuites"; static final String DEFAULT_STORE_TYPE = "JKS"; @@ -111,7 +111,7 @@ public static SSLConfig getSslConfig(SessionSettings sessionSettings, SessionID sslConfig.setEnabledCipherSuites(getEnabledCipherSuites(sessionSettings, sessionID)); sslConfig.setEnabledProtocols(getEnabledProtocols(sessionSettings, sessionID)); sslConfig.setNeedClientAuth(isNeedClientAuth(sessionSettings, sessionID)); - sslConfig.setUseSNI(isUseSNI(sessionSettings, sessionID)); + sslConfig.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm(sessionSettings, sessionID)); return sslConfig; } @@ -150,7 +150,7 @@ public static boolean isNeedClientAuth(SessionSettings sessionSettings, SessionI return "Y".equals(getString(sessionSettings, sessionID, SETTING_NEED_CLIENT_AUTH, "N")); } - public static boolean isUseSNI(SessionSettings sessionSettings, SessionID sessionID) { - return "Y".equals(getString(sessionSettings, sessionID, SETTING_USE_SNI, "N")); + public static String getEndpointIdentificationAlgorithm(SessionSettings sessionSettings, SessionID sessionID) { + return getString(sessionSettings, sessionID, SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, null); } } diff --git a/quickfixj-core/src/test/java/quickfix/DataDictionaryTest.java b/quickfixj-core/src/test/java/quickfix/DataDictionaryTest.java index e660f5d8bc..add2d6fe1a 100644 --- a/quickfixj-core/src/test/java/quickfix/DataDictionaryTest.java +++ b/quickfixj-core/src/test/java/quickfix/DataDictionaryTest.java @@ -19,9 +19,24 @@ package quickfix; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.hasProperty; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import quickfix.field.Account; import quickfix.field.AvgPx; import quickfix.field.BodyLength; @@ -32,7 +47,6 @@ import quickfix.field.LastMkt; import quickfix.field.MsgSeqNum; import quickfix.field.MsgType; -import quickfix.field.NoHops; import quickfix.field.NoPartyIDs; import quickfix.field.NoPartySubIDs; import quickfix.field.NoRelatedSym; @@ -59,693 +73,11 @@ import quickfix.fix44.QuoteRequest; import quickfix.test.util.ExpectedTestFailure; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.math.BigDecimal; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.hasProperty; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - public class DataDictionaryTest { @Rule public final ExpectedException expectedException = ExpectedException.none(); - @Test - public void testDictionary() throws Exception { - DataDictionary dd = getDictionary(); - - assertEquals("wrong field name", "Currency", dd.getFieldName(15)); - assertEquals("wrong value description", "BUY", dd.getValueName(4, "B")); - assertEquals("wrong value for given value name", "2", dd.getValue(54, "SELL")); - assertEquals("wrong value type", FieldType.STRING, dd.getFieldType(1)); - assertEquals("wrong version", FixVersions.BEGINSTRING_FIX44, dd.getVersion()); - assertFalse("unexpected field values existence", dd.hasFieldValue(1)); - assertTrue("unexpected field values nonexistence", dd.hasFieldValue(4)); - assertFalse("unexpected field existence", dd.isField(9999)); - assertTrue("unexpected field nonexistence", dd.isField(4)); - assertTrue("unexpected field value existence", !dd.isFieldValue(4, "C")); - assertTrue("unexpected field value nonexistence", dd.isFieldValue(4, "B")); - assertTrue("wrong group info", dd.isGroup("A", 384)); - assertFalse("wrong group info", dd.isGroup("A", 1)); - assertNotNull("wrong group info", dd.getGroup("6", 232)); - assertTrue("incorrect header field", dd.isHeaderField(8)); - assertFalse("incorrect header field", dd.isHeaderField(1)); - assertTrue("incorrect trailer field", dd.isTrailerField(89)); - assertFalse("incorrect trailer field", dd.isTrailerField(1)); - assertTrue("incorrect message field", dd.isMsgField("A", 98)); - assertFalse("incorrect message field", dd.isMsgField("A", 1)); - // component field - assertTrue("incorrect message field", dd.isMsgField("6", 235)); - // group->component field - //assertTrue("incorrect message field", dd.isMsgField("6", 311)); - assertTrue("incorrect message type", dd.isMsgType("A")); - assertFalse("incorrect message type", dd.isMsgType("%")); - assertTrue("incorrect field requirement", dd.isRequiredField("A", 98)); - assertFalse("incorrect field requirement", dd.isRequiredField("A", 95)); - assertEquals("incorrect field name", "Account", dd.getFieldName(1)); - assertEquals("incorrect msg type", "0", dd.getMsgType("Heartbeat")); - assertEquals("incorrect msg type", "B", dd.getMsgType("News")); - assertFalse(dd.isMsgField("UNKNOWN_TYPE", 1)); - } - - @Test - public void testMissingFieldAttributeForRequired() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - assertConfigErrorForMissingAttributeRequired(data); - } - - private void assertConfigErrorForMissingAttributeRequired(String data) { - try { - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } catch (ConfigError e) { - // Expected - assertTrue(e.getMessage().contains("does not have a 'required'")); - } - } - - @Test - public void testMissingComponentAttributeForRequired() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - assertConfigErrorForMissingAttributeRequired(data); - } - - @Test - public void testMissingGroupAttributeForRequired() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - assertConfigErrorForMissingAttributeRequired(data); - } - - @Test - public void testHeaderTrailerRequired() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - DataDictionary dd = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(1, dd.getNumMessageCategories()); - assertEquals("0", dd.getMsgType("Heartbeat")); - - assertTrue("BeginString should be required", dd.isRequiredHeaderField(8)); - assertFalse("OnBehalfOfCompID should not be required", dd.isRequiredHeaderField(115)); - assertTrue("Checksum should be required", dd.isRequiredTrailerField(10)); - assertFalse("Signature should not be required", dd.isRequiredTrailerField(89)); - - // now tests for fields that aren't actually in the dictionary - should come back false - assertFalse("Unknown header field shows up as required", dd.isRequiredHeaderField(666)); - assertFalse("Unknown trailer field shows up as required", dd.isRequiredTrailerField(666)); - } - - @Test - public void testMessageWithNoChildren40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=msg"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessageWithTextElement40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=msg"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessagesWithNoChildren40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No messages defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessagesWithTextElement40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No messages defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testHeaderWithNoChildren40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += ""; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=HEADER"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testHeaderWithTextElement40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=HEADER"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testTrailerWithNoChildren40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=TRAILER"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testTrailerWithTextElement40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=TRAILER"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testFieldsWithNoChildren40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testFieldsWithTextElement40() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessageWithNoChildren50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=msg"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessageWithTextElement50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields found: msgType=msg"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessagesWithNoChildren50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No messages defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testMessagesWithTextElement50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No messages defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testHeaderWithNoChildren50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += ""; - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testHeaderWithTextElement50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testTrailerWithNoChildren50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testTrailerWithTextElement50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testFieldsWithNoChildren50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += ""; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testFieldsWithTextElement50() throws Exception { - String data = ""; - data += ""; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += ""; - - expectedException.expect(ConfigError.class); - expectedException.expectMessage("No fields defined"); - - new DataDictionary(new ByteArrayInputStream(data.getBytes())); - } - - @Test - public void testHeaderGroupField() throws Exception { - DataDictionary dd = getDictionary(); - assertTrue(dd.isHeaderGroup(NoHops.FIELD)); - } - @Test public void testMessageValidateBodyOnly() throws Exception { final quickfix.fix44.NewOrderSingle newSingle = new quickfix.fix44.NewOrderSingle( @@ -755,7 +87,7 @@ public void testMessageValidateBodyOnly() throws Exception { newSingle.setField(new Price(42.37)); newSingle.setField(new HandlInst()); newSingle.setField(new Symbol("QFJ")); - newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER_BEST_EXECUTION)); + newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER)); newSingle.setField(new TimeInForce(TimeInForce.DAY)); newSingle.setField(new Account("testAccount")); @@ -773,7 +105,7 @@ protected void execute() throws Throwable { @Test public void testMessageDataDictionaryMismatch() throws Exception { final quickfix.fix43.NewOrderSingle newSingle = new quickfix.fix43.NewOrderSingle( - new ClOrdID("123"), new HandlInst(HandlInst.MANUAL_ORDER_BEST_EXECUTION), new Side(Side.BUY), new TransactTime(), new OrdType( + new ClOrdID("123"), new HandlInst(HandlInst.MANUAL_ORDER), new Side(Side.BUY), new TransactTime(), new OrdType( OrdType.LIMIT)); newSingle.setField(new OrderQty(42)); newSingle.setField(new Price(42.37)); @@ -795,49 +127,6 @@ protected void execute() throws Throwable { dd.validate(newSingle, true); } - // QF C++ treats the string argument as a filename although it's - // named 'url'. QFJ string argument can be either but this test - // ensures the DD works correctly with a regular file path. - @Test - public void testDictionaryWithFilename() throws Exception { - DataDictionary dd = new DataDictionary("FIX40.xml"); - assertEquals("wrong field name", "Currency", dd.getFieldName(15)); - // It worked! - } - - // Support finding DD in classpath - @Test - public void testDictionaryInClassPath() throws Exception { - URLClassLoader customClassLoader = new URLClassLoader(new URL[] { new URL("file:etc") }, - getClass().getClassLoader()); - Thread currentThread = Thread.currentThread(); - ClassLoader previousContextClassLoader = currentThread.getContextClassLoader(); - currentThread.setContextClassLoader(customClassLoader); - try { - DataDictionary dd = new DataDictionary("FIX40.xml"); - assertEquals("wrong field name", "Currency", dd.getFieldName(15)); - // It worked! - } finally { - currentThread.setContextClassLoader(previousContextClassLoader); - } - } - - // QFJ-235 - @Test - public void testWildcardEnumValue() throws Exception { - DataDictionary dd = getDictionary(); - assertTrue(dd.isFieldValue(65, "FOO")); - } - - @Test - public void testMessageCategory() throws Exception { - DataDictionary dd = getDictionary(); - assertTrue(dd.isAdminMessage(MsgType.LOGON)); - assertFalse(dd.isAppMessage(MsgType.LOGON)); - assertFalse(dd.isAdminMessage(MsgType.ORDER_SINGLE)); - assertTrue(dd.isAppMessage(MsgType.ORDER_SINGLE)); - } - @Test public void testAllowUnknownFields() throws Exception { final quickfix.fix44.NewOrderSingle newSingle = new quickfix.fix44.NewOrderSingle( @@ -853,7 +142,7 @@ public void testAllowUnknownFields() throws Exception { newSingle.setField(new Price(42.37)); newSingle.setField(new HandlInst()); newSingle.setField(new Symbol("QFJ")); - newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER_BEST_EXECUTION)); + newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER)); newSingle.setField(new TimeInForce(TimeInForce.DAY)); newSingle.setField(new Account("testAccount")); @@ -873,26 +162,6 @@ protected void execute() throws Throwable { dictionary.validate(newSingle); } - // QFJ-535 - @Test - public void testValidateFieldsOutOfOrderForGroups() throws Exception { - final DataDictionary dictionary = new DataDictionary(getDictionary()); - dictionary.setCheckUnorderedGroupFields(false); - Message messageWithGroupLevel1 = new Message( - "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" + - "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" + - "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001736=currency\001661=1\00110=130\001", - dictionary); - dictionary.validate(messageWithGroupLevel1); - - Message messageWithGroupLevel2 = new Message( - "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" + - "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" + - "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001539=1\001524=1\001538=1\001525=a\00110=145\001", - dictionary); - dictionary.validate(messageWithGroupLevel2); - } - // QFJ-535 @Test public void testNewOrderSingleWithCorrectTag50() throws Exception { @@ -1347,7 +616,7 @@ public void testAllowingBlankValuesDisablesFieldValidation() throws Exception { newSingle.setField(new Price(42.37)); newSingle.setField(new HandlInst()); newSingle.setField(new Symbol("QFJ")); - newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER_BEST_EXECUTION)); + newSingle.setField(new HandlInst(HandlInst.MANUAL_ORDER)); newSingle.setField(new TimeInForce(TimeInForce.DAY)); newSingle.setField(new Account("testAccount")); newSingle.setField(new StringField(EffectiveTime.FIELD)); @@ -1379,7 +648,7 @@ public void testConcurrentValidationFailure() throws Exception { Message msg = MessageUtils.parse(messageFactory, dd, msgString); Group partyGroup = msg.getGroups(quickfix.field.NoPartyIDs.FIELD).get(0); char partyIdSource = partyGroup.getChar(PartyIDSource.FIELD); - assertEquals(PartyIDSource.PROPRIETARY_CUSTOM_CODE, partyIdSource); + assertEquals(PartyIDSource.PROPRIETARY, partyIdSource); return msg; }; resultList.add(ptpe.submit(messageParser)); @@ -1398,102 +667,6 @@ public void testConcurrentValidationFailure() throws Exception { } } - @Test - public void shouldLoadDictionaryWhenExternalDTDisEnabled() throws ConfigError { - new DataDictionary("FIX_External_DTD.xml", DocumentBuilderFactory::newInstance); - } - - @Test - public void shouldFailToLoadDictionaryWhenExternalDTDisDisabled() { - try { - new DataDictionary("FIX_External_DTD.xml"); - fail("should fail to load dictionary with external DTD"); - } catch (ConfigError e) { - assertEquals("External DTD: Failed to read external DTD 'mathml.dtd', because 'http' access is not allowed due to restriction set by the accessExternalDTD property.", e.getCause().getCause().getMessage()); - } - } - - /** - * For FIX.Latest a minor version is not required. - */ - @Test - public void testMissingMinorVersion() throws Exception { - String data = ""; - data += ""; - data = getCommonDataDictionaryString(data); - - DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(0, dataDictionary.getMinorVersion()); - } - - @Test - public void testFixLatestMajorVersion() throws Exception { - String data = ""; - data += ""; - data = getCommonDataDictionaryString(data); - - DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(0, dataDictionary.getMinorVersion()); - assertEquals("FIX.Latest", dataDictionary.getFullVersion()); - } - - @Test - public void testFixLatestMajorVersionAndEP() throws Exception { - String data = ""; - data += ""; - data = getCommonDataDictionaryString(data); - - DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(0, dataDictionary.getMinorVersion()); - assertEquals("FIX.Latest_EP260", dataDictionary.getFullVersion()); - } - - @Test - public void testSP() throws Exception { - String data = ""; - data += ""; - data = getCommonDataDictionaryString(data); - - DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(0, dataDictionary.getMinorVersion()); - assertEquals("FIX.5.0", dataDictionary.getVersion()); - assertEquals("FIX.5.0SP2", dataDictionary.getFullVersion()); - } - - @Test - public void testEPAndSP() throws Exception { - String data = ""; - data += ""; - data = getCommonDataDictionaryString(data); - - DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes())); - assertEquals(0, dataDictionary.getMinorVersion()); - assertEquals("FIX.5.0", dataDictionary.getVersion()); - assertEquals("FIX.5.0SP2_EP260", dataDictionary.getFullVersion()); - } - - private String getCommonDataDictionaryString(String data) { - data += "
    "; - data += " "; - data += "
    "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += " "; - data += "
    "; - return data; - } - - // // Group Validation Tests in RepeatingGroupTest // @@ -1529,4 +702,5 @@ public static DataDictionary getDictionary(String fileName) throws Exception { return new DataDictionary(DataDictionaryTest.class.getClassLoader() .getResourceAsStream(fileName)); } + } diff --git a/quickfixj-core/src/test/java/quickfix/DefaultDataDictionaryProviderTest.java b/quickfixj-core/src/test/java/quickfix/DefaultDataDictionaryProviderTest.java index 39051326b9..cbe17aa69b 100644 --- a/quickfixj-core/src/test/java/quickfix/DefaultDataDictionaryProviderTest.java +++ b/quickfixj-core/src/test/java/quickfix/DefaultDataDictionaryProviderTest.java @@ -19,12 +19,14 @@ package quickfix; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.Matchers.equalTo; import static quickfix.field.ApplVerID.*; import org.junit.BeforeClass; @@ -51,7 +53,7 @@ public void returnRegisteredSessonDictionaryWithoutDiscovery() throws Exception DataDictionary dd = provider.getSessionDataDictionary(FixVersions.BEGINSTRING_FIX44); - assertThat(dd, is(dictionaryForTest2)); + assertThat(dd, sameInstance(dictionaryForTest2)); } @Test @@ -61,7 +63,7 @@ public void returnNullSessonDictionaryWithoutDiscovery() throws Exception { DataDictionary dd = provider.getSessionDataDictionary(FixVersions.BEGINSTRING_FIX44); - assertThat(dd, is(nullValue())); + assertThat(dd, nullValue()); } @Test @@ -70,8 +72,8 @@ public void returnSessionDictionaryWithDiscovery() throws Exception { DataDictionary dd = provider.getSessionDataDictionary(FixVersions.BEGINSTRING_FIX40); - assertThat(dd, is(notNullValue())); - assertThat(dd.getVersion(), is(FixVersions.BEGINSTRING_FIX40)); + assertThat(dd, notNullValue()); + assertThat(dd.getVersion(), equalTo(FixVersions.BEGINSTRING_FIX40)); } @Test @@ -93,7 +95,7 @@ public void returnRegisteredAppDictionaryWithoutDiscovery() throws Exception { DataDictionary dd = provider.getApplicationDataDictionary(new ApplVerID(FIX40)); - assertThat(dd, is(dictionaryForTest2)); + assertThat(dd, sameInstance(dictionaryForTest2)); } @Test @@ -103,7 +105,7 @@ public void returnNullAppDictionaryWithoutDiscovery() throws Exception { DataDictionary dd = provider.getApplicationDataDictionary(new ApplVerID(FIX40)); - assertThat(dd, is(nullValue())); + assertThat(dd, nullValue()); } @Test @@ -113,8 +115,8 @@ public void returnAppDictionaryWithDiscovery() throws Exception { DataDictionary dd = provider.getApplicationDataDictionary(new ApplVerID(FIX40)); - assertThat(dd, is(notNullValue())); - assertThat(dd.getVersion(), is(FixVersions.BEGINSTRING_FIX40)); + assertThat(dd, notNullValue()); + assertThat(dd.getVersion(), equalTo(FixVersions.BEGINSTRING_FIX40)); } @Test diff --git a/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java index 1fec31694c..4803934bb6 100644 --- a/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java +++ b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java @@ -41,6 +41,7 @@ public void testMessageCreate() throws Exception { assertMessage(quickfix.fix50.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50, MsgType.ADVERTISEMENT)); assertMessage(quickfix.fix50sp1.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50SP1, MsgType.ADVERTISEMENT)); assertMessage(quickfix.fix50sp2.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50SP2, MsgType.ADVERTISEMENT)); + assertMessage(quickfix.fixlatest.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIXLATEST, MsgType.ADVERTISEMENT)); assertMessage(quickfix.Message.class, MsgType.ADVERTISEMENT, factory.create("unknown", MsgType.ADVERTISEMENT)); } @@ -56,6 +57,7 @@ public void testFixtCreate() throws Exception { assertMessage(quickfix.fix50.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50), MsgType.EMAIL)); assertMessage(quickfix.fix50sp1.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50SP1), MsgType.EMAIL)); assertMessage(quickfix.fix50sp2.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50SP2), MsgType.EMAIL)); + assertMessage(quickfix.fixlatest.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIXLATEST), MsgType.EMAIL)); } @Test @@ -74,6 +76,7 @@ protected void execute() throws Throwable { assertEquals(quickfix.fix50.News.NoLinesOfText.class, factory.create(FixVersions.FIX50, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); assertEquals(quickfix.fix50sp1.News.NoLinesOfText.class, factory.create(FixVersions.FIX50SP1, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); assertEquals(quickfix.fix50sp2.News.NoLinesOfText.class, factory.create(FixVersions.FIX50SP2, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); + assertEquals(quickfix.fixlatest.News.NoLinesOfText.class, factory.create(FixVersions.FIXLATEST, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); assertNull("if group can't be created return null", factory.create(BEGINSTRING_FIX40, MsgType.MARKET_DATA_SNAPSHOT_FULL_REFRESH, NoMDEntries.FIELD)); } @@ -93,7 +96,8 @@ public static Object[][] getParameters() { {ApplVerID.FIX44, quickfix.fix44.Email.class}, {ApplVerID.FIX50, quickfix.fix50.Email.class}, {ApplVerID.FIX50SP1, quickfix.fix50sp1.Email.class}, - {ApplVerID.FIX50SP2, quickfix.fix50sp2.Email.class} + {ApplVerID.FIX50SP2, quickfix.fix50sp2.Email.class}, + {ApplVerID.FIXLATEST, quickfix.fixlatest.Email.class} }; } } diff --git a/quickfixj-core/src/test/java/quickfix/ExceptionTest.java b/quickfixj-core/src/test/java/quickfix/ExceptionTest.java index 7da22e93f5..13572eb49f 100644 --- a/quickfixj-core/src/test/java/quickfix/ExceptionTest.java +++ b/quickfixj-core/src/test/java/quickfix/ExceptionTest.java @@ -23,36 +23,15 @@ public class ExceptionTest extends TestCase { - public void testDoNotSend() { - new DoNotSend(); - } - - public void testIncorrectDataFormat() { - IncorrectDataFormat e = new IncorrectDataFormat(5, "test"); - assertEquals(5, e.getField()); - assertEquals("test", e.getData()); - } - - public void testIncorrectTagValue() { - new IncorrectTagValue(5); - IncorrectTagValue e = new IncorrectTagValue(5, "test"); - } - public void testRejectLogon() { new RejectLogon(); } - - public void testRuntimeError() { - new RuntimeError(); - new RuntimeError("test"); - new RuntimeError(new Exception()); - } - + public void testSessionNotFound() { new SessionNotFound(); new SessionNotFound("test"); } - + public void testSessionException() { new SessionException(); new SessionException("test"); diff --git a/quickfixj-core/src/test/java/quickfix/FieldMapTest.java b/quickfixj-core/src/test/java/quickfix/FieldMapTest.java index f347e685ed..beac5e6eeb 100644 --- a/quickfixj-core/src/test/java/quickfix/FieldMapTest.java +++ b/quickfixj-core/src/test/java/quickfix/FieldMapTest.java @@ -1,21 +1,16 @@ package quickfix; -import org.junit.Test; -import quickfix.field.EffectiveTime; -import quickfix.field.MDEntryTime; -import quickfix.field.converter.UtcTimeOnlyConverter; +import static org.junit.Assert.assertEquals; -import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneOffset; -import java.util.Iterator; -import java.util.Optional; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; +import org.junit.Test; + +import quickfix.field.EffectiveTime; +import quickfix.field.MDEntryTime; +import quickfix.field.converter.UtcTimeOnlyConverter; /** * Tests the {@link FieldMap} class. @@ -69,67 +64,7 @@ public void testSpecificFields() throws Exception { UtcTimeOnlyConverter.convert(map.getField(new MDEntryTime()).getValue(), UtcTimestampPrecision.MILLIS)); } - private void testOrdering(int[] vals, int[] order, int[] expected) { - FieldMap map = new Message(order); - for (int v : vals) - map.setInt(v, v); - Iterator> it = map.iterator(); - for (int e : expected) - assertEquals(String.valueOf(e), it.next().getObject()); - } - - @Test - public void testOrdering() { - testOrdering(new int[]{1, 2, 3}, null, new int[]{1, 2, 3}); - testOrdering(new int[]{3, 2, 1}, null, new int[]{1, 2, 3}); - testOrdering(new int[]{1, 2, 3}, new int[]{1, 2, 3}, new int[]{1, 2, 3}); - testOrdering(new int[]{3, 2, 1}, new int[]{1, 2, 3}, new int[]{1, 2, 3}); - testOrdering(new int[]{1, 2, 3}, new int[]{1, 3, 2}, new int[]{1, 3, 2}); - testOrdering(new int[]{3, 2, 1}, new int[]{1, 3, 2}, new int[]{1, 3, 2}); - testOrdering(new int[]{1, 2, 3}, new int[]{1, 3}, new int[]{1, 3, 2}); - testOrdering(new int[]{3, 2, 1}, new int[]{1, 3}, new int[]{1, 3, 2}); - testOrdering(new int[]{1, 2, 3}, new int[]{3, 1}, new int[]{3, 1, 2}); - testOrdering(new int[]{3, 2, 1}, new int[]{3, 1}, new int[]{3, 1, 2}); - } - - @Test - public void testOptionalString() { - FieldMap map = new Message(); - map.setField(new StringField(128, "bigbank")); - Optional optValue = map.getOptionalString(128); - assertTrue(optValue.isPresent()); - assertEquals("bigbank", optValue.get()); - assertFalse(map.getOptionalString(129).isPresent()); - } - - @Test - public void testOptionalDecimal() { - FieldMap map = new Message(); - map.setField(new DecimalField(44, new BigDecimal("1565.10"))); - Optional optValue = map.getOptionalDecimal(44); - assertTrue(optValue.isPresent()); - assertEquals(0, optValue.get().compareTo(new BigDecimal("1565.10"))); - assertFalse(map.getOptionalDecimal(6).isPresent()); - } - - @Test - public void testNullFieldException() { - FieldMap map = new Message(); - StringField field = new StringField(0, null); - assertThrows(FieldException.class, () -> map.setField(field)); - } - private long epochMilliOfLocalDate(LocalDateTime localDateTime) { return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); } - - @Test - public void testRemoveGroup() { - FieldMap map = new Message(); - Group group = new Group(73, 11); - map.addGroup(group); - assertTrue(map.hasGroup(73)); - map.removeGroup(73); - assertFalse(map.hasGroup(73)); - } } diff --git a/quickfixj-core/src/test/java/quickfix/FieldTest.java b/quickfixj-core/src/test/java/quickfix/FieldTest.java index 06586e41d0..6035fc563b 100644 --- a/quickfixj-core/src/test/java/quickfix/FieldTest.java +++ b/quickfixj-core/src/test/java/quickfix/FieldTest.java @@ -19,8 +19,15 @@ package quickfix; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDateTime; +import java.util.Arrays; + import org.junit.Test; import org.quickfixj.CharsetSupport; + import quickfix.field.ClOrdID; import quickfix.field.ExecInst; import quickfix.field.MDUpdateAction; @@ -32,18 +39,6 @@ import quickfix.fix50.MarketDataIncrementalRefresh; import quickfix.fix50.NewOrderSingle; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Arrays; -import java.util.Date; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - public class FieldTest { @Test @@ -57,185 +52,6 @@ public void testMessageSetGetString() { assertEquals(side1, side2); } - private void testFieldCalcuations(String value, int checksum, int length) { - Field field = new Field<>(12, value); - field.setObject(value); - assertEquals("12=" + value, field.toString()); - assertEquals(checksum, field.getChecksum()); - assertEquals(length, field.getLength()); - - value = value.substring(0, value.length() - 1) + (char)(value.charAt(value.length() - 1) + 1); - checksum = (checksum + 1) & 0xFF; - field.setObject(value); - assertEquals("12=" + value, field.toString()); - assertEquals(checksum, field.getChecksum()); - assertEquals(length, field.getLength()); - - field.setTag(13); - checksum = (checksum + 1) & 0xFF; - assertEquals("13=" + value, field.toString()); - assertEquals(checksum, field.getChecksum()); - assertEquals(length, field.getLength()); - } - - @Test - public void testFieldCalculationsWithDefaultCharset() { - testFieldCalcuations("VALUE", 30, 9); - } - - @Test - public void testFieldCalculationsWithUTF8Charset() throws UnsupportedEncodingException { - CharsetSupport.setCharset("UTF-8"); - try { - testFieldCalcuations("\u6D4B\u9A8C\u6570\u636E", 50, 16); - } finally { - CharsetSupport.setCharset(CharsetSupport.getDefaultCharset()); - } - } - - @Test - public void testDateField() { - DateField field = new DateField(11); - Date date = new Date(); - field.setValue(date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - field = new DateField(11, date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - } - - @Test - public void testUtcDateOnlyField() { - UtcDateOnlyField field = new UtcDateOnlyField(11); - LocalDate date = LocalDate.now(); - field.setValue(date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - field = new UtcDateOnlyField(11, date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - } - - @Test - public void testUtcTimeOnlyField() { - UtcTimeOnlyField field = new UtcTimeOnlyField(11); - LocalTime date = LocalTime.now(); - field.setValue(date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - field = new UtcTimeOnlyField(11, date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - } - - @Test - public void testUtcTimeStampField() { - UtcTimeStampField field = new UtcTimeStampField(11); - LocalDateTime date = LocalDateTime.now(); - field.setValue(date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - field = new UtcTimeStampField(11, date); - assertEquals(11, field.getTag()); - assertEquals(date, field.getValue()); - } - - @Test - public void testBooleanField() { - BooleanField field = new BooleanField(11); - field.setValue(true); - assertEquals(11, field.getTag()); - assertTrue(field.getValue()); - field.setValue(Boolean.FALSE); - assertEquals(11, field.getTag()); - assertFalse(field.getValue()); - field = new BooleanField(22, true); - assertEquals(22, field.getTag()); - assertTrue(field.getValue()); - field = new BooleanField(33, Boolean.TRUE); - assertEquals(33, field.getTag()); - assertTrue(field.getValue()); - } - - @Test - public void testDoubleField() { - DoubleField field = new DoubleField(11); - field.setValue(12.3); - assertEquals(11, field.getTag()); - assertEquals(12.3, field.getValue(), 0); - field.setValue(new Double(23.4)); - assertEquals(11, field.getTag()); - assertEquals(23.4, field.getValue(), 0); - field = new DoubleField(22, 34.5); - assertEquals(22, field.getTag()); - assertEquals(34.5, field.getValue(), 0); - field = new DoubleField(33, new Double(45.6)); - assertEquals(33, field.getTag()); - assertEquals(45.6, field.getValue(), 0); - } - - @Test(expected = NumberFormatException.class) - public void testDoubleFieldException() { - DoubleField field = new DoubleField(11, Double.NaN); - } - - @Test - public void testDecimalField() { - DecimalField field = new DecimalField(11); - field.setValue(12.3); - assertEquals(11, field.getTag()); - assertEquals(BigDecimal.valueOf(12.3), field.getValue()); - field.setValue(23.4); - assertEquals(11, field.getTag()); - assertEquals(BigDecimal.valueOf(23.4), field.getValue()); - field = new DecimalField(22, 34.5); - assertEquals(22, field.getTag()); - assertEquals(BigDecimal.valueOf(34.5), field.getValue()); - field = new DecimalField(33, 45.6); - assertEquals(33, field.getTag()); - assertEquals(BigDecimal.valueOf(45.6), field.getValue()); - } - - @Test(expected = NumberFormatException.class) - public void testDecimalFieldException() { - DecimalField field = new DecimalField(11, Double.POSITIVE_INFINITY); - } - - @Test - public void testCharField() { - CharField field = new CharField(11); - field.setValue('x'); - assertEquals(11, field.getTag()); - assertEquals('x', field.getValue()); - field.setValue(Character.valueOf('X')); - assertEquals(11, field.getTag()); - assertEquals('X', field.getValue()); - field = new CharField(22, 'a'); - assertEquals(22, field.getTag()); - assertEquals('a', field.getValue()); - field = new CharField(33, Character.valueOf('A')); - assertEquals(33, field.getTag()); - assertEquals('A', field.getValue()); - } - - @Test - public void testIntField() { - IntField field = new IntField(11); - field.setValue(12); - assertEquals(11, field.getTag()); - assertEquals(12, field.getValue()); - field.setValue(Integer.valueOf(23)); - assertEquals(11, field.getTag()); - assertEquals(23, field.getValue()); - field = new IntField(22, 23); - assertEquals(22, field.getTag()); - assertEquals(23, field.getValue()); - field = new IntField(33, Integer.valueOf(44)); - assertEquals(33, field.getTag()); - assertEquals(44, field.getValue()); - } - @Test public void testBytesField() { byte[] data = "rawdata".getBytes(); @@ -269,19 +85,6 @@ public void testBytesFieldFullRange() { } } - @Test - public void testFieldhashCode() throws Exception { - assertEqualsAndHash(new IntField(11, 100), new IntField(11, 100)); - assertEqualsAndHash(new DoubleField(11, 100.0), new DoubleField(11, 100.0)); - assertEqualsAndHash(new StringField(11, "foo"), new StringField(11, "foo")); - assertEqualsAndHash(new BooleanField(11, true), new BooleanField(11, true)); - assertEqualsAndHash(new CharField(11, 'x'), new CharField(11, 'x')); - LocalDateTime date = LocalDateTime.now(); - assertEqualsAndHash(new UtcDateOnlyField(11, date.toLocalDate()), new UtcDateOnlyField(11, date.toLocalDate())); - assertEqualsAndHash(new UtcTimeOnlyField(11, date.toLocalTime()), new UtcTimeOnlyField(11, date.toLocalTime())); - assertEqualsAndHash(new UtcTimeStampField(11, date), new UtcTimeStampField(11, date)); - } - // QFJ-881 @Test public void testMultipleStringValue() throws Exception { diff --git a/quickfixj-core/src/test/java/quickfix/MessageComponentTestFixLatest.java b/quickfixj-core/src/test/java/quickfix/MessageComponentTestFixLatest.java new file mode 100644 index 0000000000..b2ce954ab1 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/MessageComponentTestFixLatest.java @@ -0,0 +1,162 @@ +package quickfix; + +import org.junit.Test; +import quickfix.field.AgreementCurrency; +import quickfix.field.Product; +import quickfix.field.SecurityType; +import quickfix.field.Symbol; +import quickfix.fixlatest.QuoteRequest; +import quickfix.fixlatest.component.FinancingDetails; +import quickfix.fixlatest.component.Instrument; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class MessageComponentTestFixLatest { + + @Test + public void shouldCopyCustomTagsToComponent() throws FieldNotFound { + Instrument instrument1 = new Instrument(); + instrument1.set(new Symbol("EURUSD")); + instrument1.set(new Product(Product.CURRENCY)); + instrument1.set(new SecurityType(SecurityType.FOREIGN_EXCHANGE_CONTRACT)); + instrument1.setString(12345, "ABC"); + instrument1.setInt(54321, 0xCAFE); + + assertEquals("EURUSD", instrument1.getSymbol().getValue()); + assertEquals(Product.CURRENCY, instrument1.getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, instrument1.getSecurityType().getValue()); + assertEquals("ABC", instrument1.getString(12345)); + assertEquals(0xCAFE, instrument1.getInt(54321)); + + Instrument instrument2 = new Instrument(); + instrument1.copyTo(instrument2); + + assertEquals("EURUSD", instrument2.getSymbol().getValue()); + assertEquals(Product.CURRENCY, instrument2.getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, instrument2.getSecurityType().getValue()); + + assertEquals("ABC", instrument2.getString(12345)); + assertEquals(0xCAFE, instrument2.getInt(54321)); + } + + @Test + public void shouldNotCopyCustomTagsFromComponent() throws FieldNotFound { + Instrument instrument1 = new Instrument(); + instrument1.set(new Symbol("EURUSD")); + instrument1.set(new Product(Product.CURRENCY)); + instrument1.set(new SecurityType(SecurityType.FOREIGN_EXCHANGE_CONTRACT)); + instrument1.setString(12345, "ABC"); + instrument1.setInt(54321, 0xCAFE); + + assertEquals("EURUSD", instrument1.getSymbol().getValue()); + assertEquals(Product.CURRENCY, instrument1.getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, instrument1.getSecurityType().getValue()); + assertEquals("ABC", instrument1.getString(12345)); + assertEquals(0xCAFE, instrument1.getInt(54321)); + + Instrument instrument2 = new Instrument(); + instrument2.copyFrom(instrument1); + + assertEquals("EURUSD", instrument2.getSymbol().getValue()); + assertEquals(Product.CURRENCY, instrument2.getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, instrument2.getSecurityType().getValue()); + + assertFalse(instrument2.isSetField(12345)); + assertFalse(instrument2.isSetField(54321)); + } + + @Test + public void shouldSetComponentWithCustomTags() throws FieldNotFound { + Instrument instrument = new Instrument(); + instrument.set(new Symbol("EURUSD")); + instrument.set(new Product(Product.CURRENCY)); + instrument.set(new SecurityType(SecurityType.FOREIGN_EXCHANGE_CONTRACT)); + instrument.setString(12345, "ABC"); + instrument.setInt(54321, 0xCAFE); + + FinancingDetails financingDetails = new FinancingDetails(); + financingDetails.set(new AgreementCurrency("USD")); + financingDetails.setString(111222, "DEF"); + + QuoteRequest.NoRelatedSym noRelatedSym = new QuoteRequest.NoRelatedSym(); + noRelatedSym.set(instrument); + noRelatedSym.set(financingDetails); + + assertEquals("EURUSD", noRelatedSym.getInstrumentComponent().getSymbol().getValue()); + assertEquals(Product.CURRENCY, noRelatedSym.getInstrumentComponent().getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, noRelatedSym.getInstrumentComponent().getSecurityType().getValue()); + assertEquals("ABC", noRelatedSym.getString(12345)); + assertEquals(0xCAFE, noRelatedSym.getInt(54321)); + + assertEquals("USD", noRelatedSym.getFinancingDetailsComponent().getAgreementCurrency().getValue()); + assertEquals("DEF", noRelatedSym.getString(111222)); + } + + @Test + public void shouldOverrideCustomComponentTags() throws FieldNotFound { + Instrument instrument = new Instrument(); + instrument.set(new Symbol("EURUSD")); + instrument.set(new Product(Product.CURRENCY)); + instrument.set(new SecurityType(SecurityType.FOREIGN_EXCHANGE_CONTRACT)); + instrument.setString(12345, "ABC"); + instrument.setInt(54321, 0xCAFE); + + FinancingDetails financingDetails = new FinancingDetails(); + financingDetails.set(new AgreementCurrency("USD")); + financingDetails.setString(111222, "DEF"); + + QuoteRequest.NoRelatedSym noRelatedSym = new QuoteRequest.NoRelatedSym(); + noRelatedSym.set(instrument); + noRelatedSym.set(financingDetails); + + instrument.set(new Symbol("USDCAD")); + instrument.setString(12345, "XYZ"); + noRelatedSym.set(instrument); + + financingDetails.set(new AgreementCurrency("CAD")); + financingDetails.setString(111222, "GHI"); + financingDetails.setInt(54321, 0xBABE); + noRelatedSym.set(financingDetails); + + assertEquals("USDCAD", noRelatedSym.getInstrumentComponent().getSymbol().getValue()); + assertEquals(Product.CURRENCY, noRelatedSym.getInstrumentComponent().getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, noRelatedSym.getInstrumentComponent().getSecurityType().getValue()); + assertEquals("XYZ", noRelatedSym.getString(12345)); + assertEquals(0xBABE, noRelatedSym.getInt(54321)); + + assertEquals("CAD", noRelatedSym.getFinancingDetailsComponent().getAgreementCurrency().getValue()); + assertEquals("GHI", noRelatedSym.getString(111222)); + } + + @Test + public void shouldNotGetComponentWithCustomTags() throws FieldNotFound { + Instrument instrument = new Instrument(); + instrument.set(new Symbol("EURUSD")); + instrument.set(new Product(Product.CURRENCY)); + instrument.set(new SecurityType(SecurityType.FOREIGN_EXCHANGE_CONTRACT)); + instrument.setString(12345, "ABC"); + instrument.setInt(54321, 0xCAFE); + + FinancingDetails financingDetails = new FinancingDetails(); + financingDetails.set(new AgreementCurrency("USD")); + financingDetails.setString(111222, "DEF"); + + QuoteRequest.NoRelatedSym noRelatedSym = new QuoteRequest.NoRelatedSym(); + noRelatedSym.set(instrument); + noRelatedSym.set(financingDetails); + + instrument = noRelatedSym.getInstrumentComponent(); + + assertEquals("EURUSD", instrument.getSymbol().getValue()); + assertEquals(Product.CURRENCY, instrument.getProduct().getValue()); + assertEquals(SecurityType.FOREIGN_EXCHANGE_CONTRACT, instrument.getSecurityType().getValue()); + assertFalse(instrument.isSetField(12345)); + assertFalse(instrument.isSetField(54321)); + + financingDetails = noRelatedSym.getFinancingDetailsComponent(); + + assertEquals("USD", financingDetails.getAgreementCurrency().getValue()); + assertFalse(instrument.isSetField(111222)); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/MessageCrackerTest.java b/quickfixj-core/src/test/java/quickfix/MessageCrackerTest.java index 1599ec1f8e..8b56101361 100644 --- a/quickfixj-core/src/test/java/quickfix/MessageCrackerTest.java +++ b/quickfixj-core/src/test/java/quickfix/MessageCrackerTest.java @@ -226,6 +226,23 @@ public void onMessage(quickfix.fix44.Email email, SessionID sessionID) { assertTrue(messageCracked > 0); } + + @Test + public void testFixtMessageCrackingWithFixLatestApplVerID() throws Exception { + quickfix.fixlatest.Email message = createFixLatestEmail(); + message.getHeader().setString(ApplVerID.FIELD, ApplVerID.FIXLATEST); + + MessageCracker cracker = new MessageCracker() { + @SuppressWarnings("unused") + public void onMessage(quickfix.fixlatest.Email email, SessionID sessionID) { + messageCracked++; + } + }; + + cracker.crack(message, new SessionID(FixVersions.BEGINSTRING_FIXT11, "SENDER", "TARGET")); + + assertTrue(messageCracked > 0); + } @Test public void testFixtMessageCrackingWithSessionDefaultApplVerID() throws Exception { @@ -275,4 +292,12 @@ private quickfix.fix44.Email createFix44Email() { message.getHeader().setString(TargetCompID.FIELD, "TARGET"); return message; } + + private quickfix.fixlatest.Email createFixLatestEmail() { + quickfix.fixlatest.Email message = new quickfix.fixlatest.Email(); + message.getHeader().setString(BeginString.FIELD, FixVersions.BEGINSTRING_FIXT11); + message.getHeader().setString(SenderCompID.FIELD, "SENDER"); + message.getHeader().setString(TargetCompID.FIELD, "TARGET"); + return message; + } } diff --git a/quickfixj-core/src/test/java/quickfix/MessageTest.java b/quickfixj-core/src/test/java/quickfix/MessageTest.java index e6fca46f20..0d3613cae3 100644 --- a/quickfixj-core/src/test/java/quickfix/MessageTest.java +++ b/quickfixj-core/src/test/java/quickfix/MessageTest.java @@ -19,15 +19,28 @@ package quickfix; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import javax.xml.parsers.DocumentBuilderFactory; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.quickfixj.CharsetSupport; + import quickfix.field.Account; import quickfix.field.AllocAccount; import quickfix.field.AllocShares; -import quickfix.field.ApplExtID; -import quickfix.field.ApplVerID; import quickfix.field.AvgPx; import quickfix.field.BeginString; import quickfix.field.BidType; @@ -38,7 +51,6 @@ import quickfix.field.CrossID; import quickfix.field.CrossPrioritization; import quickfix.field.CrossType; -import quickfix.field.CstmApplVerID; import quickfix.field.CumQty; import quickfix.field.EncodedText; import quickfix.field.EncodedTextLen; @@ -80,7 +92,6 @@ import quickfix.field.RawData; import quickfix.field.RawDataLength; import quickfix.field.RefMsgType; -import quickfix.field.SecureData; import quickfix.field.SecurityID; import quickfix.field.SecurityIDSource; import quickfix.field.SecurityReqID; @@ -96,7 +107,6 @@ import quickfix.field.StrikePrice; import quickfix.field.Symbol; import quickfix.field.TargetCompID; -import quickfix.field.TargetSubID; import quickfix.field.Text; import quickfix.field.TotNoOrders; import quickfix.field.TradeDate; @@ -119,166 +129,27 @@ import quickfix.fix44.component.Parties; import quickfix.fix50.MarketDataSnapshotFullRefresh; -import javax.xml.parsers.DocumentBuilderFactory; -import java.math.BigDecimal; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Calendar; -import java.util.TimeZone; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - public class MessageTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - @Test - public void testRepeatingField() throws Exception { - final Message m = new Message( - "8=FIX.4.0\0019=100\00135=D\00134=2\00149=TW\00156=ISLD\00111=ID\00121=1\001" - + "40=1\00154=1\00140=2\00138=200\00155=INTC\00110=160\001"); - assertFalse("message should be invalid", m.hasValidStructure()); - assertEquals("wrong invalid tag", 40, m.getInvalidTag()); - } - @Test public void testTrailerFieldOrdering() throws Exception { final NewOrderSingle order = createNewOrderSingle(); - + order.getTrailer().setField(new Signature("FOO")); order.getTrailer().setField(new SignatureLength(3)); - + assertTrue(order.toString().contains("93=3\00189=FOO\001")); } - + private NewOrderSingle createNewOrderSingle() { return new NewOrderSingle(new ClOrdID("CLIENT"), new HandlInst( - HandlInst.AUTOMATED_EXECUTION_ORDER_PUBLIC_BROKER_INTERVENTION_OK), new Symbol("ORCL"), + HandlInst.AUTOMATED_EXECUTION_INTERVENTION_OK), new Symbol("ORCL"), new Side(Side.BUY), new TransactTime(LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC)), new OrdType(OrdType.LIMIT)); } - - @Test - public void testHeaderCustomFieldOrdering() throws Exception { - - class MyMessage extends Message { - - final int[] headerFieldOrder = { - BeginString.FIELD, - BodyLength.FIELD, - MsgType.FIELD, - TargetSubID.FIELD, - SendingTime.FIELD, - MsgSeqNum.FIELD, - SenderCompID.FIELD, - TargetCompID.FIELD - }; - - public MyMessage() { - super(); - header = new Header(headerFieldOrder); - } - } - - final MyMessage myMessage = new MyMessage(); - - myMessage.getHeader().setField(new SenderCompID("foo")); - myMessage.getHeader().setField(new MsgSeqNum(22)); - myMessage.getHeader().setString(SendingTime.FIELD, "20120922-11:00:00"); - myMessage.getHeader().setField(new TargetCompID("bar")); - - assertTrue(myMessage.toString().contains("52=20120922-11:00:00\00134=22\00149=foo\00156=bar")); - } - - - @Test - public void testHeaderFieldWithCustomTransportDictionaryConstructorReadsHeaderField() throws Exception { - - final DataDictionary customSessionDictionary = new DataDictionary("FIXT11_Custom_Test.xml"); - customSessionDictionary.setAllowUnknownMessageFields(false); - - final DataDictionary standardSessionDictionary = new DataDictionary("FIXT11.xml"); - standardSessionDictionary.setAllowUnknownMessageFields(false); - - final DataDictionary applicationDictionary = new DataDictionary("FIX50.xml"); - applicationDictionary.setAllowUnknownMessageFields(false); - - final String sep = "\001"; - final StringBuilder sb = new StringBuilder(); - sb.append("8=FIXT1.1"); - sb.append(sep); - sb.append("9=112"); - sb.append(sep); - sb.append("35=6"); - sb.append(sep); - sb.append("49=SENDER_COMP_ID"); - sb.append(sep); - sb.append("56=TARGET_COMP_ID"); - sb.append(sep); - sb.append("34=20"); - sb.append(sep); - sb.append("52=20120922-11:00:00"); - sb.append(sep); - sb.append("12312=foo"); - sb.append(sep); - sb.append("23=123456"); - sb.append(sep); - sb.append("28=N"); - sb.append(sep); - sb.append("55=[N/A]"); - sb.append(sep); - sb.append("54=1"); - sb.append(sep); - sb.append("27=U"); - sb.append(sep); - sb.append("10=52"); - sb.append(sep); - final String messageData = sb.toString(); - - final Message standardMessage = new Message(messageData, standardSessionDictionary, applicationDictionary, true); - - // Test that field is in body not the header - assertTrue(standardMessage.toString().contains("12312=foo")); - assertFalse(standardMessage.getHeader().isSetField(12312)); - assertTrue(standardMessage.isSetField(12312)); - assertEquals("foo", standardMessage.getString(12312)); - - // Test that field is correctly classified in header with customSessionDictionary - final Message customMessage = new Message(messageData, customSessionDictionary, applicationDictionary, true); - assertTrue(customMessage.toString().contains("12312=foo")); - assertTrue(customMessage.getHeader().isSetField(12312)); - assertEquals("foo", customMessage.getHeader().getString(12312)); - assertFalse(customMessage.isSetField(12312)); - } - - @Test - public void testTrailerCustomFieldOrdering() throws Exception { - - class MyMessage extends Message { - - final int[] trailerFieldOrder = {Signature.FIELD, SignatureLength.FIELD, CheckSum.FIELD}; - - public MyMessage() { - super(); - trailer = new Trailer(trailerFieldOrder); - } - } - - final MyMessage myMessage = new MyMessage(); - - myMessage.getTrailer().setField(new Signature("FOO")); - myMessage.getTrailer().setField(new SignatureLength(3)); - assertTrue(myMessage.toString().contains("89=FOO\00193=3\001")); - } - + @Test public void testHeaderGroupParsing() throws Exception { final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001" @@ -297,7 +168,7 @@ public void testHeaderGroupParsing() throws Exception { public void testEmbeddedMessage() throws Exception { final ExecutionReport report = new ExecutionReport(new OrderID("ORDER"), - new ExecID("EXEC"), new ExecType(ExecType.FILL), new OrdStatus(OrdStatus.FILLED), + new ExecID("EXEC"), new ExecType(ExecType.TRADE), new OrdStatus(OrdStatus.FILLED), new Side(Side.BUY), new LeavesQty(100), new CumQty(100), new AvgPx(50)); final NewOrderSingle order = createNewOrderSingle(); @@ -387,29 +258,6 @@ public void testParsing2() throws Exception { assertEquals("wrong value", "CAD", valueMessageType.getString(UnderlyingCurrency.FIELD)); } - @Test - public void testParseEmptyString() throws Exception { - final String data = ""; - - // with validation - try { - new Message(data, DataDictionaryTest.getDictionary()); - } catch (final InvalidMessage im) { - } catch (final Throwable e) { - e.printStackTrace(); - fail("InvalidMessage expected, got " + e.getClass().getName()); - } - - // without validation - try { - new Message(data, DataDictionaryTest.getDictionary(), false); - } catch (final InvalidMessage im) { - } catch (final Throwable e) { - e.printStackTrace(); - fail("InvalidMessage expected, got " + e.getClass().getName()); - } - } - @Test public void testValidation() throws Exception { final String data = "8=FIX.4.4\0019=309\00135=8\00149=ASX\00156=CL1_FIX44\00134=4\001" + @@ -486,6 +334,20 @@ public void testAppMessageValidation() throws Exception { mdsfr.fromString(data, sessDictionary, appDictionary, true); DataDictionary.validate(mdsfr, sessDictionary, appDictionary); } + + @Test + public void testAppMessageValidationFixLatest() throws Exception { + final String data = "8=FIXT.1.1\0019=234\00135=W\00134=2\00149=ABFX\00152=20080722-16:37:11.234\001" + + "56=X2RV1\00155=EUR/USD\001262=CAP0000011\001779=20080722-16:37:11.234\001268=2\001269=0\001270=1.57844\00115=EUR\001" + + "271=500000\001272=20080724\001269=1\001270=1.57869\00115=EUR\001271=500000\001272=20080724\00110=118\001"; + final quickfix.fixlatest.MarketDataSnapshotFullRefresh mdsfr = new quickfix.fixlatest.MarketDataSnapshotFullRefresh(); + final DataDictionary sessDictionary = DataDictionaryTest.getDictionary("FIXT11.xml"); + final DataDictionary appDictionary = DataDictionaryTest.getDictionary("FIXLatest.xml"); + assertNotNull(sessDictionary); + assertNotNull(appDictionary); + mdsfr.fromString(data, sessDictionary, appDictionary, true); + DataDictionary.validate(mdsfr, sessDictionary, appDictionary); + } @Test public void testAdminMessageValidation() throws Exception { @@ -507,7 +369,7 @@ public void testGroupDelimOrdering() throws Exception { final Group partyGroup = new Group(quickfix.field.NoPartyIDs.FIELD, PartyID.FIELD); partyGroup.setField(new PartyID("TraderName")); partyGroup.setField(new PartyIDSource( - PartyIDSource.GENERALLY_ACCEPTED_MARKET_PARTICIPANT_IDENTIFIER)); + PartyIDSource.GENERAL_IDENTIFIER)); partyGroup.setField(new PartyRole(11)); order.addGroup(partyGroup); final String data = order.toString(); @@ -542,15 +404,6 @@ public void testComponentGroupInsertion() throws Exception { assertEquals("wrong # of party IDs", 2, order.getNoPartyIDs().getValue()); } - // QFJ-66 Should not throw exception when parsing data field in header - @Test - public void testHeaderDataField() throws Exception { - final Message m = new Message("8=FIX.4.2\0019=53\00135=A\00190=4\00191=ABCD\001" - + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00110=241\001", - DataDictionaryTest.getDictionary()); - assertEquals("ABCD", m.getHeader().getString(SecureData.FIELD)); - } - // QFJ-52 @Test public void testInvalidFirstFieldInGroup() throws Exception { @@ -583,40 +436,6 @@ public void testRequiredGroupValidation() throws Exception { } } - /** - * Test for data fields with SOH. This test is based on report from a user on - * the QuickFIX mailing list. The problem was the user's configuration but this - * seems like a good unit test to keep in the suite. - */ - @Test - public void testDataFieldParsing() throws Exception { - final String data = "10001=Canonical.1.00\00110002=001058\00125001=01\00110003=SAPI_ADMRESP\00110004=SUBSCRIBE_RESP\001" - + "10009=705\00110012=01\00110005=SPGW\00110006=SAPI\00110007=0\00110010=16:25:11.537\001" - + "10045=SDQADL:01:/SDB/ENT/@/@/STKSDLL:7\00110955=Y\00110963=043\00110961=03\00111285=N\001" - + "11339=823,980\00110919=N\00111111=86795696\00110898=043\00110920=~\00110938=N\00111340=5- 9.99\001" - + "11343=0.20\00111344=~\00111341=~\00111342=0.15\00111345=10- 14.99\00111348=0.25\00111349=~\00111346=~\001" - + "11347=0.15\00111350=15- 19.99\00111353=0.30\00111354=~\00111351=~\00111352=0.20\00111338=23SEP05\001" - + "10981=0\00110485=N\00110761=0\00111220=~\00111224=N\00110808=N\00110921=~\00110960=N\00110957=N\00111329=N\001" - + "11286=0\00111214=USA\00110917=Y\00111288=0\00110906=N\00110737=0.01\00110956=~\00110967=~\00110965=~\00110809=0\001" - + "10762=N\00110763=N\00110712=1\00110905=09:30:00\00110918=YA0101\00110951=Y\00110469=1\00110949=1\00110487=Q\00110950=Y\001" - + "10899=N\00110380=N\00110696=03\00111082=18.41\00110217=12\00110954=N\00110708=E\00110958=N\00111213=US \00111334=N\001" - + "11332=N\00111331=N\00111330=N\00111335=N\00111333=N\00110767=3\00110974=~\00110980=AIRTRAN HOLDINGS \00111289=N\001" - + "10912=4\00110915=0501\00110914=0501\00110975=N\00110913=SLK\00110698=055\00110666=AAI\00110903=S\00111328=N\001" - + "10624=L\00111287=0\00110699=0\00110962=L\00111227=SUB1\00111229=5\00111228=1\00111236=16:24:41.521\00111277=16:25:11.630\001"; - - try { - final DataDictionary dictionary = DataDictionaryTest.getDictionary(); - final Message m = new Message(("8=FIX.4.4\0019=1144\00135=A\001" - + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00195=1092\001" + "96=" - + data + "\00110=5\001"), dictionary); - assertEquals(1144, m.bodyLength()); - final Message m2 = new Message(m.toString(), dictionary); - assertEquals(1144, m2.bodyLength()); - } catch (final InvalidMessage e) { - fail(e.getMessage()); - } - } - /** * Test for data fields with SOH. This test is based on report from a user on * the QuickFIX mailing list. The problem was the user's configuration but this @@ -654,17 +473,6 @@ public void testDataFieldWithManualFieldInsertion() throws Exception { } } - @Test - public void testFix5HeaderFields() { - assertTrue(Message.isHeaderField(ApplVerID.FIELD)); - assertTrue(Message.isHeaderField(CstmApplVerID.FIELD)); - } - - @Test - public void testApplExtIDIsHeaderField() { - assertTrue(Message.isHeaderField(ApplExtID.FIELD)); - } - @Test public void testCalculateStringWithNestedGroups() throws Exception { final NewOrderCross noc = new NewOrderCross(); @@ -682,7 +490,7 @@ public void testCalculateStringWithNestedGroups() throws Exception { noc.setString(TransactTime.FIELD, "20060319-09:08:19"); noc.setString(CrossID.FIELD, "184214"); noc.setInt(CrossType.FIELD, - CrossType.CROSS_IOC_CROSS_TRADE_WHICH_IS_EXECUTED_PARTIALLY_AND_THE_REST_IS_CANCELLED_ONE_SIDE_IS_FULLY_EXECUTED_THE_OTHER_SIDE_IS_PARTIALLY_EXECUTED_WITH_THE_REMAINDER_BEING_CANCELLED_THIS_IS_EQUIVALENT_TO_AN_IOC_ON_THE_OTHER_SIDE_NOTE_CROSSPRIORITIZATION_FIELD_MAY_BE_USED_TO_INDICATE_WHICH_SIDE_SHOULD_FULLY_EXECUTE_IN_THIS_SCENARIO_); + CrossType.CROSS_IOC); noc.setInt(CrossPrioritization.FIELD, CrossPrioritization.NONE); final NewOrderCross.NoSides side = new NewOrderCross.NoSides(); @@ -691,13 +499,13 @@ public void testCalculateStringWithNestedGroups() throws Exception { final NewOrderCross.NoSides.NoPartyIDs party = new NewOrderCross.NoSides.NoPartyIDs(); party.setString(PartyID.FIELD, "8"); - party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY_CUSTOM_CODE); + party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY); party.setInt(PartyRole.FIELD, PartyRole.CLEARING_FIRM); side.addGroup(party); party.setString(PartyID.FIELD, "AAA35777"); - party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY_CUSTOM_CODE); + party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY); party.setInt(PartyRole.FIELD, PartyRole.CLIENT_ID); side.addGroup(party); @@ -710,13 +518,13 @@ public void testCalculateStringWithNestedGroups() throws Exception { party.clear(); party.setString(PartyID.FIELD, "8"); - party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY_CUSTOM_CODE); + party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY); party.setInt(PartyRole.FIELD, PartyRole.CLEARING_FIRM); side.addGroup(party); party.clear(); party.setString(PartyID.FIELD, "aaa"); - party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY_CUSTOM_CODE); + party.setChar(PartyIDSource.FIELD, PartyIDSource.PROPRIETARY); party.setInt(PartyRole.FIELD, PartyRole.CLIENT_ID); side.addGroup(party); @@ -744,63 +552,6 @@ public void testFieldOrdering() throws Exception { actualMessageString.contains("453=2\001448=8\001447=D\001452=4\001448=AAA35354\001447=D\001452=3")); } - @Test - public void testHeaderFieldsMissing() throws Exception { - try { - new Message("1=FIX.4.2"); - } catch (final InvalidMessage e) { - // expected - } - } - - @Test - public void testHeaderFieldInBody() throws Exception { - final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001" - + "98=0\001212=4\001384=2\001372=D\001385=R\001372=8\001385=S\00110=103\001", - DataDictionaryTest.getDictionary()); - - assertFalse(message.hasValidStructure()); - - assertTrue(message.getHeader().isSetField(212)); - - assertEquals(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER, message - .getException().getSessionRejectReason()); - assertEquals(212, message.getException().getField()); - } - - @Test - public void testTrailerFieldInBody() throws Exception { - final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001" - + "98=0\00193=5\001384=2\001372=D\001385=R\001372=8\001385=S\00110=63\001", - DataDictionaryTest.getDictionary()); - - assertFalse(message.hasValidStructure()); - - final SignatureLength signatureLength = new SignatureLength(); - message.getTrailer().getField(signatureLength); - assertEquals(5, signatureLength.getValue()); - } - - @Test - public void testMessageFromString() { - Message message = null; - - boolean badMessage = false; - try { - message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=036\001"); - } catch (final InvalidMessage e) { - badMessage = true; - } - assertTrue("Message should be invalid", badMessage); - - try { - message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"); - } catch (final InvalidMessage e) { - fail("Message should be valid (" + e.getMessage() + ")"); - } - assertEquals("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001", message.toString()); - } - @Test public void testMessageGroups() { final Message message = new Message(); @@ -809,48 +560,6 @@ public void testMessageGroups() { assertGroupContent(message, numAllocs); } - // Includes test for QFJ-413. Repeating group check for size = 0 - @Test - public void testMessageGroupCountValidation() throws Exception { - final String data = "8=FIX.4.4\0019=222\00135=D\00149=SenderCompId\00156=TargetCompId\00134=37\001" + - "52=20070223-22:28:33\00111=183339\00122=8\00138=1\00140=2\00144=12\00148=BHP\00154=2\001" + - "55=BHP\00159=1\00160=20060223-22:38:33\001526=3620\00178=0\00179=AllocACC1\00180=1010.1\001" + - "79=AllocACC2\00180=2020.2\001453=2\001448=8\001447=D\001452=4\001448=AAA35354\001447=D\001452=3\00110=079\001"; - final Message message = new Message(); - final DataDictionary dd = DataDictionaryTest.getDictionary(); - message.fromString(data, dd, true); - try { - dd.validate(message); - fail("No exception thrown"); - } catch (final FieldException e) { - final String emsg = e.getMessage(); - assertNotNull("No exception message", emsg); - assertTrue(emsg.startsWith("Incorrect NumInGroup")); - } - } - - /** - * QFJ-760 - */ - @Test - public void testMessageWithMissingChecksumField() throws Exception { - // checksum is "merged" into field 452, i.e. SOH is missing between field 452 and 10 - String badMessage = "8=FIX.4.4\0019=275\00135=D\00134=3\00149=441000-XXXXX-X-XXXX-001\001" + - "52=20131113-10:22:31.567\00156=XXXXX\0011=A1\00111=9fef3663330e209e1bce\00118=H\001" + - "22=4\00138=200\00140=M\00148=XX0005519XXXX\00154=1\00155=[N/A]\00158=MassTest\00159=0\001" + - "60=20131113-10:22:31.567\001100=XXXX\001526=9fef3663330e209e1bce\001453=1\001" + - "448=XXXXXXXX030\001447=D\001452=3610=016\001"; - - Message msg = new Message(); - try { - msg.fromString(badMessage, DataDictionaryTest.getDictionary(), true); - fail(); - } catch (final InvalidMessage e) { - final String emsg = e.getMessage(); - assertNotNull("No exception message", emsg); - assertTrue(emsg.startsWith("Field not found")); - } - } @Test public void testMessageCloneWithGroups() { @@ -866,7 +575,7 @@ public void testFieldOrderAfterClone() { final Message message = new quickfix.fix44.NewOrderSingle(); final quickfix.fix44.NewOrderSingle.NoPartyIDs partyIdGroup = new quickfix.fix44.NewOrderSingle.NoPartyIDs(); partyIdGroup.set(new PartyID("PARTY_1")); - partyIdGroup.set(new PartyIDSource(PartyIDSource.DIRECTED_BROKER_THREE_CHARACTER_ACRONYM_AS_DEFINED_IN_ISITC_ETC_BEST_PRACTICE_GUIDELINES_DOCUMENT)); + partyIdGroup.set(new PartyIDSource(PartyIDSource.ISITCACRONYM)); partyIdGroup.set(new PartyRole(PartyRole.INTRODUCING_FIRM)); message.addGroup(partyIdGroup); final Message clonedMessage = (Message) message.clone(); @@ -984,261 +693,6 @@ public void testHasGroup() { assertFalse("wrong value", message.hasGroup(3, numAllocs.getFieldTag())); } - @Test - public void testIsEmpty() { - final Message message = new Message(); - assertTrue("Message should be empty on construction", message.isEmpty()); - message.getHeader().setField(new BeginString("FIX.4.2")); - assertFalse("Header should contain a field", message.isEmpty()); - message.clear(); - assertTrue("Message should be empty after clear", message.isEmpty()); - message.setField(new Symbol("MSFT")); - assertFalse("Body should contain a field", message.isEmpty()); - message.clear(); - assertTrue("Message should be empty after clear", message.isEmpty()); - message.getTrailer().setField(new CheckSum("10")); - assertFalse("Trailer should contain a field", message.isEmpty()); - message.clear(); - assertTrue("Message should be empty after clear", message.isEmpty()); - } - - @Test - public void testMessageSetGetString() { - final Message message = new Message(); - - try { - message.getString(5); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setString(5, "string5"); - - try { - assertEquals("string5", message.getString(5)); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - - expectedException.expect(FieldException.class); - message.setString(100, null); - } - - @Test - public void testMessageSetGetBoolean() { - final Message message = new Message(); - - try { - message.getBoolean(7); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setBoolean(7, true); - - try { - assertTrue(message.getBoolean(7)); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - } - - @Test - public void testMessageSetGetChar() { - final Message message = new Message(); - - try { - message.getChar(12); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setChar(12, 'a'); - - try { - assertEquals('a', message.getChar(12)); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - } - - @Test - public void testMessageSetGetChars() throws FieldNotFound { - final Message message = new Message(); - - try { - message.getChars(18); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setChars(18, 'a', 'b', '4'); - assertArrayEquals(new char[]{'a', 'b', '4'}, message.getChars(18)); - } - - @Test - public void testMessageSetGetCharsInvalidFormatException() throws FieldNotFound { - expectedException.expect(FieldException.class); - expectedException.expectMessage("invalid char array: [65, 32, 98, 32, 48, 53]"); - - final Message message = new Message(); - message.setString(123, "A b 05"); - message.getChars(123); - } - - @Test - public void testMessageSetGetInt() { - final Message message = new Message(); - - try { - message.getInt(56); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setInt(56, 23); - - try { - assertEquals(23, message.getInt(56)); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - } - - @Test - public void testMessageSetGetDouble() { - final Message message = new Message(); - - try { - message.getDouble(9812); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - message.setDouble(9812, 12.3443); - - try { - assertEquals(12.3443, message.getDouble(9812), 1e-10); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - } - - @Test - public void testMessageSetGetUtcTimeStamp() { - final Message message = new Message(); - - try { - message.getUtcTimeStamp(8); - fail("exception not thrown"); - } catch (final FieldNotFound e) { - } - - final TimeZone timezone = TimeZone.getTimeZone("GMT+0"); - final Calendar calendar = Calendar.getInstance(timezone); - calendar.set(2002, 8, 6, 12, 34, 56); - calendar.set(Calendar.MILLISECOND, 0); - - final LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(calendar.getTimeInMillis()), ZoneOffset.UTC); - message.setUtcTimeStamp(8, time); - - try { - assertEquals(message.getUtcTimeStamp(8), time); - } catch (final FieldNotFound e) { - fail("exception thrown"); - } - } - - @Test - public void testRemoveField() { - final Message message = new Message(); - message.setField(new StringField(12, "value")); - assertTrue(message.isSetField(12)); - message.removeField(12); - assertTrue(!message.isSetField(12)); - } - - @Test - public void testMessageIterator() { - Message message = new Message(); - java.util.Iterator> i = message.iterator(); - assertFalse(i.hasNext()); - try { - assertNull(i.next()); - fail("exception not thrown"); - } catch (final java.util.NoSuchElementException e) { - } - - try { - message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"); - i = message.iterator(); - assertTrue(i.hasNext()); - StringField field = (StringField) i.next(); - assertEquals(108, field.getField()); - assertEquals("30", field.getValue()); - - assertFalse(i.hasNext()); - try { - assertNull(i.next()); - fail("exception not thrown"); - } catch (final java.util.NoSuchElementException e) { - } - - final java.util.Iterator> j = message.getHeader().iterator(); - assertTrue(j.hasNext()); - field = (StringField) j.next(); - assertEquals(8, field.getField()); - assertEquals("FIX.4.2", field.getValue()); - field = (StringField) j.next(); - assertEquals(9, field.getField()); - assertEquals("12", field.getValue()); - field = (StringField) j.next(); - assertEquals(35, field.getField()); - assertEquals("A", field.getValue()); - - assertFalse(j.hasNext()); - try { - assertNull(j.next()); - fail("exception not thrown"); - } catch (final java.util.NoSuchElementException e) { - } - } catch (final InvalidMessage e) { - fail("exception thrown"); - } - } - - @Test - public void testIsAdmin() { - final Message message = new Message(); - - message.getHeader().setString(MsgType.FIELD, MsgType.HEARTBEAT); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.LOGON); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.LOGOUT); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.SEQUENCE_RESET); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.RESEND_REQUEST); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.TEST_REQUEST); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.REJECT); - assertTrue(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.ORDER_SINGLE); - assertFalse(message.isAdmin()); - - message.getHeader().setString(MsgType.FIELD, MsgType.QUOTE_RESPONSE); - assertFalse(message.isAdmin()); - } - @Test public void testComponent() throws Exception { final Instrument instrument = new Instrument(); @@ -1318,79 +772,6 @@ public void testReplaceGroup() throws Exception { assertEquals("C", group.getField(clOrdID).getValue()); } - @Test - public void testFalseMessageStructureException() { - try { - final DataDictionary dd = DataDictionaryTest.getDictionary(); - // duplicated tag 98 - // QFJ-65 - new Message("8=FIX.4.4\0019=22\00135=A\00198=0\00198=0\001108=30\00110=223\001", dd, - true); - // For now, this will not cause an exception if the length and checksum are correct - } catch (final Exception e) { - final String text = e.getMessage(); - assertTrue("Wrong exception message: " + text, !text.contains("Actual body length")); - } - } - - @Test - public void testComponentInGroup() { - try { - final DataDictionary dd = DataDictionaryTest.getDictionary(); - // duplicated tag 98 - // QFJ-65 - // 8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001 - // 57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001 - // 570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001 - // 917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001 - // 552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001 - // 448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001 - // 452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001 - // 452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001 - // 624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001 - // 9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001 - // 524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001 - // 602=BRN FMG0010! 63=8 608-FXXXXX 624=1 637=80.09 687=1.0 654=41296073 9019=1 9023=1 9020=20100201 9021=20100228 539=4 524=805\001 - // 525=D\001538=4\001524=11122556 525=D\001538=51 524=Newedge 525=D 538=60 524=U 525=D 538=54 10=112 - new Message( - "8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001" + - "57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001" + - "570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001" + - "917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001" + - "552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001" + - "448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001" + - "452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001" + - "452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001" + - "624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001" + - "9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001" + - "524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001602=BRN FMG0010!\001" + - "63=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\0019019=1\0019023=1\001" + - "9020=20100201\001021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001" + - "525=D\001538=51\001524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001" + - "602=BRN FMG0010!\00163=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\001" + - "9019=1\0019023=1\0019020=20100201\001021=20100228\001", - dd, true); - // For now, this will not cause an exception if the length and checksum are correct - } catch (final Exception e) { - final String text = e.getMessage(); - assertTrue("Wrong exception message: " + text, !text.contains("Actual body length")); - } - } - - @Test - public void testFalseMessageStructureException2() { - try { - final DataDictionary dd = DataDictionaryTest.getDictionary(); - // duplicated raw data length - // QFJ-121 - new Message("8=FIX.4.4\0019=22\00135=A\00196=X\001108=30\00110=223\001", dd, true); - } catch (final Exception e) { - final String text = e.getMessage(); - assertTrue("Wrong exception message: " + text, - text != null && !text.contains("Actual body length")); - } - } - @Test public void testFieldWithEqualsCharacter() { try { @@ -1441,16 +822,6 @@ public void testMiscFeeType() { } } - /** - * Verify that an empty message can still be "printed" and doesn't result in any exceptions - */ - @Test - public void testEmptyMessageToString() throws Exception { - final Message msg = new quickfix.Message(); - assertNotNull(msg.toString()); - assertTrue("empty message contains no checksum", msg.toString().length() > 0); - } - @Test public void testMessageBytesField() throws Exception { final Logon logon = new Logon(); @@ -1643,7 +1014,7 @@ public void testUnknownFieldsInRepeatingGroupsAndValidation() throws Exception { // QFJ-169 public void testInvalidFieldInGroup() throws Exception { SecurityRequestResult resultCode = new SecurityRequestResult( - SecurityRequestResult.NO_INSTRUMENTS_FOUND_THAT_MATCH_SELECTION_CRITERIA); + SecurityRequestResult.NO_INSTRUMENTS_FOUND); UnderlyingSymbol underlyingSymbolField = new UnderlyingSymbol("UND"); SecurityReqID id = new SecurityReqID("1234"); @@ -1854,40 +1225,6 @@ public void testRepeatingGroupCountWithNonIntegerValues() throws Exception { } } - - // QFJ-770/QFJ-792 - @Test - public void testRepeatingGroupCountWithUnknownFields() throws Exception { - String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|" - + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|" - + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|" - + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|" - + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|"; - - DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary()); - Message message = new Message(); - message.fromString(test.replaceAll("\\|", "\001"), dictionary, true); - Group group = message.getGroup(1, 711); - String underlyingSymbol = group.getString(311); - assertEquals("780508", underlyingSymbol); - } - - @Test - // QFJ-940 - public void testRawString() throws Exception { - - String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|" - + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|" - + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|" - + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|" - + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|"; - - DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary()); - Message message = new Message(); - message.fromString(test.replaceAll("\\|", "\001"), dictionary, true); - assertEquals(test, message.toRawString().replaceAll("\001", "\\|")); - } - // QFJ-722 @Test public void testIfMessageHeaderIsOverwritten() { @@ -1904,34 +1241,6 @@ public void testIfMessageHeaderIsOverwritten() { assertEquals(quickfix.fixt11.Message.Header.class, fixt11Message.getHeader().getClass()); } - // QFJ-722 - @Test - public void testIfMessageHeaderIsCreatedWithEveryConstructor() throws Exception { - final String rawMessage = "8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001"; - final DataDictionary dataDictionary = new DataDictionary(DataDictionaryTest.getDictionary()); - - final Message emptyConstructor = new Message(); - assertNotNull(emptyConstructor.getHeader()); - - final Message secondConstructor = new Message(new int[]{}); - assertNotNull(secondConstructor.getHeader()); - - final Message thirdConstructor = new Message(rawMessage); - assertNotNull(thirdConstructor.getHeader()); - - final Message fourthConstructor = new Message(rawMessage, false); - assertNotNull(fourthConstructor.getHeader()); - - final Message fifthConstructor = new Message(rawMessage, dataDictionary); - assertNotNull(fifthConstructor.getHeader()); - - final Message sixthConstructor = new Message(rawMessage, dataDictionary, false); - assertNotNull(sixthConstructor.getHeader()); - - final Message seventhConstructor = new Message(rawMessage, dataDictionary, dataDictionary, false); - assertNotNull(seventhConstructor.getHeader()); - } - @Test public void shouldConvertToXmlWhenDataDictionaryLoadedWithExternalDTD() throws ConfigError { DataDictionary dataDictionary = new DataDictionary("FIX_External_DTD.xml", DocumentBuilderFactory::newInstance); @@ -1943,15 +1252,13 @@ public void shouldConvertToXmlWhenDataDictionaryLoadedWithExternalDTD() throws C xml = xml.replace("\r", "").replace("\n", "").replaceAll(">\\s+<", "><"); assertEquals("
    ", xml); } - @Test public void shouldConvertToXMLWithoutIndent() { Message message = new Message(); message.setString(Account.FIELD, "test-account"); - assertEquals("
    ", message.toXML()); } - + @Test public void shouldConvertToXMLWithIndent() { Message message = new Message(); @@ -1962,11 +1269,10 @@ public void shouldConvertToXMLWithIndent() { // formatting CDATA elements can be different across JVM's so we have to strip whitespaces before and after for the test to pass // https://bugs.openjdk.java.net/browse/JDK-8215543 xml = xml.replaceAll("\\s+\\s+", ""); - assertEquals("\n" + "\n" + - "
    \n" + " \n" + - " \n" + " \n" + - " \n" + "\n", xml); + "
    \n" + " \n" + + " \n" + " \n" + + " \n" + "\n", xml); } @Test diff --git a/quickfixj-core/src/test/java/quickfix/MessageUtilsTest.java b/quickfixj-core/src/test/java/quickfix/MessageUtilsTest.java index 8038a219c0..803b9d6d5e 100644 --- a/quickfixj-core/src/test/java/quickfix/MessageUtilsTest.java +++ b/quickfixj-core/src/test/java/quickfix/MessageUtilsTest.java @@ -20,13 +20,11 @@ package quickfix; import quickfix.field.ApplVerID; -import quickfix.field.BeginString; import quickfix.field.DefaultApplVerID; import quickfix.field.EmailThreadID; import quickfix.field.EmailType; import quickfix.field.EncryptMethod; import quickfix.field.HeartBtInt; -import quickfix.field.MsgType; import quickfix.field.SenderCompID; import quickfix.field.Subject; import quickfix.field.TargetCompID; @@ -38,24 +36,20 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; import org.junit.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +/** + * NOTE: There are two MessageUtilsTests. + * One in quickfixj-base, one in quickfixj-core, which each test + * some functionality. This test covers some test cases that cannot + * be tested in the quickfixj-base module due to classes that are + * generated later in the compile process, e.g. MessageFactories. + */ public class MessageUtilsTest { - @Test - public void testGetStringField() throws Exception { - String messageString = "8=FIX.4.2\0019=12\00135=X\001108=30\00110=049\001"; - assertEquals("wrong value", "FIX.4.2", MessageUtils.getStringField(messageString, - BeginString.FIELD)); - assertEquals("wrong value", "X", MessageUtils.getStringField(messageString, MsgType.FIELD)); - assertNull(messageString, MessageUtils.getStringField(messageString, SenderCompID.FIELD)); - } - @Test public void testSessionIdFromMessage() throws Exception { Message message = new Logon(); @@ -87,72 +81,6 @@ public void testReverseSessionIdFromMessageWithMissingFields() throws Exception assertEquals(sessionID.getTargetCompID(), SessionID.NOT_SET); } - @Test - public void testSessionIdFromRawMessage() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + - "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - SessionID sessionID = MessageUtils.getSessionID(messageString); - assertEquals(sessionID.getBeginString(), "FIX.4.0"); - assertEquals("TW", sessionID.getSenderCompID()); - assertEquals("ISLD", sessionID.getTargetCompID()); - } - - @Test - public void testReverseSessionIdFromRawMessage() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\00150=TWS\001" + - "142=TWL\00152=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - SessionID sessionID = MessageUtils.getReverseSessionID(messageString); - assertEquals(sessionID.getBeginString(), "FIX.4.0"); - assertEquals("ISLD", sessionID.getSenderCompID()); - assertEquals("TW", sessionID.getTargetCompID()); - assertEquals("TWS", sessionID.getTargetSubID()); - assertEquals("TWL", sessionID.getTargetLocationID()); - } - - @Test - public void testMessageType() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + - "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - assertEquals("A", MessageUtils.getMessageType(messageString)); - } - - @Test - public void testMessageTypeError() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + - "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - try { - MessageUtils.getMessageType(messageString); - fail("expected exception"); - } catch (InvalidMessage e) { - // expected - } - } - - @Test - public void testMessageTypeError2() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00135=1"; - try { - MessageUtils.getMessageType(messageString); - fail("expected exception"); - } catch (InvalidMessage e) { - // expected - } - } - - @Test - public void testGetNonexistentStringField() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + - "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - assertNull(MessageUtils.getStringField(messageString, 35)); - } - - @Test - public void testGetStringFieldWithBadValue() throws Exception { - String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" + - "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223"; - assertNull(MessageUtils.getStringField(messageString, 10)); - } - @Test public void testParse() throws Exception { Session mockSession = mock(Session.class); @@ -162,9 +90,9 @@ public void testParse() throws Exception { String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001"; - Message message = MessageUtils.parse(mockSession, messageString); + Message message = MessageSessionUtils.parse(mockSession, messageString); - assertThat(message, is(notNullValue())); + assertThat(message, notNullValue()); } @Test @@ -177,7 +105,7 @@ public void testLegacyParse() throws Exception { "447=D\001452=4\001448=FIX11\001447=D\001452=36\00160=20060320-03:34:29\00110=169\001"; Message message = MessageUtils.parse(new quickfix.fix40.MessageFactory(), DataDictionaryTest.getDictionary(), data); - assertThat(message, is(notNullValue())); + assertThat(message, notNullValue()); } @Test @@ -192,7 +120,7 @@ public void testParseFixt() throws Exception { email.getHeader().setField(new SenderCompID("SENDER")); email.getHeader().setField(new TargetCompID("TARGET")); - Message message = MessageUtils.parse(mockSession, email.toString()); + Message message = MessageSessionUtils.parse(mockSession, email.toString()); assertThat(message, is(notNullValue())); assertThat(message, instanceOf(quickfix.fix40.Email.class)); @@ -208,7 +136,7 @@ public void testParseFixtLogon() throws Exception { quickfix.fixt11.Logon logon = new quickfix.fixt11.Logon(new EncryptMethod(EncryptMethod.NONE_OTHER), new HeartBtInt(30), new DefaultApplVerID(ApplVerID.FIX42)); - Message message = MessageUtils.parse(mockSession, logon.toString()); + Message message = MessageSessionUtils.parse(mockSession, logon.toString()); assertThat(message, is(notNullValue())); assertThat(message, instanceOf(quickfix.fixt11.Logon.class)); @@ -223,7 +151,7 @@ public void testParseFixtLogout() throws Exception { quickfix.fixt11.Logout logout = new quickfix.fixt11.Logout(); - Message message = MessageUtils.parse(mockSession, logout.toString()); + Message message = MessageSessionUtils.parse(mockSession, logout.toString()); assertThat(message, is(notNullValue())); assertThat(message, instanceOf(quickfix.fixt11.Logout.class)); @@ -241,7 +169,7 @@ public void testParseFix50() throws Exception { email.getHeader().setField(new SenderCompID("SENDER")); email.getHeader().setField(new TargetCompID("TARGET")); - Message message = MessageUtils.parse(mockSession, email.toString()); + Message message = MessageSessionUtils.parse(mockSession, email.toString()); assertThat(message, is(notNullValue())); assertThat(message, instanceOf(quickfix.fix50.Email.class)); @@ -262,9 +190,9 @@ public void testParseMessageWithoutChecksumValidation() throws InvalidMessage { String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" + "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=283\001"; - Message message = MessageUtils.parse(mockSession, messageString); + Message message = MessageSessionUtils.parse(mockSession, messageString); - assertThat(message, is(notNullValue())); + assertThat(message, notNullValue()); } } diff --git a/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java b/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java index 1132a307f2..a6cf3ea396 100644 --- a/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java +++ b/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java @@ -148,6 +148,25 @@ private quickfix.fix50sp2.QuoteRequest.NoRelatedSym buildNestedGroupWithStandard return gNoRelatedSym; } + private quickfix.fixlatest.QuoteRequest.NoRelatedSym buildNestedGroupWithStandardFieldsFIXLatest( + String settingValue) { + // The root group + final quickfix.fixlatest.QuoteRequest.NoRelatedSym gNoRelatedSym = new quickfix.fixlatest.QuoteRequest.NoRelatedSym(); + + // The nested group + final quickfix.fixlatest.QuoteRequest.NoRelatedSym.NoLegs nestedgroup = new quickfix.fixlatest.QuoteRequest.NoRelatedSym.NoLegs(); + nestedgroup.setField(new LegSymbol(settingValue)); + gNoRelatedSym.addGroup(nestedgroup); + + // Adding a second fake nested group to avoid being the case of having + // one element which is not relevant :-) + final quickfix.fixlatest.QuoteRequest.NoRelatedSym.NoLegs oneMoreNestedgroup = new quickfix.fixlatest.QuoteRequest.NoRelatedSym.NoLegs(); + oneMoreNestedgroup.setField(new LegSymbol("Donald")); + gNoRelatedSym.addGroup(oneMoreNestedgroup); + + return gNoRelatedSym; + } + @Test public void testSettingGettingNestedGroupWithStandardFields() throws FieldNotFound { final String settingValue = "SETTING_VALUE"; @@ -356,6 +375,32 @@ public void testValidationWithNestedGroupAndStandardFieldsFIX50SP2() throws Inva assertEquals(2, validatedMessage.getGroupCount(gNoRelatedSym.getFieldTag())); } + @Test + public void testValidationWithNestedGroupAndStandardFieldsFIXLatest() throws InvalidMessage, ConfigError { + final quickfix.fixlatest.QuoteRequest quoteRequest = new quickfix.fixlatest.QuoteRequest(); + + final quickfix.field.QuoteReqID gQuoteReqID = new quickfix.field.QuoteReqID(); + gQuoteReqID.setValue("12342"); + quoteRequest.setField(gQuoteReqID); + + final quickfix.fixlatest.QuoteRequest.NoRelatedSym gNoRelatedSym = buildNestedGroupWithStandardFieldsFIXLatest("DEFAULT_VALUE"); + gNoRelatedSym.setField(new Symbol("SYM00")); + gNoRelatedSym.setField(new SettlDate2("20120801")); + + quoteRequest.addGroup(gNoRelatedSym); + quoteRequest.addGroup(gNoRelatedSym); + + final String sourceFIXString = quoteRequest.toString(); + final DataDictionary fixDataDictionary = new DataDictionary("FIXLatest.xml"); + final quickfix.fixlatest.QuoteRequest validatedMessage = (quickfix.fixlatest.QuoteRequest) messageFactory.create(FixVersions.FIXLATEST, QuoteRequest.MSGTYPE); + validatedMessage.fromString(sourceFIXString, fixDataDictionary, true); + + String validateFIXString = validatedMessage.toString(); + + assertEquals("Message validation failed", sourceFIXString, validateFIXString); + assertEquals(2, validatedMessage.getGroupCount(gNoRelatedSym.getFieldTag())); + } + @Test public void testValidationWithNestedGroupAndStandardFieldsWithoutDelimiter() throws InvalidMessage { final quickfix.fix44.QuoteRequest quoteRequest = new quickfix.fix44.QuoteRequest(); diff --git a/quickfixj-core/src/test/java/quickfix/SessionTest.java b/quickfixj-core/src/test/java/quickfix/SessionTest.java index 7fb7627e4e..daadf31032 100644 --- a/quickfixj-core/src/test/java/quickfix/SessionTest.java +++ b/quickfixj-core/src/test/java/quickfix/SessionTest.java @@ -2021,7 +2021,7 @@ public void testGenerateRejectAndTargetSeqNum() throws Exception { "8=FIX.4.2\0019=0113\00135=4\00134=223\00143=Y\001122=20100908-17:59:30.642\00149=THEM\00156=US\001369=178\00152=20100908-17:59:30.642\001123=Y\00136=225\00110=110\001", "8=FIX.4.2\0019=0246\00135=8\001115=THEM\00134=225\00143=Y\001122=20100908-17:52:37.920\00149=THEM\00156=US\001369=178\00152=20100908-17:59:30.642\00137=10118506\00111=a00000052.1\00117=17537743\00120=0\001150=4\00139=4\00155=ETFC\00154=1\00138=500000\00144=0.998\00132=0\00131=0\001151=0\00114=0\0016=0\00160=20100908-17:52:37.920\00110=80\001" }; for (String message : messages) - session.next(MessageUtils.parse(session, message)); + session.next(MessageSessionUtils.parse(session, message)); assertEquals(226, session.getStore().getNextTargetMsgSeqNum()); } diff --git a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java index 053dbb2018..6d31b9293f 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java @@ -22,6 +22,8 @@ import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; +import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +44,7 @@ import quickfix.mina.ProtocolFactory; import quickfix.mina.SessionConnector; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import java.lang.reflect.Field; @@ -51,10 +54,14 @@ import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import org.apache.mina.util.AvailablePortFinder; import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + public class SSLCertificateTest { // Note: To diagnose cipher suite errors, run with -Djavax.net.debug=ssl:handshake @@ -102,6 +109,111 @@ public void shouldAuthenticateServerCertificate() throws Exception { } } + /** + * Server certificate has Common Name = localhost and no Server Alternative Name extension. + */ + @Test + public void shouldAuthenticateServerNameUsingServerCommonName() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-cn.keystore", false, + "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-cn.truststore", + CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1683903911")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + /** + * Server certificate has Common Name = server, but it has Server Alternative Name extension (DNS name). + */ + @Test + public void shouldAuthenticateServerNameUsingSNIExtension() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-sni.keystore", false, + "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-sni.truststore", + CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1683904647")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + /** + * Server certificate has Common Name = server and no Server Alternative Name extension. + */ + @Test + public void shouldFailWhenHostnameDoesNotMatchServerName() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-bad-cn.keystore", false, + "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-bad-cn.truststore", + CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", "HTTPS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown("No name matching localhost found", SSLHandshakeException.class); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown("Received fatal alert: certificate_unknown", SSLHandshakeException.class); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + @Test public void shouldAuthenticateServerAndClientCertificates() throws Exception { int freePort = AvailablePortFinder.getNextAvailable(); @@ -446,19 +558,21 @@ static abstract class TestConnector { private final SessionConnector connector; private final CountDownLatch exceptionThrownLatch; + private final AtomicReference exception; public TestConnector(SessionSettings sessionSettings) throws ConfigError { this.connector = prepareConnector(sessionSettings); this.exceptionThrownLatch = new CountDownLatch(1); + this.exception = new AtomicReference<>(); } private SessionConnector prepareConnector(SessionSettings sessionSettings) throws ConfigError { SessionConnector sessionConnector = createConnector(sessionSettings); sessionConnector.setIoFilterChainBuilder(chain -> chain.addFirst("Exception handler", new IoFilterAdapter() { @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { LOGGER.info("exceptionCaught", cause); + exception.set(cause); exceptionThrownLatch.countDown(); nextFilter.exceptionCaught(session, cause); } @@ -476,12 +590,12 @@ private SSLSession findSSLSession(Session session) throws Exception { return null; IoFilterChain filterChain = ioSession.getFilterChain(); - SSLFilter sslFilter = (SSLFilter) filterChain.get(SSLSupport.FILTER_NAME); + SslFilter sslFilter = (SslFilter) filterChain.get(SSLSupport.FILTER_NAME); if (sslFilter == null) return null; - return sslFilter.getSslSession(ioSession); + return (SSLSession) ioSession.getAttribute(SslFilter.SSL_SECURED); } private Session findSession(SessionID sessionID) { @@ -541,14 +655,14 @@ public void assertNotAuthenticated(SessionID sessionID) throws Exception { } } - public void assertLoggedOn(SessionID sessionID) throws InterruptedException { + public void assertLoggedOn(SessionID sessionID) { Session session = findSession(sessionID); if (!session.isLoggedOn()) throw new AssertionError("Session is not logged on"); } - public void assertNotLoggedOn(SessionID sessionID) throws InterruptedException { + public void assertNotLoggedOn(SessionID sessionID) { Session session = findSession(sessionID); if (session.isLoggedOn()) @@ -556,11 +670,25 @@ public void assertNotLoggedOn(SessionID sessionID) throws InterruptedException { } public void assertSslExceptionThrown() throws Exception { + assertSslExceptionThrown(null, null); + } + + public void assertSslExceptionThrown(String errorMessage, Class errorType) throws Exception { boolean reachedZero = exceptionThrownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); if (!reachedZero) { throw new AssertionError("No SSL exception thrown"); } + + Throwable throwable = exception.get(); + + if (errorMessage != null) { + assertEquals(errorMessage, throwable.getMessage()); + } + + if (errorType != null) { + assertSame(errorType, throwable.getClass()); + } } public void assertNoSslExceptionThrown() throws Exception { @@ -711,8 +839,14 @@ private SessionSettings createAcceptorSettings(String keyStoreName, boolean need } private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, - String protocols, String senderId, String targetId, String port, String keyStoreType, - String trustStoreType) { + String protocols, String senderId, String targetId, String port, String keyStoreType, + String trustStoreType) { + return createInitiatorSettings(keyStoreName, trustStoreName, cipherSuites, protocols, senderId, targetId, port,keyStoreType, trustStoreType, null); + } + + private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, + String protocols, String senderId, String targetId, String port, String keyStoreType, + String trustStoreType, String endpointIdentificationAlgorithm) { HashMap defaults = new HashMap<>(); defaults.put("ConnectionType", "initiator"); defaults.put("SocketConnectProtocol", ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); @@ -748,6 +882,10 @@ private SessionSettings createInitiatorSettings(String keyStoreName, String trus defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); } + if (endpointIdentificationAlgorithm != null) { + defaults.put(SSLSupport.SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, endpointIdentificationAlgorithm); + } + SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, senderId, targetId); SessionSettings sessionSettings = new SessionSettings(); diff --git a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java index 47afdb6cd4..08f7744b97 100644 --- a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java +++ b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java @@ -75,6 +75,11 @@ public void onMessage(quickfix.fix50sp2.NewOrderSingle message, SessionID sessio process(message, sessionID); } + public void onMessage(quickfix.fixlatest.NewOrderSingle message, SessionID sessionID) + throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { + process(message, sessionID); + } + public void onMessage(quickfix.fix50.SecurityDefinition message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { try { @@ -84,6 +89,15 @@ public void onMessage(quickfix.fix50.SecurityDefinition message, SessionID sessi } } + public void onMessage(quickfix.fixlatest.SecurityDefinition message, SessionID sessionID) + throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { + try { + Session.sendToTarget(message, sessionID); + } catch (SessionNotFound snf) { + snf.printStackTrace(); + } + } + public void onMessage(quickfix.fix44.NewOrderSingle message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { process(message, sessionID); diff --git a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATServer.java b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATServer.java index 3a0c0807a1..b637a0a306 100644 --- a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATServer.java +++ b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATServer.java @@ -53,6 +53,8 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ATServer implements Runnable { private final Logger log = LoggerFactory.getLogger(ATServer.class); @@ -72,6 +74,9 @@ public class ATServer implements Runnable { private String keyStorePassword; private Map overridenProperties = null; + //Pattern to get FIX version from test location example :"fixLatest/20_SimultaneousResendRequest.def" + protected static final Pattern fixVersionFromTestLocationPattern = Pattern.compile("^(.*?)(?:[\\/,\\\\].*)$"); + public ATServer() { // defaults } @@ -90,9 +95,13 @@ public ATServer(TestSuite suite, boolean threaded, int transportType, int port, this.overridenProperties = overridenProperties; this.transportType = transportType; this.port = port; + // determine the FIX versions, by convention the first part of the name (location) of the test. Enumeration e = suite.tests(); while (e.hasMoreElements()) { - fixVersions.add(e.nextElement().toString().substring(0, 5)); + Matcher matcher = fixVersionFromTestLocationPattern.matcher(e.nextElement().toString()); + if (matcher.find()) { + fixVersions.add(matcher.group(1)); + } } resetOnDisconnect = true; log.info("creating sessions for {}", fixVersions); @@ -158,6 +167,10 @@ public void run() { acceptFixVersion(FixVersions.BEGINSTRING_FIXT11); } + if (fixVersions.contains("fixLatest")) { + acceptFixVersion(FixVersions.BEGINSTRING_FIXT11); + } + ATApplication application = new ATApplication(); MessageStoreFactory factory = usingMemoryStore ? new MemoryStoreFactory() diff --git a/quickfixj-core/src/test/java/quickfix/test/acceptance/AcceptanceTestSuite.java b/quickfixj-core/src/test/java/quickfix/test/acceptance/AcceptanceTestSuite.java index 72317fb7bd..3fcf2c9b9e 100644 --- a/quickfixj-core/src/test/java/quickfix/test/acceptance/AcceptanceTestSuite.java +++ b/quickfixj-core/src/test/java/quickfix/test/acceptance/AcceptanceTestSuite.java @@ -73,6 +73,7 @@ public void run(TestResult result) { TestConnection connection = null; String failureString = "test " + filename + " failed with message: "; try { + log.info("Running test {}, filename : {}", this.testname, this.filename); connection = new TestConnection(); List testSteps = load(filename); for (TestStep testStep : testSteps) { @@ -171,6 +172,7 @@ public AcceptanceTestSuite(String testDirectory, boolean multithreaded, Map56=ISLD369=098=0108=21137=10 +E8=FIXT.1.19=7435=A34=149=ISLD52=00000000-00:00:00.00056=TW369=198=0108=21137=1010=0 + +I8=FIXT.1.135=134=249=TW52=